文章目录
1 ObjectInputStream和ObjectOutputStream之间通信的死锁现象
1.1 第一个解释
Socket编程中一个很奇怪的现象:用DataInputStream对象没错,但改为ObjectInputStream就出错了,而且try…catch还抓不到异常。
例如在Socket客户端中,使用DataInputStream进行下述编程,很正常,不会出现问题:
DataInputStream dis = new DataInputStream(socket.getInputStream);
DataOutputStream dos = new DataOutputStream(socket.getOutputStream);
但若改用ObjectInputStream对象进行编程,如下:
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream);
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream);
这时程序就死锁住了,不报错或异常出来,用try…catch也抓不到异常信息。
解决方案:将dis与dos的顺序换一下,即dos在dis之前,这样程序又恢复正常了,不会出现死锁卡住的现象。如下:
ObjectOutputStream dos = new ObjectOutputStream(socket.getOutputStream);
ObjectInputStream dis = new ObjectInputStream(socket.getInputStream);
1.2 第二个解释
Socket使用ObjectInputStream与ObjectOutputStream读写数据问题
服务端一直阻塞。
原因
ObjectInputStreamAPI:Creates an ObjectInputStream that reads from the specified InputStream. A serialization stream header is read from the stream and verified. This constructor will block until the corresponding ObjectOutputStream has written and flushed the header.
意思是,创建一个ObjectInputStream,从指定流中InputStream读取数据,从流中读取序列化标头进行验证,那么这个构造函数将被阻塞,直到相应的ObjectOutputStream已经写入数据并刷新了报头。即,读取与写入是一一对应的,代码中,只需将client中的创建流的顺序更改一下即可。
————————————————
版权声明:本文为CSDN博主「tigerboy1974」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/tigerboy1974/article/details/104803221
1.3 解决办法
服务器端和客户端操作的是同一个socket,两边实例化对象流的时候输入和输出要岔开实例化。即:客户端按照对象输出流、对象输入流的顺序实例化,服务器端按照对象输入流、对象输出流的顺序实例化。
2 对象输出流无法写入的问题
对象输出流写入后需要手动flush才能真正写入。
2.1 哪些写入流操作需要手动flush?
2.1.1 字节流OutputStream
OutputStream的flush方法是一个空方法,这意味着除非子类覆写这个方法,否则子类并不会有实际的flush操作。
OutputStream
的直接子类有:ByteArrayOutputStream
、FileOutputStream
、FilterOutputStream
、ObjectOutputStream
、OutputStream
、PipedOutputStream
等几个类。
注意:这里的子类 OutputStream
是包 org.omg.CORBA.portable
的。
-
对于
FileOutputStream
、ByteArrayOutputStream
、org.omg.CORBA.portable.OutputStream
类它们的 flush() 方法均是从父类继承的 flush 方法。 -
FilterOutputStream
类重写了 flush() 方法,但是实质还是调用父类的 flush() 方法。 -
ObjectOutputStream
、PipedOutputStream
类重写了 flush() 方法。因此这两个类写入后需要手动flush。 -
BufferedOutputStream
是带有缓冲区的字节流,因此也需要手动flush。
2.1.2 字符流Writer
一般使用字符流之后需要调用一下flush()
或者 close()
方法。
2.1.3 总结
- 字节流中只有
ObjectOutputStream
和PipedOutputStream
写入后需要手动刷新flush才能即时写入。否则只能等到流关闭的时候一次性写入。 - 字符流Writer都需要手动flush。
BufferedOutputStream
和BufferedWriter
需要手动flush。
2.2 延展:Java io中的flush操作
参考:http://www.veryitman.com/2019/05/19/JavaIO%E4%B8%AD%E7%A5%9E%E5%A5%87%E7%9A%84flush/
3 常见的异常
3.1 EOFException
当对象读取流读到结尾时会有此异常。此时请检查:
- 如果是从文件中读取对象,对象读取流之前不要实例化对象写入流。具体参考之前的一篇博客。
- 如果是socket编程对象读取流时报此异常,需要检查写入之前是否在另一端先向socket写入了数据。
- 循环读取的时候,检查是否多读了。
3.2 OptionalDataException
用对象流写入和读取的操作应该是对应的。即
- 用
readObject
读取的内容必须来自writeObject
写入的对象。查看jdk文档后发现readObject
在这种情况下报OptionalDataException,即读取了基本数据类型(int, String, boolean等)。
OptionalDataException - Primitive data was found in the stream instead of objects.
readInt
读取的必须是writeInt
写入的内容。readUTF
读取的必须是writeUTF
写入的内容。(但如果是writeObject(String)
则仍然需要用readObject()
以对象方式读取)。其他的基本类型也类似。