【Java TCP/IP Socket】TCP Socket通信中由read返回值造成的的死锁问题(含代码)(转)
书上示例
在第一章《基本套接字》中,作者给出了一个TCP Socket通信的例子——反馈服务器,即服务器端直接把从客户端接收到的数据原原本本地反馈回去。
书上客户端代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
import java.net.Socket; import java.net.SocketException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; public class TCPEchoClient { public static void main(String[] args) throws IOException { if ((args.length < 2 ) || (args.length > 3 )) // Test for correct # of args throw new IllegalArgumentException( "Parameter(s): <Server> <Word> [<Port>]" ); String server = args[ 0 ]; // Server name or IP address // Convert argument String to bytes using the default character encoding byte [] data = args[ 1 ].getBytes(); int servPort = (args.length == 3 ) ? Integer.parseInt(args[ 2 ]) : 7 ; // Create socket that is connected to server on specified port Socket socket = new Socket(server, servPort); System.out.println( "Connected to server...sending echo string" ); InputStream in = socket.getInputStream(); OutputStream out = socket.getOutputStream(); out.write(data); // Send the encoded string to the server // Receive the same string back from the server int totalBytesRcvd = 0 ; // Total bytes received so far int bytesRcvd; // Bytes received in last read while (totalBytesRcvd < data.length) { if ((bytesRcvd = in.read(data, totalBytesRcvd,data.length - totalBytesRcvd)) == - 1 ) throw new SocketException( "Connection closed prematurely" ); totalBytesRcvd += bytesRcvd; } // data array is full System.out.println( "Received: " + new String(data)); socket.close(); // Close the socket and its streams } } |
书上的服务器端代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
import java.net.*; // for Socket, ServerSocket, and InetAddress import java.io.*; // for IOException and Input/OutputStream public class TCPEchoServer { private static final int BUFSIZE = 32 ; // Size of receive buffer public static void main(String[] args) throws IOException { if (args.length != 1 ) // Test for correct # of args throw new IllegalArgumentException( "Parameter(s): <Port>" ); int servPort = Integer.parseInt(args[ 0 ]); // Create a server socket to accept client connection requests ServerSocket servSock = new ServerSocket(servPort); int recvMsgSize; // Size of received message byte [] receiveBuf = new byte [BUFSIZE]; // Receive buffer while ( true ) { // Run forever, accepting and servicing connections Socket clntSock = servSock.accept(); // Get client connection SocketAddress clientAddress = clntSock.getRemoteSocketAddress(); System.out.println( "Handling client at " + clientAddress); InputStream in = clntSock.getInputStream(); OutputStream out = clntSock.getOutputStream(); // Receive until client closes connection, indicated by -1 return while ((recvMsgSize = in.read(receiveBuf)) != - 1 ) { out.write(receiveBuf, 0 , recvMsgSize); } clntSock.close(); // Close the socket. We are done with this client! } /* NOT REACHED */ } } |
示例程序当然运行无误,运行结果如下:
问题的引出
首先明确几点:
1、客户端与服务器端在接收和发送数据时,read()和write()方法不一定要对应,比如,其中一方可以一次发送多个字节的数据,而另一方可以一个字节一个字节地接收,也可以一个字节一个字节地方送,而多个字节多个字节地接收。因为TCP协议会将数据分成多个块进行发送,而后在另一端会从多个块进行接收,再组合在一起,它并不仅能确定read()和write()方法中所发送信息的界限。
2、read()方法会在没有数据可读时发生阻塞,直到有新的数据可读。
注意客户端中下面部分代码
1
2
3
4
5
|
while (totalBytesRcvd < data.length) { if ((bytesRcvd = in.read(data, totalBytesRcvd,data.length - totalBytesRcvd)) == - 1 ) throw new SocketException( "Connection closed prematurely" ); totalBytesRcvd += bytesRcvd; } // data array is full |
客户端从Socket套接字中读取数据,直到收到的数据的字节长度和原来发送的数据的字节长度相同为止,这里的前提是已经知道了要从服务器端接收的数据的大小,如果现在我们不知道要反馈回来的数据的大小,那么我们只能用read方法不断读取,直到read()返回-1,说明接收到了所有的数据。我这里采用一个字节一个字节读取的方式,代码改为如下:
1
2
3
4
|
while ((bytesRcvd = in.read())!= - 1 ){ data[totalBytesRcvd] = ( byte )bytesRcvd; totalBytesRcvd++; } |
这时问题就来了,输出结果如下:
问题的分析
客户端没有数据打印出来,初步推断应该是read()方法始终没有返回-1,导致程序一直无法往下运行,我在客客户端执行窗口中按下CTRL+C,强制结束运行,在服务器端抛出如下异常:
1
2
3
4
5
|
while ((bytesRcvd = in.read())!= - 1 ){ data[totalBytesRcvd] = ( byte )bytesRcvd; System.out.println(( char )data[totalBytesRcvd]); totalBytesRcvd++; } |
此时运行结果如下:
问题的解决
查阅相关资料,仔细阅读了书上的每个细节,在通过对比书上的代码和自己的代码,发现了问题所在。问题就出现在read()方法上,这里的重点是read()方法何时返回-1,在一般的文件读取中,这代表流的结束,亦即读取到了文件的末尾,但是在Socket套接字中,这样的概念很模糊,因为套接字中数据的末尾并没有所谓的结束标记,无法通过其自身表示传输的数据已经结束,那么究竟什么时候read()会返回-1呢?答案是:当TCP通信连接的一方关闭了套接字时。
1
2
|
out.write(data); // Send the encoded string to the server socket.shutdownOutput(); |
总结
由于read()方法只有在另一端关闭套接字的输出流时,才会返回-1,而有时候由于我们不知道所要接收数据的大小,因此不得不用read()方法返回-1这一判断条件,那么此时,合理的程序设计应该是先关闭网络输出流(亦即套接字的输出流),再关闭套接字。
http://www.importnew.com/20151.html
【Java TCP/IP Socket】TCP Socket通信中由read返回值造成的的死锁问题(含代码)(转)的更多相关文章
- JAVA基础知识之网络编程——-TCP/IP协议,socket通信,服务器客户端通信demo
OSI模型分层 OSI模型是指国际标准化组织(ISO)提出的开放系统互连参考模型(Open System Interconnection Reference Model,OSI/RM),它将网络分为七 ...
- c# TCP/IP协议利用Socket Client通信(只含客户端Demo)
完全是基础,新手可以随意看看,大牛可以关闭浏览页了,哈哈. TCP/IP协议 TCP/IP是一系列网络通信协议的统称,其中最核心的两个协议是TCP和IP.TCP称为传输控制协议,IP称为互联网络协议. ...
- http、TCP/IP协议与socket之间的区别
http.TCP/IP协议与socket之间的区别 网络由下往上分为: www.2cto.com 物理层-- 数据链路层-- 网络层-- ...
- Linux TCP/IP 协议栈之 Socket 的实现分析(一)
内核版本:2.6.37参考[作者:kendo的文章(基于内涵版本2.6.12)] 第一部份 Socket套接字的创建 socket 并不是 TCP/IP协议的一部份. 从广义上来讲,socket 是U ...
- http、TCP/IP协议与socket之间的区别(转载)
http.TCP/IP协议与socket之间的区别 https://www.cnblogs.com/iOS-mt/p/4264675.html http.TCP/IP协议与socket之间的区别 ...
- Http TCP/IP协议和socket之间的区别和联系
总结,TCP/IP是传输层协议,主要解决数据如何在网路中传输,socket是TCP/IP协议的具体实现,是对TCP/IP协议的封装和应用,属于程序员层面,HTTP是应用层协议,应用层协议很多,类似的像 ...
- TCP/IP、TCP、UDP、Socket知识汇总
带你了解TCP/IP,UDP,Socket之间关系 https://blog.csdn.net/chaoshenzhaoxichao/article/details/79785318 主要知识点: T ...
- TCP/IP协议栈在Linux内核中的运行时序分析
网络程序设计调研报告 TCP/IP协议栈在Linux内核中的运行时序分析 姓名:柴浩宇 学号:SA20225105 班级:软设1班 2021年1月 调研要求 在深入理解Linux内核任务调度(中断处理 ...
- TCP/IP基础概念及通信过程举例
TCP/IP基础概念及通信过程举例 出现 上个世纪60年代,由于中央集中式网络的容灾性较弱,以美国国防部为中心的一家组织研究出分组交换网络.后来为了验证分组交换技术的实用性,ARPANET出现了,并且 ...
随机推荐
- float与position
使用float会使块级元素的宽高表现为包裹内容(在不设定宽高的情况下) 这是当然的 我们使用float就是使几个div排在一行 当然不可能在宽度上撑满父元素啦 至于高度 不论有没有float 高 ...
- Week13(12月5日):不怕错误,慢慢来 :)
Part I:提问 =========================== 1.ASP.NET MVC的最新版本是( ). A.2 B.3 C.4 D.5 2.本学期授课中使 ...
- 条码知识之十:EAN-128条码(下)
国际物品编码协会(EAN)和美国统一代码委员会(UCC)将CODE-128码引入EAN/UCC系统,并作如下规定:起始符由一个START A/B/C 加一个辅助字符FNC1构成,以区别普通的CODE- ...
- python yaml使用
YAML Ain't Markup Language 和GNU一样,YAML是一个递归着说“不”的名字.不同的是,GNU对UNIX说不,YAML说不的对象是XML. YAML不是XML. 为什么不是X ...
- html里面自定义弹出窗口
网页上默认的提示框或对话框一般比较丑,可以利用div遮盖层来自定义对话框 1.定义一个按钮或者链接(项目里面是通过点击一个图片) <img src="images/zz.gif&quo ...
- ASP.NET MVC 以Stream 下载文件
1.0以Stream 下载文件 nl.fileid = Int32.Parse(id); //服务器上对应的id Stream stream = Lawsuit.DownLoad(nl);//服务器 ...
- Spring IOC三种注入方式(接口注入、setter注入、构造器注入)(摘抄)
IOC ,全称 (Inverse Of Control) ,中文意思为:控制反转, Spring 框架的核心基于控制反转原理. 什么是控制反转?控制反转是一种将组件依赖关系的创建和管理置于程序外部的技 ...
- linux c 得到文件大小
#include <sys/stat.h> unsigned long get_file_size(const char *path) { unsigned long filesize = ...
- 基于visual Studio2013解决算法导论之030二叉查找树
题目 二叉查找树 解决代码及点评 // 12二叉查找树.cpp : 定义控制台应用程序的入口点. // // 3 - 10二叉搜索树查找.cpp : 定义控制台应用程序的入口点. // #in ...
- 莱特币ltc在linux下的多种挖矿方案详解
莱特币ltc在linux下的多种挖矿方案详解 4.0.1 Nvidia显卡Linux驱动Nvidia全部驱动:http://www.nvidia.cn/Download/index.aspx?lang ...