C# Async TCP Server 矫枉过正?
Posted
技术标签:
【中文标题】C# Async TCP Server 矫枉过正?【英文标题】:C# Async TCP Server overkill? 【发布时间】:2011-01-27 21:36:19 【问题描述】:这确实是一个实施问题,所以我觉得最好从我的具体案例开始。
我有一个 C# 服务器,它从移动客户端异步侦听 TCP 连接。当一个移动客户端连接一个新线程时,客户端会发送一个小的(通常小于 100 字节)文本消息并接收一个类似大小的返回。服务器响应后,关闭连接并结束线程。
当前的基本用法是用户登录,检查内容有时长达 5 分钟,发送少量消息,从而在服务器上快速连续创建新线程 ,并且他们断开连接仅在几个小时后重新连接。此外,每个用户都有自己的服务器在他们的 PC 上运行,因此大多数服务器在任何给定时间都只会连接一个客户端,在极少数情况下会连接两个。
现在我遇到了以下错误,一个现有的连接被远程主机强行关闭,这让我想,我做错了吗? em>
所以我的问题:
-
我当前的设置在这里合适吗?
如果是这样,我应该在发送一条小消息后结束线程,还是在给定的空闲时间后使其保持活动状态并关闭?
万一我做的一切都正确,极不可能,我是否应该通过在放弃前简单地重试几次来避免错误?
第四次也是最后一次,该错误完全杀死了服务器(服务器由另一个进程生成,任何未捕获的异常都会杀死它),如果我们已经做到了这一点,并且我的实现还可以,我该如何避免这种情况?
编辑:
在这里回答一些问题:
异常发生在我收到所有数据之前,但仅在用户快速连续发送多条消息的情况下发生。 我记得最大积压为 5,除非用户正在运行 Windows Server,但是我没有设置我的,也不知道默认值是什么,我会尝试将其显式设置为 5。异步服务器代码:
public void StartListening()
//Data buffer for incoming data.
byte[] bytes = new Byte[1024];
//Establish the local endpoint for the socket.
IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());
IPAddress ipAddress = ipHostInfo.AddressList[0];
IPEndPoint localEndPoint = new IPEndPoint(ipAddress, Port);
//Create a TCP/IP socket.
Socket listener = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
listener.SetSocketOption(SocketOptionLevel.Socket,SocketOptionName.DontLinger,1);
listener.SetSocketOption(SocketOptionLevel.Socket,SocketOptionName.ReuseAddress,1);
//Bind the socket to the local endpoint and listen for
//incoming connections.
try
listener.Bind(localEndPoint);
listener.Listen(100);
while (listening)
//Set the event to nonsignaled state.
allDone.Reset();
//Start an asychronous socket to listen for connections.
Print("Waiting for a connection...");
listener.BeginAccept(
new AsyncCallback(AcceptCallback),
listener);
//Wait until a connection is made before continuing.
allDone.WaitOne();
catch (Exception e)
Print(e.ToString());
listener.Close();
public void AcceptCallback(IAsyncResult arg)
//Signal the main thread to continue.
allDone.Set();
try
//Get the socket that handles the client request.
Socket listener = (Socket) arg.AsyncState;
Socket handler = listener.EndAccept(arg);
//Create the state object.
StateObject state = new StateObject();
state.workSocket = handler;
handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReadCallback), state);
catch (ObjectDisposedException ex)
Print("Server terminated from another thread.");
public void ReadCallback(IAsyncResult arg)
String content = String.Empty;
//Retrieve the state object and the handler socket
//from the asynchronous state object.
StateObject state = (StateObject) arg.AsyncState;
Socket handler = state.workSocket;
//Read data from the client socket.
int bytesRead = 0;
try
bytesRead = handler.EndReceive(arg);
catch (ObjectDisposedException ex)
Print("Process was terminated from another thread.");
if (bytesRead > 0)
//There might be more data, so store the data received so far.
state.sb.Append(Encoding.ASCII.GetString(
state.buffer,0,bytesRead));
//Check for end-of-file tag. If it is not there, read
//more data.
content = state.sb.ToString();
if (content.IndexOf("<EOF>") > -1)
content = content.Remove(content.Length-6);
//All the data has been read from the
//client. Display it on the console.
Print("Read " + content.Length + " bytes from socket. \n Data : " + content);
Respond(handler, content);
else
//Not all data received. Get more.
handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReadCallback), state);
private void Send(Socket handler, String data)
//Convert the string data to byte data using ASCII encoding.
byte[] byteData = Encoding.ASCII.GetBytes(data);
//Begin sending the data to the remote device.
handler.BeginSend(byteData,0,byteData.Length,0,
new AsyncCallback(SendCallback),handler);
private void SendCallback(IAsyncResult arg)
try
//Retrieve the socket from the state object.
Socket handler = (Socket) arg.AsyncState;
//Complete sending the data to the remote device.
int bytesSent = handler.EndSend(arg);
Print("Sent " + bytesSent + " bytes to client.");
handler.Shutdown(SocketShutdown.Both);
//need to make this not linger around
handler.LingerState = new LingerOption(true,1);
handler.Close();
catch (Exception e)
Print(e.ToString());
【问题讨论】:
这听起来像是一个 UDP 应用程序——与处理消息相比,您可能花费更多的时间和带宽进行三次 TCP 握手。 你可能是对的,如果我没记错的话,由于某种原因我不能使用 UDP,但我会再研究一下。 【参考方案1】:理想情况下,您将使用 .NET 线程池,这比为每个连接创建一个新线程要高效得多。您能否分享您的确切“异步”代码 - 如果您在 TCPListener 上使用现有的异步模式,那么您可能已经在使用线程池。
关于异常,当您的客户端与服务器断开连接时,您会期望看到这种情况。它是否在您设法接收所有数据之前发生?你是在客户端刷新你的套接字吗?
就完全崩溃服务器而言,只需继续测试,并记录任何全局未处理的异常。这样您就可以了解所有可以预期的内容。
【讨论】:
而且使用WSAAsyncSelect
或WSAEventSelect
会比使用线程池更高效。
我已经添加了我的代码,异常发生在接收到所有数据之前,因此它很可能与最大数量的连接有关,因为异步它们都被立即接受但未处理。
【参考方案2】:
您可能想查看this article,其中列出了需要检查的几项内容。例如,当您在套接字上 .Listen() 时,您的 backlog 设置为什么?
【讨论】:
我没有设置它,我现在明确设置它并且我得到的错误实例较少,但它仍然会发生。我相信最大值是 5,除非您运行的是 Windows Server。不过我会看看这篇文章,看起来很有信息量。以上是关于C# Async TCP Server 矫枉过正?的主要内容,如果未能解决你的问题,请参考以下文章
在 C# 5 中使用 async/await 模式编写高度可扩展的 TCP/IP 服务器?