Socket编程中的踩坑点


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 的直接子类有:ByteArrayOutputStreamFileOutputStreamFilterOutputStreamObjectOutputStreamOutputStreamPipedOutputStream 等几个类。

注意:这里的子类 OutputStream 是包 org.omg.CORBA.portable 的。

  • 对于 FileOutputStreamByteArrayOutputStreamorg.omg.CORBA.portable.OutputStream 类它们的 flush() 方法均是从父类继承的 flush 方法

  • FilterOutputStream 类重写了 flush() 方法,但是实质还是调用父类的 flush() 方法

  • ObjectOutputStreamPipedOutputStream重写了 flush() 方法。因此这两个类写入后需要手动flush。

  • BufferedOutputStream是带有缓冲区的字节流,因此也需要手动flush。

2.1.2 字符流Writer

一般使用字符流之后需要调用一下flush()或者 close()方法。

2.1.3 总结

  • 字节流中只有ObjectOutputStreamPipedOutputStream写入后需要手动刷新flush才能即时写入。否则只能等到流关闭的时候一次性写入。
  • 字符流Writer都需要手动flush。
  • BufferedOutputStreamBufferedWriter需要手动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()以对象方式读取)。其他的基本类型也类似。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值