[英]How do I write a scalable socket server using C# 4.0?
我想编写一个简单的套接字服务器,但是我希望它是可垂直扩展的,例如,不是每个连接创建一个线程,也不是长时间运行的任务,这可能会占用所有线程。
服务器接收包含查询的请求并流式传输任意大的结果。
我希望使用C#4中提供的技术和库来实现这一目标的惯用方法,重点是简单的代码,而不是原始性能。
重新打开套接字服务器是可伸缩系统的有用部分。 如果要水平缩放,则有不同的技术。 如果您从未创建套接字服务器,则应该无法回答此问题。
我已经在类似的工作了一两个星期,所以希望我能够帮助你一点点。
如果您专注于简单代码,我建议使用TcpClient和TcpListener类。 它们都使插座更容易使用。 虽然它们自.NET Framework 1.1以来就已存在,但它们已经更新,仍然是您最好的选择。
在如何利用.NET Framework 4.0编写简单代码方面,任务首先浮现在脑海中。 它们使编写异步代码变得更加痛苦,并且一旦C#5出现( 新的异步和等待关键字 ),迁移代码将变得更加容易。 以下是Tasks如何简化代码的示例:
而不是使用tcpListener.BeginAcceptTcpClient(AsyncCallback callback, object state);
并提供一个调用EndAcceptTcpClient();
的回调方法EndAcceptTcpClient();
并且可选地转换你的状态对象,C#4允许你利用闭包,lambda和Tasks来使这个过程更具可读性和可扩展性。 这是一个例子:
private void AcceptClient(TcpListener tcpListener)
{
Task<TcpClient> acceptTcpClientTask = Task.Factory.FromAsync<TcpClient>(tcpListener.BeginAcceptTcpClient, tcpListener.EndAcceptTcpClient, tcpListener);
// This allows us to accept another connection without a loop.
// Because we are within the ThreadPool, this does not cause a stack overflow.
acceptTcpClientTask.ContinueWith(task => { OnAcceptConnection(task.Result); AcceptClient(tcpListener); }, TaskContinuationOptions.OnlyOnRanToCompletion);
}
private void OnAcceptConnection(TcpClient tcpClient)
{
string authority = tcpClient.Client.RemoteEndPoint.ToString(); // Format is: IP:PORT
// Start a new Task to handle client-server communication
}
FromAsync非常有用,因为Microsoft提供了许多可以简化常见异步操作的重载。 这是另一个例子:
private void Read(State state)
{
// The int return value is the amount of bytes read accessible through the Task's Result property.
Task<int> readTask = Task<int>.Factory.FromAsync(state.NetworkStream.BeginRead, state.NetworkStream.EndRead, state.Data, state.BytesRead, state.Data.Length - state.BytesRead, state, TaskCreationOptions.AttachedToParent);
readTask.ContinueWith(ReadPacket, TaskContinuationOptions.OnlyOnRanToCompletion);
readTask.ContinueWith(ReadPacketError, TaskContinuationOptions.OnlyOnFaulted);
}
State只是一个用户定义的类,通常只包含TcpClient实例,数据(字节数组),也可能包含读取的字节。
正如你所看到的,ContinueWith可以用来代替许多繁琐的try-catches
,直到现在这是一个必要的邪恶。
在你的帖子开头你提到不想为每个连接创建一个线程或创建很长时间运行的任务,我想我会在这一点上解决这个问题。 就个人而言,我没有看到为每个连接创建一个线程的问题。
但是,您必须小心使用Tasks(对ThreadPool的抽象)来进行长时间运行。 ThreadPool很有用,因为创建新线程的开销不可忽略,对于诸如读取或写入数据和处理客户端连接等简短任务,首选任务。
您必须记住ThreadPool是具有专用函数的共享资源(避免花费更多时间创建线程而不是实际使用它的开销)。 因为它是共享的,如果你使用了一个线程,另一个资源就不能,这很快就会导致线程池饥饿和死锁情况。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.