因为 TCP 本身是无边界的协议,因此它并没有结束标志,也无法分包。「包」的界定,是更上层的协议的事了(比如 HTTP)。
socket和文件不一样,从文件中读,读到末尾就到达流的结尾了,所以会返回-1或null,循环结束,但是socket是连接两个主机的桥梁,一端无法知道另一端到底还有没有数据要传输。
socket如果不关闭的话,read之类的阻塞函数会一直等待它发送数据,就是所谓的阻塞。
如果发送的东西非常多必须要用循环读,可以有以下解决方案:
- 调用socket的 shutdownOutput 方法(Java)关闭输出流,该方法的文档说明为,将此套接字的输出流置于“流的末尾”,这样另一端的输入流上的read操作就会返回-1。
- 约定结束标志,当读到该结束标志时退出不再read。 (Http 的 Transfer-Encoding: Chunked 首部,表示将以一个 length 为 0 的 chunk 做结束标志)
- 设置超时(timeout),会在设置的超时时间到达后抛出SocketTimeoutException异常而不再阻塞。
- 双方定义好通信协议,在协议头部约定好数据的长度。当读取到的长度等于这个长度时就不再继续调用read方法。(Http 的 content-length 首部,会给出主体的长度)
而如果需要发送多个相互独立的内容,内容之间就需要有明确的分界,方法有:
- 像 multipart-form 一样,使用 boundary 字串做分割,使用 Content-Type 等首部做内容标识。
- 用二进制帧做分割,在帧首部定义好帧的长度和其他信息。
参考
关于java网络编程中获取输入流中数据的问题
Linux编程之socket:tcp流协议产生的粘包问题及解决方法
终端如何与服务器通信——玩转通信协议(源码下载)