现在的位置: 首页 > 综合 > 正文

socket网络通信

2018年12月11日 ⁄ 综合 ⁄ 共 11585字 ⁄ 字号 评论关闭

http://blog.csdn.net/dabing69221/article/details/17351881

前言:

  在Java Socket中提供了2中关闭方法,其中包括:close() 、shutdownOutput()/shutdownInput() ,前一段时间做项目中用到了Socket,但是没有好好总结。

  现在,我把我遇到的问题分享一下:

  

  在网络协议中,通常明确指定了由谁来发起 “关闭”连接。

  例如:在HTTP协议中,客户端先向服务端发送一个请求,然后服务器响应请求的信息。由于客户端不知道服务端响应信息的大小,因此服务器必须通过关闭套

  接字来指示响应信息结束 —— 这个就是由服务器端发起的关闭连接!

一. Socket.close()方法

   这个方法我们估计都不会陌生,一般使用完Socket后,都会调用Socket.close()  来释放相关的资源。当我们调用Socket.close()方法将同时终止两个方向(输入

   和输出)的数据流。一旦一个终端(客户端或服务端)关闭了套接字,它将无法再发送或接收数据。这就意味着close( )方法只能在调用者完成了通信之后用来

   给另一端发送信号。只要服务端收到了客户端的关闭信号,就立即关闭连接。

  

   1. 考虑一个场景:

   假设你有一个服务器,接收到消息后,在发回给客户端,这种情况下应该是有哪一端来关闭连接呢?

   由于从客户端发送的消息长度是任意的,客户端需要关闭连接以通知服务器消息发送完毕,那么客户端应该什么时候调用close( )方法呢,如果客户端在发送完最后

   一个字节后立即调用close( )方法,它将无法接收服务器发送的消息 (因为: 关闭此套接字也将会关闭该套接字的 InputStream 和 OutputStream);如果客户端在接收

   完所有的数据后在调用close ( )方法,那么服务端 将不知道到底有多少数据要接收(因为:客户端的输出流一直没有关闭,所以,服务端的输入流也就无法到达流末尾返回-1),

   所以,服务器在接收数据时就一直阻塞,而这个时候客户端又要等待接收服务端发送来的消息(即使服务端发送了消息,但因为服务端的输出流一直没有关闭,所以,

   服务端的输入流也就无法到达流末尾返回-1,所以客户端在接收数据时就一直阻塞) —— 以上问题就造成:”服务端和客户端都在接收数据时阻塞"

   

   情况一 :客户端在发送完最后一个字节后立即调用close( )方法

  

  1. //服务端  
  2. public class Server  
  3. {  
  4.     public static void main(String[] args) throws IOException  
  5.     {  
  6.         System.out.println("------------------------------------");  
  7.         System.out.println("Server start......");  
  8.         System.out.println("------------------------------------");  
  9.   
  10.         ServerSocket server = new ServerSocket(8888);  
  11.   
  12.         while (true)  
  13.         {  
  14.             Socket client = server.accept();  
  15.             OutputStream out = client.getOutputStream();  
  16.             InputStream in = client.getInputStream();  
  17.   
  18.             ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();  
  19.             byte[] temp = new byte[1024];  
  20.             int realLen = 0;  
  21.             while ((realLen = in.read(temp)) != -1)  
  22.             {  
  23.                 byteArrayOut.write(temp, 0, realLen);  
  24.             }  
  25.   
  26.             byte[] recv = byteArrayOut.toByteArray();  
  27.   
  28.             // 将接收的消息,发回给客户端  
  29.             out.write(recv);  
  30.             out.flush();  
  31.         }  
  32.     }  
  33. }  
  34.   
  35.   
  36. //客户端  
  37. public class Client  
  38. {  
  39.   
  40.     public static void main(String[] args) throws IOException  
  41.     {  
  42.         System.out.println("------------------------------------");  
  43.         System.out.println("Client start......");  
  44.         System.out.println("------------------------------------");  
  45.         byte[] msg = new String("connect successfully!!!").getBytes();  
  46.   
  47.         InetAddress inetAddr = InetAddress.getLocalHost();  
  48.         Socket client = new Socket(inetAddr, 8888);  
  49.         OutputStream out = client.getOutputStream();  
  50.         InputStream in = client.getInputStream();  
  51.   
  52.         out.write(msg);  
  53.         out.flush();  
  54.         // 情况一 :客户端在发送完最后一个字节后立即调用close( )方法  
  55.         client.close();  
  56.   
  57.         ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();  
  58.         byte[] temp = new byte[1024];  
  59.         int realLen = 0;  
  60.         while ((realLen = in.read(temp)) != -1)  
  61.         {  
  62.             byteArrayOut.write(temp, 0, realLen);  
  63.         }  
  64.   
  65.         byte[] recv = byteArrayOut.toByteArray();  
  66.   
  67.         System.out.println("Client receive msg:" + new String(recv));  
  68.   
  69.         /* 
  70.          * 切记:在这里关闭输入流,并不会使服务端的输入流到达流末尾返回-1,仅仅是释放资源而已 
  71.          */  
  72.         in.close();  
  73.         out.close();  
  74.   
  75.     }  
  76. }  

 运行结果:服务端与客户端都接收不到数据

  

   情况二:如果客户端在接收完所有的数据后在调用close ( )方法

   服务端代码没变*(请参看情况一,所以这里只贴出客户端代码:

  

  1. //客户端  
  2. public class Client  
  3. {  
  4.   
  5.     public static void main(String[] args) throws IOException  
  6.     {  
  7.         System.out.println("------------------------------------");  
  8.         System.out.println("Client start......");  
  9.         System.out.println("------------------------------------");  
  10.         byte[] msg = new String("connect successfully!!!").getBytes();  
  11.   
  12.         InetAddress inetAddr = InetAddress.getLocalHost();  
  13.         Socket client = new Socket(inetAddr, 8888);  
  14.         OutputStream out = client.getOutputStream();  
  15.         InputStream in = client.getInputStream();  
  16.   
  17.         out.write(msg);  
  18.         out.flush();  
  19.   
  20.         ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();  
  21.         byte[] temp = new byte[1024];  
  22.         int realLen = 0;  
  23.         while ((realLen = in.read(temp)) != -1)  
  24.         {  
  25.             byteArrayOut.write(temp, 0, realLen);  
  26.         }  
  27.         // 情况二 :如果客户端在接收完所有的数据后在调用close()方法  
  28.         client.close();  
  29.   
  30.         byte[] recv = byteArrayOut.toByteArray();  
  31.   
  32.         System.out.println("Client receive msg:" + new String(recv));  
  33.   
  34.         /* 
  35.          * 切记:在这里关闭输入流,并不会使服务端的输入流到达流末尾返回-1,仅仅是释放资源而已 
  36.          */  
  37.         in.close();  
  38.         out.close();  
  39.   
  40.     }  
  41. }  

(如果将客户端代码的23-26行代码注释掉,双方通信就会完成。这里的原因并不是代码注释中所写的,而是因为死锁了。关闭socket肯定会在通信流中有结束信息。客户端发送过消息之后,就开始堵塞接收服务器返回的消息,而服务器在21行,第一次执行read(temp)获得了字节数组,循环第二次时又开始堵塞read(temp),也就是在等待客户端发送的结束消息,于是就死锁了)   

运行结果:服务端与客户端都接收不到数据

     


二. Sockt.shutdownInput( )与Socket.shutdownOutput( )

   在上边的例子中,我们考虑了一个场景,但是分析之后——发现会造成 “服务端和客户端都在接收数据时阻塞 ”,我们现在需要一种方法来告诉连接的另一端 

   “我已经发送完所有数据” ,同时还要保持接收数据的能力。

   这个问题Java中已经帮我们解决了,TCP套接字中提供了一种实现这个功能的方法,Socket.shutdownOutput( )和Socket.shutdownInput( )方法能够将输入输出流相互独立的关闭。

   调用Socket.shutdownInput( )后, 禁用此套接字的输入流,发送到套接字的输入流端的任何数据都将被确认然后被静默丢弃。任何想从该套接字的输入流读取数据的操作都将返回-1;

   调用Socket.shutdownOutput()后,禁用此套接字的输出流,对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列(即-1),之后,从另一端TCP

   套接字的输入流中读取数据时,如果到达输入流末尾而不再有数据可用,则返回 -1

    

   注意:

   当调用Socket.shutdownInput( )后,还能够往该套接字中写数据(执行OutputStream.write( ));

   当调用Socket.shutdownOutput( )后,还能够往该套接字中读数据(执行InputStream.read( ));

   

  使用Socket.shutdownInput( )和Socket.shutdownOutput( ),代码如下:

 

  1. //服务端  
  2. public class Server  
  3. {  
  4.     public static void main(String[] args) throws IOException  
  5.     {  
  6.         System.out.println("------------------------------------");  
  7.         System.out.println("Server start......");  
  8.         System.out.println("------------------------------------");  
  9.   
  10.         ServerSocket server = new ServerSocket(8888);  
  11.   
  12.         while (true)  
  13.         {  
  14.             Socket client = server.accept();  
  15.             OutputStream out = client.getOutputStream();  
  16.             InputStream in = client.getInputStream();  
  17.   
  18.             ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();  
  19.             byte[] temp = new byte[1024];  
  20.             int realLen = 0;  
  21.             while ((realLen = in.read(temp)) != -1)  
  22.             {  
  23.                 byteArrayOut.write(temp, 0, realLen);  
  24.             }  
  25.   
  26.             byte[] recv = byteArrayOut.toByteArray();  
  27.   
  28.             // 将接收的消息,发回给客户端  
  29.             out.write(recv);  
  30.             out.flush();  
  31.             // 关闭该套接字的输入流  
  32.             client.shutdownOutput();  
  33.         }  
  34.     }  
  35. }  
  36.   
  37.   
  38. //客户端  
  39. public class Client  
  40. {  
  41.   
  42.     public static void main(String[] args) throws IOException  
  43.     {  
  44.         System.out.println("------------------------------------");  
  45.         System.out.println("Client start......");  
  46.         System.out.println("------------------------------------");  
  47.         byte[] msg = new String("connect successfully!!!").getBytes();  
  48.   
  49.         InetAddress inetAddr = InetAddress.getLocalHost();  
  50.         Socket client = new Socket(inetAddr, 8888);  
  51.         OutputStream out = client.getOutputStream();  
  52.         InputStream in = client.getInputStream();  
  53.   
  54.         out.write(msg);  
  55.         out.flush();  
  56.         // 关闭该套接字的输出流  
  57.         client.shutdownOutput();  
  58.   
  59.         ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();  
  60.         byte[] temp = new byte[1024];  
  61.         int realLen = 0;  
  62.         while ((realLen = in.read(temp)) != -1)  
  63.         {  
  64.             byteArrayOut.write(temp, 0, realLen);  
  65.         }  
  66.   
  67.         byte[] recv = byteArrayOut.toByteArray();  
  68.   
  69.         System.out.println("Client receive msg:" + new String(recv));  
  70.   
  71.         /* 
  72.          * 切记:在这里关闭输入流,并不会使服务端的输入流到达流末尾返回-1,仅仅是释放资源而已 
  73.          */  
  74.         in.close();  
  75.         out.close();  
  76.   
  77.     }  
  78. }  


   运行看看,完全没有问题,网上还有人说 “Socket.shutdownInput( )与Socket.shutdownOutput( )” 是 “半关闭方法” — 其实这么说也对。

三. 深入剖析TCP关闭连接机制

   TCP套接字中有一个优雅的关闭机制,以保证应用程序在关闭连接时不必担心正在传输的数据会丢失。这个机制还允许两个方向的数据传输相互独立的终止。

   关闭机制的工作流程:应用程序通过调用连接套接字的close( )方法或shutdownOutput( )方法表明数据已经发送完毕。此刻,底层的TCP实现首先将留存在

   SendQ队列中的数据传输出去(还要依赖另一端RecvQ队列的剩余空间),然后向另一端发送一个关闭TCP连接的握手消息,该关闭握手消息可以看作时流

   终止标志:它告诉接收端TCP不会再有新的数据传入RecvQ队列了。(关闭握手消息本身并没有传递给接收端应用程序,而是通过read( ) 方法返回-1来指示

   其在字节流中的位置)正在关闭的TCP将等待其关闭握手消息的确认信息,该确认信息表明在连接上传输的所有数据已经安全的传输到了RecvQ中。只要接收

   到了确认信息,该连接就变成 “半关闭 ”状态。直到连接的另一个方向上收到了对称的握手消息后,连接才完全关闭——也就是说,连接的两端都表明它们再也

  没有数据要发送了。

  

不管是客户端还是服务端在接收另一方发来的消息时,肯定有一种机制来保证接收到了消息的末尾

上例中,如果不用shutdown,要自定义一些协议保证接收,比如刚开始的第一个字节表示接下来传输的长度,接收方只需接收到这个长度大小的消息就不用再接收了

上例中直接用的InputStream,在java中已经有封装好的类,就是DataInputStream, 在接收的时候就是先读取长度,再按照长度读取流,需要主要的发送方要用DataOutputStream发送

如果用BufferedReader和PrintWriter来接收和发送数据时,一般接收用readline,那么发送一定是println,而不是print,应该readline一定要读到一个行换行才返回,否则就是堵塞了

server

package communication;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

//服务端
public class Server
{
  public static void main(String[] args) throws IOException
  {
      System.out.println("------------------------------------");
      System.out.println("Server start......");
      System.out.println("------------------------------------");

      ServerSocket server = new ServerSocket(8889);

      while (true)
      {
//          Socket client = server.accept();
//          OutputStream out = client.getOutputStream();
//          InputStream in = client.getInputStream();
//
//          ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();
//          byte[] temp = new byte[1024];
//          int realLen = 0;
//          while ((realLen = in.read(temp)) != -1)
//          {
//        	  System.out.println("read");
//              byteArrayOut.write(temp, 0, realLen);
//          }
//
//          byte[] recv = byteArrayOut.toByteArray();
//
//          // 将接收的消息,发回给客户端
//          out.write(recv);
//          out.flush();
    	  Socket client = server.accept();
    	  System.out.println("accept");
    	  OutputStream out = client.getOutputStream();
    	  InputStream in = client.getInputStream();
    	  
    	  BufferedReader br = new BufferedReader(new InputStreamReader(in));
    	  String line = br.readLine();
    	  System.out.println(line);//不能是print

    	  
    	  PrintWriter pw = new PrintWriter(out);
    	  pw.println("server " + line);
    	  pw.flush();
    	  
    	  br.close();
    	  pw.close();
    	  
      }
  }
}

client

package communication;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;

//客户端
public class ToyClient
{

    public static void main(String[] args) throws IOException
    {
        System.out.println("------------------------------------");
        System.out.println("Client start......");
        System.out.println("------------------------------------");
//        byte[] msg = new String("connect successfully!!!").getBytes();
//
//        InetAddress inetAddr = InetAddress.getLocalHost();
//        Socket client = new Socket(inetAddr, 8888);
//        OutputStream out = client.getOutputStream();
//        InputStream in = client.getInputStream();
//
//        out.write(msg);
//        out.flush();
//
//        ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();
//        byte[] temp = new byte[1024];
//        int realLen = 0;
//        while ((realLen = in.read(temp)) != -1)
//        {
//            byteArrayOut.write(temp, 0, realLen);
//        }
//        // 情况二 :如果客户端在接收完所有的数据后在调用close()方法
//        
//        in.close();
//        out.close();
//        
//        client.close();
//        
//        byte[] recv = byteArrayOut.toByteArray();
//
//        System.out.println("Client receive msg:" + new String(recv));

        /*
         * 切记:在这里关闭输入流,并不会使服务端的输入流到达流末尾返回-1,仅仅是释放资源而已
         */
        
        InetAddress inetAddr = InetAddress.getLocalHost();
      Socket client = new Socket(inetAddr, 8889);
      OutputStream out = client.getOutputStream();
      InputStream in = client.getInputStream();
      
      BufferedReader br = new BufferedReader(new InputStreamReader(in));
      PrintWriter pw = new PrintWriter(out);
      
      pw.println("connect successfully!!!");//bu
      pw.flush();
      
      //String line = br.readLine();
      //System.out.println(line);
      
      pw.close();
      br.close();
      
      client.close();
        

    }
}

抱歉!评论已关闭.