我有一个客户端/服务器基础架构。目前他们使用 TcpClient 和 TcpListener 在所有客户端和服务器之间发送接收数据。
我目前所做的是当接收到数据时(在它自己的线程上),它被放入队列中以供另一个线程处理以释放套接字,以便它准备好并打开以接收新数据。
// Enter the listening loop.
while (true)
{
Debug.WriteLine("Waiting for a connection... ");
// Perform a blocking call to accept requests.
using (client = server.AcceptTcpClient())
{
data = new List<byte>();
// Get a stream object for reading and writing
using (NetworkStream stream = client.GetStream())
{
// Loop to receive all the data sent by the client.
int length;
while ((length = stream.Read(bytes, 0, bytes.Length)) != 0)
{
var copy = new byte[length];
Array.Copy(bytes, 0, copy, 0, length);
data.AddRange(copy);
}
}
}
receivedQueue.Add(data);
}
但是我想知道是否有更好的方法来做到这一点。例如,如果有 10 个客户端并且它们都想同时向服务器发送数据,则一个将通过而其他所有客户端都将失败。或者如果一个客户端连接速度慢并且占用套接字,所有其他通信将停止.
有没有办法可以同时接收所有客户端的数据,并在下载完成后将接收到的数据加入到队列中等待处理?
最佳答案
所以这是一个可以帮助您入门的答案 - 比我的 blog post 更适合初学者。 .
.Net 有一个围绕 Begin* 和 End* 调用的异步模式。例如 - BeginReceive
和 EndReceive
。它们几乎总是有它们的非异步对应物(在本例中为 Receive
);并实现完全相同的目标。
要记住的最重要的事情是,socket 的作用不仅仅是使调用异步 - 它们公开了一种叫做 IOCP(IO 完成端口,Linux/Mono 有这两个但我忘了名字)的东西,这对在服务器上使用; IOCP 的关键在于您的应用程序在等待数据时不使用线程。
如何使用开始/结束模式
与非异步方法相比,每个 Begin* 方法都多了 2 个参数。第一个是 AsyncCallback,第二个是对象。这两个的意思是,“这是一个完成后调用的方法”和“这是该方法中我需要的一些数据”。被调用的方法总是具有相同的签名,在这个方法中你调用 End* 对应的方法来获得如果你同步完成它会得到的结果。例如:
private void BeginReceiveBuffer()
{
_socket.BeginReceive(buffer, 0, buffer.Length, BufferEndReceive, buffer);
}
private void EndReceiveBuffer(IAsyncResult state)
{
var buffer = (byte[])state.AsyncState; // This is the last parameter.
var length = _socket.EndReceive(state); // This is the return value of the method call.
DataReceived(buffer, 0, length); // Do something with the data.
}
这里发生的是 .Net 开始等待来自套接字的数据,一旦它获得数据,它就会调用 EndReceiveBuffer
并传递“自定义数据”(在本例中为 buffer
) 通过 state.AsyncResult
传递给它。当您调用 EndReceive
时,它会返回接收到的数据的长度(如果失败则抛出异常)。
更好的套接字模式
此表单将为您提供中央错误处理 - 它可以在异步模式包装类似流的“事物”的任何地方使用(例如,TCP 按照发送的顺序到达,因此它可以被视为 Stream
对象)。
private Socket _socket;
private ArraySegment<byte> _buffer;
public void StartReceive()
{
ReceiveAsyncLoop(null);
}
// Note that this method is not guaranteed (in fact
// unlikely) to remain on a single thread across
// async invocations.
private void ReceiveAsyncLoop(IAsyncResult result)
{
try
{
// This only gets called once - via StartReceive()
if (result != null)
{
int numberOfBytesRead = _socket.EndReceive(result);
if(numberOfBytesRead == 0)
{
OnDisconnected(null); // 'null' being the exception. The client disconnected normally in this case.
return;
}
var newSegment = new ArraySegment<byte>(_buffer.Array, _buffer.Offset, numberOfBytesRead);
// This method needs its own error handling. Don't let it throw exceptions unless you
// want to disconnect the client.
OnDataReceived(newSegment);
}
// Because of this method call, it's as though we are creating a 'while' loop.
// However this is called an async loop, but you can see it the same way.
_socket.BeginReceive(_buffer.Array, _buffer.Offset, _buffer.Count, SocketFlags.None, ReceiveAsyncLoop, null);
}
catch (Exception ex)
{
// Socket error handling here.
}
}
接受多个连接
您通常做的是编写一个包含您的套接字等(以及您的异步循环)的类,并为每个客户端创建一个。例如:
public class InboundConnection
{
private Socket _socket;
private ArraySegment<byte> _buffer;
public InboundConnection(Socket clientSocket)
{
_socket = clientSocket;
_buffer = new ArraySegment<byte>(new byte[4096], 0, 4096);
StartReceive(); // Start the read async loop.
}
private void StartReceive() ...
private void ReceiveAsyncLoop() ...
private void OnDataReceived() ...
}
每个客户端连接都应由您的服务器类跟踪(以便您可以在服务器关闭时干净地断开它们,以及搜索/查找它们)。
关于c# - 接受多个 tcp 客户端的最佳方式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7104293/