ROS Group 产品服务
Product Service 开源代码库
Github 官网
Official website 技术交流
Technological exchanges 激光雷达
LIDAR ROS教程
ROS Tourials 深度学习
Deep Learning 机器视觉
Computer Vision
如何正确的处理TCP连接
-
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是客户端显示的端口号
显示如下图
可以看到服务器也有一个socket处在FIN_WAIT2的状态。但是这个并没有什么影响,过一段时间后系统会自动释放这个socket。
同样如果客户端直接关闭连接就会导致服务器的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。