多用户通信系统 - Socket 编程使用对象流进行通信的踩坑记录
多用户通信系统 - Socket 编程使用对象流进行通信的踩坑记录
在多用户通信系统项目中,客户端 Socket
与服务端 Socket
的所有通信均使用对象流的方式来完成。一切很好,直到在完成离线发送消息/发送文件功能时,我才踩到在 Socket 编程中使用对象流通信的坑
java.io.StreamCorruptedException: invalid stream header: 7371007E
在数据通道某一端的 Socket
使用同一个 ObjectOutputStream
写多个对象,但在另外一端的 Socket
使用不同的 ObjectInputStream
来读数据通道内的多个对象,就会产生异常:java.io.StreamCorruptedException: invalid stream header: 7371007E
。
参考:StackOverflow - java.io.StreamCorruptedException: invalid stream header: 7371007E 高赞回答可知该异常发生的原因(虽然提问者问得不太好):
The error you get is because the objectOutputStream writes a header, which is expected by objectIutputStream. As you are not writing multiple streams, but simply multiple objects, then the next objectInputStream created on the socket input fails to find a second header, and throws an exception.
简单来说就是:ObjectOutputStream
写(多个)对象数据时,会在最开始写一个 header
,而该 header
是 ObjectInputStream
期待获取到的,获取到后才进行读(多个)对象数据的操作,若获取不到,就会抛出该异常。
对于同一个 ObjectOutputStream
写出的多个对象数据,第一个 ObjectInputStream
在读取时还能读到 header
,因此可以正常 readObject
,但第二个 ObjectInputStream
就读不到期待的 header
了,因此抛出异常。
java.io.StreamCorruptedException: invalid type code: AC
在数据通道某一端的 Socket
使用不同的 ObjectOutputStream
写多个对象,但在另外一端的 Socket
使用同一个 ObjectInputStream
来读数据通道内的多个对象,就会产生异常:java.io.StreamCorruptedException: invalid type code: AC
参考:StackOverflow - StreamCorruptedException: invalid type code: AC 告知回答可知该异常发生的原因:
The underlying problem is that you are using a new
ObjectOutputStream
to write to a stream that you have already used a priorObjectOutputStream
to write to. These streams have headers which are written and read by the respective constructors, so if you create anotherObjectOutputStream
you will write a new header, which starts with - guess what? -0xAC
, and the existingObjectInputStream
isn’t expecting another header at this point so it barfs.
问题依然出在对象流的 header
上面。
对于使用不同的 ObjectOutputStream
分别单独写出的多个对象数据,每个对象数据都有一个 header
开头,此时如果只用同一个 ObjectInputStream
来全部读取,那么 ObjectInputStream
只会在最开始读取一次 header
,并认为后面的都是对象数据,所以在读到第二个 ObjectOutputStream
写出的 header
时,就会抛出异常
如何避坑
对于一个数据连接通道的两端的 Socket
,保证一端的 Socket
所绑定的某个 ObjectOutputStream
写出的所有对象数据,总是被另外一段的 Socket
所绑定的某个 ObjectInputStream
全部读取,就可以避免上面的两个异常发生!
在 StackOverflow - StreamCorruptedException: invalid type code: AC 也给出了最佳实践:
Use a single OOS and OIS for the life of the socket, and don’t use any other streams on the socket.
And don’t use any other streams or Readers or Writers on the same socket. The object stream APIs can handle all Java primitive datatypes and all Serializable classes.
在 Socket
的整个生命周期中,从始至终只使用一个 ObjectInputStream
和一个 ObjectOutputStream
来进行网络通信。另外,不要使用其他的流,建议只使用对象流,因为对象流可以处理所有 Java 原生类型以及所有序列化类。
多用户通信系统项目中解决以上异常
虽然最佳实践是建议在 Socket
的整个生命周期中,只使用一个 ObjectInputStream
和一个 ObjectOutputStream
,但是多用户通信系统项目做到离线发送消息/文件这个功能时,已经接近尾声,此前每次使用对象流通信,都是在数据通道两端的 Socket
对象上都创建一个新的对象流,要遵循最佳实践,改动较大。
因此,这里就不遵循最佳实践,而是每次通信,都在两端的 Socket
对象上创建新的对象流。
QQServer
项目中 com.hspedu.qqserver.service.QQServer.java
服务端将多个离线私聊/文件消息,转发给刚上线登录的用户时,确保写每个 Message
对象时,都创建一个新的 ObjectOutputStream
!
1 | ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream()); |
QQClient
项目中 com.hspedu.qqclient.service.ClientConnectServerThread.java
客户端持有 Socket
的线程不断运行尝试读取数据,每次读取 Message
对象数据,都会创建新的 ObjectInputStream
来读取,这里不需要更改
1 |
|