导航

    蓝鲸ROS机器人论坛

    • 注册
    • 登录
    • 搜索
    • 版块
    • 话题
    • 热门
    ROS交流群
    ROS Group
    产品服务
    Product Service
    开源代码库
    Github
    官网
    Official website
    技术交流
    Technological exchanges
    激光雷达
    LIDAR
    ROS教程
    ROS Tourials
    深度学习
    Deep Learning
    机器视觉
    Computer Vision

    如何正确的处理TCP连接

    技术交流
    close socket c socket tcp socket tcp client serv
    1
    1
    3510
    正在加载更多帖子
    • 从旧到新
    • 从新到旧
    • 最多赞同
    回复
    • 在新帖中回复
    登录后回复
    此主题已被删除。只有拥有主题管理权限的用户可以查看。
    • weijiz
      weijiz 最后由 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 条回复 最后回复 回复 引用 0
      • 1 / 1
      • First post
        Last post
      Copyright © 2015-2023 BlueWhale community