<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[如何正确的处理TCP连接]]></title><description><![CDATA[<p dir="auto">TCP是一种非常常用的socket，在做各种网络通信的时候，它也是必不可少的。但是很多情况下我们并没有正确的处理TCP连接。最终可能导致大量的僵尸连接耗尽了服务器的资源。这里就稍微详细的说明正确的TCP连接创建和关闭的方式，以及僵尸连接出现的原因。因为作为开发软件的人更关心的是如何正确的使用API，所以底层的和如何使用关系不大的东西，比如TCP三次握手，在这篇文章会尽量避免。</p>
<p dir="auto">以下以C#作为示例</p>
<h4>建立连接</h4>
<p dir="auto">服务器开始监听</p>
<pre><code>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 ();
</code></pre>
<p dir="auto">一般设置好服务器监听地址和端口，服务器就可以开始监听了。这里采用的是阻塞式的socket。程序最后会阻塞在最后一句直到有客户端连接到服务器。</p>
<p dir="auto">客户端建立连接</p>
<pre><code>IPAddress targetIp = IPAddress.Parse (ipstr);
Socket mClientSocket = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
mClientSocket.Connect (new IPEndPoint (targetIp, Convert.ToInt32 (9999)));
</code></pre>
<p dir="auto">客户端程序也是比较简单的，设置好服务器的地址和端口就可以连接了。这里socket的读取和写入都是阻塞的。所以一般都是要单独给每个socket连接开一个线程。</p>
<p dir="auto">创建连接的过程是很直观和简单的，一般也很少会犯错。但是socket的断开过程就是一个比较大的坑。</p>
<h4>不正确的断开连接</h4>
<p dir="auto">如果服务器端或者客户端想要关闭连接，直接调用了Close方法。那么就会出问题。如服务器端程序执行</p>
<pre><code>mClientSocket.Close ();
</code></pre>
<p dir="auto">此时如果客户端正在调用 Receive 函数，客户端就会报出Socket被异常关闭的错误。但是客户端的这个socket实际上处在close_wait状态并没有关闭，socket也并不会被释放。如果程序这样一直执行下去最终就会耗尽系统的资源</p>
<p dir="auto">下面以实际的一个例子程序来演示一下</p>
<p dir="auto">服务器程序</p>
<pre><code>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 ();
		}
	}
}

</code></pre>
<p dir="auto">客户端程序</p>
<pre><code>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 ();
		}
	}
}

</code></pre>
<p dir="auto">服务器程序在连接建立之后直接调用了Close函数。<br />
这时通过netstat可以看到两个连接的状态。<br />
在命令行执行</p>
<pre><code>netstat -nap |grep 46755
</code></pre>
<p dir="auto">其中46755是客户端显示的端口号<br />
显示如下图<br />
<img src="/uploads/files/1466499941136-screenshot-from-2016-06-21-17-05-11.png" alt="0_1466499938307_Screenshot from 2016-06-21 17:05:11.png" class=" img-responsive img-markdown" /><br />
可以看到服务器也有一个socket处在FIN_WAIT2的状态。但是这个并没有什么影响，过一段时间后系统会自动释放这个socket。<br />
<img src="/uploads/files/1466500315861-screenshot-from-2016-06-21-17-11-38.png" alt="0_1466500313131_Screenshot from 2016-06-21 17:11:38.png" class=" img-responsive img-markdown" /><br />
同样如果客户端直接关闭连接就会导致服务器的socket处在close_wait的状态。</p>
<h4>正确的断开连接</h4>
<p dir="auto"><code>关闭之前一定要调用Shutdown</code><br />
<code>关闭之前一定要调用Shutdown</code><br />
<code>关闭之前一定要调用Shutdown</code><br />
重要的事情说三遍。Shutdown的作用就是告诉socket不要在继续读写了，socket要准备关闭了。Shutdown的方式有几种，可以只关闭本地的读写，也可以只关闭远方的读写，也可以同时关闭。</p>
<p dir="auto">正确的服务器关闭例子</p>
<pre><code>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 &amp;&amp; !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 ();
		}
	}
}
</code></pre>
<p dir="auto">不仅在本地主动关闭的时候要调用Shutdown，对方不正常关闭的时候也要调用Shutdown。如果对方不正常的关闭连接那么就会本地的socket就会抛出异常。如果对方是正常的关闭socket那么Receive的返回值就是0，然后根据这个去关闭本地的socket。</p>
<p dir="auto">比较完整的C#socket客户端和服务器的例子程序可以看这里。<br />
<a href="https://github.com/randoms/SharpLink/blob/master/SharpLink/Program.cs#L71" target="_blank" rel="noopener noreferrer">服务器程序</a><br />
<a href="https://github.com/randoms/SharpLink/blob/master/SharpLink/Program.cs#L202" target="_blank" rel="noopener noreferrer">客户端程序</a></p>
]]></description><link>http://community.bwbot.org/topic/39/如何正确的处理tcp连接</link><generator>RSS for Node</generator><lastBuildDate>Fri, 06 Mar 2026 05:21:25 GMT</lastBuildDate><atom:link href="http://community.bwbot.org/topic/39.rss" rel="self" type="application/rss+xml"/><pubDate>Tue, 21 Jun 2016 09:57:12 GMT</pubDate><ttl>60</ttl></channel></rss>