Navigation

    蓝鲸ROS机器人论坛

    • Register
    • Login
    • Search
    • Categories
    • Tags
    • Popular
    ROS交流群
    ROS Group
    产品服务
    Product Service
    开源代码库
    Github
    官网
    Official website
    技术交流
    Technological exchanges
    激光雷达
    LIDAR
    ROS教程
    ROS Tourials
    深度学习
    Deep Learning
    机器视觉
    Computer Vision

    如何正确的处理TCP连接

    技术交流
    close socket tcp socket tcp client serv c socket
    1
    1
    3063
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • weijiz
      weijiz last edited by weijiz

      TCP是一种非常常用的socket,在做各种网络通信的时候,它也是必不可少的。但是很多情况下我们并没有正确的处理TCP连接。最终可能导致大量的僵尸连接耗尽了服务器的资源。这里就稍微详细的说明正确的TCP连接创建和关闭的方式,以及僵尸连接出现的原因。因为作为开发软件的人更关心的是如何正确的使用API,所以底层的和如何使用关系不大的东西,比如TCP三次握手,在这篇文章会尽量避免。

      以下以C#作为示例

      建立连接

      服务器开始监听

      IPAddress ip = IPAddress.Parse ("0.0.0.0");
      var serverSocket = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
      serverSocket.Bind (new IPEndPoint (ip, Convert.ToInt32 (9999)));
      serverSocket.Listen (1000);
      Socket clientSocket = serverSocket.Accept ();
      

      一般设置好服务器监听地址和端口,服务器就可以开始监听了。这里采用的是阻塞式的socket。程序最后会阻塞在最后一句直到有客户端连接到服务器。

      客户端建立连接

      IPAddress targetIp = IPAddress.Parse (ipstr);
      Socket mClientSocket = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
      mClientSocket.Connect (new IPEndPoint (targetIp, Convert.ToInt32 (9999)));
      

      客户端程序也是比较简单的,设置好服务器的地址和端口就可以连接了。这里socket的读取和写入都是阻塞的。所以一般都是要单独给每个socket连接开一个线程。

      创建连接的过程是很直观和简单的,一般也很少会犯错。但是socket的断开过程就是一个比较大的坑。

      不正确的断开连接

      如果服务器端或者客户端想要关闭连接,直接调用了Close方法。那么就会出问题。如服务器端程序执行

      mClientSocket.Close ();
      

      此时如果客户端正在调用 Receive 函数,客户端就会报出Socket被异常关闭的错误。但是客户端的这个socket实际上处在close_wait状态并没有关闭,socket也并不会被释放。如果程序这样一直执行下去最终就会耗尽系统的资源

      下面以实际的一个例子程序来演示一下

      服务器程序

      using System;
      using System.Net;
      using System.Net.Sockets;
      
      namespace testSocket
      {
      	class MainClass
      	{
      		public static void Main (string[] args)
      		{
      			
      			IPAddress ip = IPAddress.Parse ("0.0.0.0");
      			var serverSocket = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
      			serverSocket.Bind (new IPEndPoint (ip, Convert.ToInt32 (9999)));
      			serverSocket.Listen (1000);
      			Socket clientSocket = serverSocket.Accept ();
      			clientSocket.Close ();
      			Console.WriteLine ("Socket connected");
      			Console.ReadKey ();
      		}
      	}
      }
      
      

      客户端程序

      using System;
      using System.Net;
      using System.Net.Sockets;
      
      namespace socketClient
      {
      	class MainClass
      	{
      		public static void Main (string[] args)
      		{
      			IPAddress targetIp = IPAddress.Parse ("127.0.0.1");
      			Socket mClientSocket = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
      			mClientSocket.Connect (new IPEndPoint (targetIp, Convert.ToInt32 (9999)));
      			Console.WriteLine ("Socket connected");
      			Console.WriteLine ("Local Port: " + mClientSocket.LocalEndPoint);
      			Console.ReadKey ();
      		}
      	}
      }
      
      

      服务器程序在连接建立之后直接调用了Close函数。
      这时通过netstat可以看到两个连接的状态。
      在命令行执行

      netstat -nap |grep 46755
      

      其中46755是客户端显示的端口号
      显示如下图
      0_1466499938307_Screenshot from 2016-06-21 17:05:11.png
      可以看到服务器也有一个socket处在FIN_WAIT2的状态。但是这个并没有什么影响,过一段时间后系统会自动释放这个socket。
      0_1466500313131_Screenshot from 2016-06-21 17:11:38.png
      同样如果客户端直接关闭连接就会导致服务器的socket处在close_wait的状态。

      正确的断开连接

      关闭之前一定要调用Shutdown
      关闭之前一定要调用Shutdown
      关闭之前一定要调用Shutdown
      重要的事情说三遍。Shutdown的作用就是告诉socket不要在继续读写了,socket要准备关闭了。Shutdown的方式有几种,可以只关闭本地的读写,也可以只关闭远方的读写,也可以同时关闭。

      正确的服务器关闭例子

      using System;
      using System.Net;
      using System.Net.Sockets;
      
      namespace testSocket
      {
      	class MainClass
      	{
      		public static void Main (string[] args)
      		{
      			bool closeFlag = false;
      			IPAddress ip = IPAddress.Parse ("0.0.0.0");
      			var serverSocket = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
      			serverSocket.Bind (new IPEndPoint (ip, Convert.ToInt32 (9999)));
      			serverSocket.Listen (1000);
                              byte[] buf = new byte[1024 * 512];
      			Socket clientSocket = serverSocket.Accept ();
                              try{
                                  int size = clientSocket.Receive(buf);
                                  if(size == 0 && !closeFlag){
                                      closeFlag = true;
                                      clientSocket.Shutdown();
                                      clientSocket.Close();
                                  }
                              }catch(SocketException e){
                                  closeFlag = true;
                                  clientSocket.Shutdown();
                                  clientSocket.Close();
                              }
                              
                              if(!closeFlag){
                                  clientSocket.Shutdown (SocketShutdown.Both);
          			    clientSocket.Close ();
                              }
      			Console.WriteLine ("Socket connected");
      			Console.ReadKey ();
      		}
      	}
      }
      

      不仅在本地主动关闭的时候要调用Shutdown,对方不正常关闭的时候也要调用Shutdown。如果对方不正常的关闭连接那么就会本地的socket就会抛出异常。如果对方是正常的关闭socket那么Receive的返回值就是0,然后根据这个去关闭本地的socket。

      比较完整的C#socket客户端和服务器的例子程序可以看这里。
      服务器程序
      客户端程序

      1 Reply Last reply Reply Quote 0
      • First post
        Last post
      Copyright © 2015-2021 BlueWhale community