使用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

解决办法:

既然直接用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     }

ObjectWriteRead

时间: 2024-08-05 19:19:28

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

我看不下去鸟。。。。Java和C#的socket通信真的简单吗?

这几天在博客园上看到好几个写Java和C#的socket通信的帖子.但是都为指出其中关键点. C# socket通信组件有很多,在vs 使用nuget搜索socket组件有很多类似的.本人使用的是自己开发的一套组件. Java socket通信的组件也有很多,常用的大多数都是用的mina或者netty.游戏行业使用也是居多. 关于socket的底层写法,实在太多,我就不在BB. 这里我想说,C#和C++或者叫VC++把是使用小端序作为字节序.而java使用的是大端序作为字节序. 也就是说比如一个

Java和C#的socket通信相关(转)

这几天在博客园上看到好几个写Java和C#的socket通信的帖子.但是都为指出其中关键点. C# socket通信组件有很多,在vs 使用nuget搜索socket组件有很多类似的.本人使用的是自己开发的一套组件. Java socket通信的组件也有很多,常用的大多数都是用的mina或者netty.游戏行业使用也是居多. 关于socket的底层写法,实在太多,我就不在BB. 这里我想说,C#和C++或者叫VC++把是使用小端序作为字节序.而java使用的是大端序作为字节序. 也就是说比如一个

[java]基于UDP的Socket通信Demo

java课编程作业:在老师给的demo的基础上实现客户端发送数据到服务器端,服务器端接受客户端后进行数据广播. 整体功能类似于聊天室,代码部分不是太难,但是在本机测试的时候出现这样的问题: 服务端通过将每一个Socket客户端的IP存入Set集合,每次接受到数据后都向当前所有的IP转发.但是本机演示的时候所有开的ChatClient客户端都是同一IP,怎么测试呢? 解决办法就是本机测试时候服务端向多个不同的端口转发就好了,这样跑起来的客户端是在不同端口上进行监听的(只是为了实现广播,实际应用下还

Java中Socket通信的知识回顾---学习笔记

两台计算机进行通信的基本前提: (1)IP地址: 每台计算机都有自己独一无二的IP地址,根据IP地址判断与哪台计算机进行通信. (2)端口号: 每个应用程序都有自己专属的端口,根据端口号判断与计算机中的哪个应用程序进行通信. 说明: <1>用于区分不同应用程序 <2>端口号的范围:0-65535,其中0-1023是为系统保留的端口号 <3>常用的协议的端口号: http:80 ftp:21 telnet:23 <4>IP地址+端口号=Socket,Socke

Socket通信——C++server端和Javaclient

一句话来说就是.C++和Java 通过socket进行通信.传输数据.通过发送"字节流"就可以. 字节对于C++和java来说是通用的.可是传输的过程有很多问题须要注意,我为了弄清楚这个过程,查了一些资料,做了一些整理. 不了解C++ socket编程,能够看这篇博客: Linux 下:socket通信(Linux下,C/C++语言):http://blog.csdn.net/giantpoplar/article/details/47657303 Windows下:winsock:h

java学习小笔记(三.socket通信)【转】

三,socket通信1.http://blog.csdn.net/kongxx/article/details/7288896这个人写的关于socket通信不错,循序渐进式的讲解,用代码示例说明,运用流和socket进行远程通讯 2.最简单的socket是一个服务端对应一个客户端 server的写法 ServerSocket server = new ServerSocket(10000); Socket socket = server.accept(); BufferedReader in =

Socket通信——C++服务器端和Java客户端

一句话来说就是,C++和Java 通过socket进行通信.数据传输,通过发送"字节流"即可. 字节对于C++和java来说是通用的,但是传输的过程有许多问题需要注意,我为了弄清楚这个过程,查了一些资料,做了一些整理. 不了解C++ socket编程,可以看这篇博客: Linux 下:socket通信(Linux下,C/C++语言):http://blog.csdn.net/giantpoplar/article/details/47657303 Windows下:winsock:ht

TCP及socket通信原理

一.网络互联模型 因特网在刚面世时,只有同一制造商生产的计算机才能彼此通信,制定网络互联模型的目的就是为异种的计算机互连提供一个共同的基础和标准框架,并为保持相关标准的一致性和兼容性提供共同的参考. 互联参考模型: OSI七层模型(Open System Interconnect):应用层.表示层.会话层.传输层.网络层.数据链路层.物理层 DoD四层模型:是OSI七层模型的浓缩版,包括 进程/应用层.主机到主机层.因特网层.网络接入层 以上两种模型是层次型的,分层模型的优点主要在于: ①将网络

java socket通信-传输文件图片--传输图片

ClientTcpSend.java   客户端发送类 package com.yjf.test; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.net.InetSocketAddress; import java.net.Socket; public class ClientTcpSend { public static String clien