【Java TCP/IP Socket】TCP Socket通信中由read返回值造成的的死锁问题(含代码)

来源:互联网 发布:otc机器人编程教材 编辑:程序博客网 时间:2024/04/19 06:09

转载请注明出处:http://blog.csdn.net/ns_code/article/details/14642873

  书上示例

    在第一章《基本套接字》中,作者给出了一个TCP Socket通信的例子——反馈服务器,即服务器端直接把从客户端接收到的数据原原本本地反馈回去。

     书上客户端代码如下:

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 argsthrow 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 encodingbyte[] data = args[1].getBytes();int servPort = (args.length == 3) ? Integer.parseInt(args[2]) : 7;// Create socket that is connected to server on specified portSocket 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 serverint totalBytesRcvd = 0;  // Total bytes received so farint bytesRcvd;           // Bytes received in last readwhile (totalBytesRcvd < data.length) {if ((bytesRcvd = in.read(data, totalBytesRcvd,data.length - totalBytesRcvd)) == -1)throw new SocketException("Connection closed prematurely");totalBytesRcvd += bytesRcvd;}  // data array is fullSystem.out.println("Received: " + new String(data));socket.close();  // Close the socket and its streams}}

     书上的服务器端代码如下:

import java.net.*;  // for Socket, ServerSocket, and InetAddressimport java.io.*;   // for IOException and Input/OutputStreampublic class TCPEchoServer {private static final int BUFSIZE = 32;   // Size of receive bufferpublic static void main(String[] args) throws IOException {    if (args.length != 1)  // Test for correct # of argsthrow 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 connectionsSocket clntSock = servSock.accept();     // Get client connectionSocketAddress 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 returnwhile ((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()方法会在没有数据可读时发生阻塞,直到有新的数据可读。

     注意客户端中下面部分代码

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,说明接收到了所有的数据。我这里采用一个字节一个字节读取的方式,代码改为如下:

while((bytesRcvd = in.read())!= -1){data[totalBytesRcvd] = (byte)bytesRcvd;totalBytesRcvd++;} 

    这时问题就来了,输出结果如下:


     

    问题的分析

      客户端没有数据打印出来,初步推断应该是read()方法始终没有返回-1,导致程序一直无法往下运行,我在客客户端执行窗口中按下CTRL+C,强制结束运行,在服务器端抛出如下异常:

Exception in thread "main" java.net.SocketException: Connection reset
        at java.net.SocketInputStream.read(Unknown Source)
        at java.net.SocketInputStream.read(Unknown Source)
        at TCPEchoServer.main(TCPEchoServer.java:32)

     异常显示,问题出现在服务端的32行,没有资源可读,现在很有可能便是由于read()方法始终没有返回-1所致,为了验证,我在客户端读取字节的代码中加入了一行打印读取的单个 字符的代码,如下:
while((bytesRcvd = in.read())!= -1){data[totalBytesRcvd] = (byte)bytesRcvd;System.out.println((char)data[totalBytesRcvd]);totalBytesRcvd++;}
     此时运行结果如下:
     很明显,客户端程序在打印出最有一个字节后不再往下执行,没有执行其后面的System.out.println("Received: " + new String(data));这行代码,这是因为read()方法已经将数据读完,没有数据可读,但又没有返回-1,因此在此处产生了阻塞。这便造成了TCP Socket 通信的死锁问题。

    问题的解决

     查阅相关资料,仔细阅读了书上的每个细节,在通过对比书上的代码和自己的代码,发现了问题所在。问题就出现在read()方法上,这里的重点是read()方法何时返回-1,在一般的文件读取中,这代表流的结束,亦即读取到了文件的末尾,但是在Socket套接字中,这样的概念很模糊,因为套接字中数据的末尾并没有所谓的结束标记,无法通过其自身表示传输的数据已经结束,那么究竟什么时候read()会返回-1呢?答案是:当TCP通信连接的一方关闭了套接字时。
     再次分析改过后的代码,客户端用到了read()返回-1这个条件,而服务端也用到了,只有二者有一方关闭了Socket,另一方的read()方法才会返回-1,而在客户端打印输出前,二者都没有关闭Socket,因此,二者的read()方法都不会返回-1,程序便阻塞在此处,都不往下执行,这便造成了死锁。
     反过来,再看书上的给出的代码,在客户端代码的while循环中,我们的条件是totalBytesRcvd < data.length,而不是(bytesRcvd = in.read())!= -1,这样,客户端在收到与其发送相同的字节数之后便会退出while循环,再往下执行,便是关闭套接字,此时服务端的read()方法检测到客户端的关闭,便会返回-1,从而继续往下执行,也将套接字关闭。因此,不会产生死锁。
     那么,如果在客户端不知道反馈回来的数据的情况下,该如何避免死锁呢?Java的Socket类提供了shutdownOutput()和shutdownInput()另个方法,用来分别只关闭Socket的输出流和输入流,而不影响其对应的输入流和输出流,那么我们便可以在客户端发送完数据后,调用shutdownOutput()方法将套接字的输出流关闭,这样,服务端的read()方法便会返回-1,继续往下执行,最后关闭服务端的套接字,而后客户端的read()方法也会返回-1,继续往下执行,直到关闭套接字。
    客户端改变后的代码部分如下:
out.write(data);  // Send the encoded string to the serversocket.shutdownOutput();
    这样,便得到了预期的运行结果,如下:


    总结

      由于read()方法只有在另一端关闭套接字的输出流时,才会返回-1,而有时候由于我们不知道所要接收数据的大小,因此不得不用read()方法返回-1这一判断条件,那么此时,合理的程序设计应该是先关闭网络输出流(亦即套接字的输出流),再关闭套接字。


原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 借呗学历填错了怎么办 上海落户应届生分不够怎么办 应届生落户分数不够72分怎么办? 上海应届生落户时间延误怎么办 南京市区户口签江宁怎么办 深圳公司集体户口离职后怎么办 济南本地户口不符合入学条件怎么办 上海住亲戚家怎么办居住证 政府卖非农户口怎么办 90年代买了户口怎么办 上海应届大学生积分不够怎么办 广州居住证回执单丢了怎么办 惠阳居住证回执单丢了怎么办 南京居住证换地方了怎么办 买家退回的商品有问题怎么办 农转农户口手续怎么办 原房东不迁户口我怎么办 户主信息页掉了怎么办 户主变了户口本首页怎么办 大人户口迁走小孩户口怎么办 网银转账处理中怎么办 教育部学籍在线验证报告有错怎么办 验证码连续输入三次错误怎么办 交通运输监察大队截车了怎么办 平安安康续保没成功怎么办 危险品经营许可证到期了怎么办 郑万350渝万怎么办 厂里饭堂的饭好难吃怎么办 学校的食堂饭菜不好不卫生怎么办 亲戚借钱我真没有怎么办 榴莲肉酸了吃了怎么办 亲戚赖在家里住怎么办 食堂饭菜味道差该怎么办 被监视居住公安打电话睡着了怎么办 鱼缺氧浮上水面怎么办 车载低音炮有电流声怎么办 925纯银变黑了怎么办 银子放久了变黑怎么办 高铁票网上售空怎么办 高铁票出票失败怎么办 高铁票名字打错怎么办