使用ObjectOutputStream进行socket通信的时候出现固定读到四个字节乱码的问题

问题描述:

最近在写一个通信相关的项目,服务器端和客户端通过socket进行通信。本来想利用read的阻塞特性,服务器端和客户端按照一定的流程进行文件读写。结果发现客户端或者服务器read方法一直都返回乱码。而且读到的一端可能是客户端,可能是服务器端,固定的读到前面有四个字节的乱码,后续读到的字节码都是正常的。

原因分析:

开始以为是流没有正常关闭。修改了代码确保正确关闭之后,发现即使重新启动服务器和客户端,还是会固定读到四个字节乱码。后面查资料分析才找出真正的原因:由于我实现的socket通信既有字符串通信,又有对象通信。所以我在传递字符串的时候,使用的是socket.getOutputStream得到的流。而在进行对象传输的时候,我在前面的输出流外面包裹了一层ObjectOutputStream。因为我是在一开始就对socket的输出流进行了包裹,而如果用ObjectOutputStream装饰输出流,默认的会自动在流前面带上四个字节的前缀。而因为开始我发消息只是发送字符串,所以我是直接使用socket的输出流。这就导致将前面的四个字节前缀发送出去,导致最终的乱码。具体参见下面相关代码:

 1 /**
 2      * Creates an ObjectOutputStream that writes to the specified OutputStream.
 3      * This constructor writes the serialization stream header to the
 4      * underlying stream; callers may wish to flush the stream immediately to
 5      * ensure that constructors for receiving ObjectInputStreams will not block
 6      * when reading the header.
 7      *
 8      * <p>If a security manager is installed, this constructor will check for
 9      * the "enableSubclassImplementation" SerializablePermission when invoked
10      * directly or indirectly by the constructor of a subclass which overrides
11      * the ObjectOutputStream.putFields or ObjectOutputStream.writeUnshared
12      * methods.
13      *
14      * @param    out output stream to write to
15      * @throws    IOException if an I/O error occurs while writing stream header
16      * @throws    SecurityException if untrusted subclass illegally overrides
17      *         security-sensitive methods
18      * @throws    NullPointerException if <code>out</code> is <code>null</code>
19      * @since    1.4
20      * @see    ObjectOutputStream#ObjectOutputStream()
21      * @see    ObjectOutputStream#putFields()
22      * @see    ObjectInputStream#ObjectInputStream(InputStream)
23      */
24     public ObjectOutputStream(OutputStream out) throws IOException {
25     verifySubclass();
26     bout = new BlockDataOutputStream(out);
27     handles = new HandleTable(10, (float) 3.00);
28     subs = new ReplaceTable(10, (float) 3.00);
29     enableOverride = false;
30     writeStreamHeader();
31     bout.setBlockDataMode(true);
32         if (extendedDebugInfo) {
33         debugInfoStack = new DebugTraceInfoStack();
34     } else {
35         debugInfoStack = null;
36         }   
37     }
38 
39 
40 
41     /**
42      * The writeStreamHeader method is provided so subclasses can append or
43      * prepend their own header to the stream.  It writes the magic number and
44      * version to the stream.
45      *
46      * @throws    IOException if I/O errors occur while writing to the underlying
47      *         stream
48      */
49     protected void writeStreamHeader() throws IOException {
50     bout.writeShort(STREAM_MAGIC);
51     bout.writeShort(STREAM_VERSION);
52     }

解决办法:

既然直接用ObjectOutputStream将原来的socket的输出流进行包裹之后会出现固定四个字节的乱码,那么可以考虑用原来的socket输出流进行写数据的时候,接收方固定丢弃四个字节乱码。这样虽然可以实现,但是总感觉很别扭。最终我优化了相关的读写对象方法,只是用原来socket的输出流进行对象读写,具体代码实现如下:

 1     public <T> void writeObj(T obj) throws ZSocketException {
 2         if (obj == null) {
 3             return;
 4         }
 5         try(ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
 6             ObjectOutputStream objOut = new ObjectOutputStream(byteOut)) {   // 这个只是为了计算出对象大小而用的中介输出流
 7             objOut.writeObject(obj);
 8             byte[] ObjByte = byteOut.toByteArray();
 9             Header header = new Header(StringMsgType.OBJECT, ObjByte.length);
10             HeaderAnalyser analyser = new HeaderAnalyser();
11 
12             // 先写消息头,再写消息内容
13             output.write(analyser.formatHeader(header), 0, Constants.HEADER_LEN);
14             output.write(ObjByte, 0, ObjByte.length);
15             output.flush();
16 
17         } catch (IOException e) {
18             throw new ZSocketException(e);
19         }
20     }
21 
22     public <T> T readObj(long len, Class<T> clazz) throws ZSocketException {
23         if (len < 0 || clazz == null) {
24             throw new IllegalArgumentException("Negative read length or null object class!");
25         }
26 
27         try(ByteArrayOutputStream out = new ByteArrayOutputStream(Constants.BUFF_SIZE)) {
28             writeData(input, out, len);
29             try (ByteArrayInputStream byteIn = new ByteArrayInputStream(out.toByteArray());
30                 ObjectInputStream objIn = new ObjectInputStream(byteIn)) {
31                 @SuppressWarnings("unchecked")
32                 T result =  (T) objIn.readObject();
33                 return result;
34             }
35         } catch (Exception e) {
36             throw new ZSocketException(e);
37         }
38 
39     }

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏猿人谷

总结---3

Email relay 和Email access分别用了什么协议? 答:SMTP,POP3 1:多态是如何实现绑定的? 多态的绑定可以分为运行是多态和编译时多...

1827
来自专栏博客园

WPF Binding学习(四) 绑定各种数据源

在这里我们使用了ListView控件和GridView控件来显示数据,这两个控件从表面来看应该属于同一级别的控件。实际上并非如此!ListView是ListBo...

1053
来自专栏JetpropelledSnake

Python入门之函数的装饰器

本章目录:     装饰器:         一、为什么要用装饰器         二、什么是装饰器         三、无参装饰器         四、装饰器...

3507
来自专栏JAVA后端开发

activiti通过扩展点重写节点行为

在activit项目中,有时需要重写节点的behaviour,但如果将代码反编译,会为后续升级,及项目打包带为不方便。   其实 acitivit已经提供了扩...

1285
来自专栏一名合格java开发的自我修养

springMVC参数绑定

处理器形参中添加如下类型的参数处理注解适配器会默认识别并进行赋值。 1 HttpServletRequest 通过request对象获取请求信息 2 Htt...

702
来自专栏Danny的专栏

在Cookie中存储对象

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huyuyang6688/article/...

794
来自专栏Java学习网

Java Web中JSP中6种动作概况知识点总结——每日一语法学习

JSP动作利用XML语法格式的标记来控制Servlet引擎的行为。利用JSP动作可以动态地插入文件、重用JavaBean组件、把用户重定向到另外的页面、为Jav...

3484
来自专栏Golang语言社区

Golang语言 - 以任意类型的slices作为输入参数

最近参与的一个业余项目,go-linq,让我了解到Go语言的类型系统并不是为任何类面向 对象编程而设计的。没有泛型,没有类型继承,也没有提供任何对这些特性有用的...

3418
来自专栏Android相关

Android中的Proguard使用

之前介绍了如何使用命令行将Jar包根据配置文件进行ProGuard,以及ProGuard的过程,会遇到的问题等。接下来会介绍常用的ProGuard如何配置参数。...

733
来自专栏青枫的专栏

java基础学习_面向对象(上)03_day08总结

============================================================================= ==...

541

扫码关注云+社区