服务器接收包含查询的请求并流传任意大的结果。
我想使用C# 4中可用的技术和库的成语方式来完成这项工作,重点是简单的代码而不是原始性能。
重新开放 套接字服务器是可扩展系统的有用部分。如果您要进行水平扩展,则有不同的技术。如果您从未创建过套接字服务器,则可能无法回答此问题。
我已经在类似的项目上工作了一两周,希望能对你有所帮助。
如果你的重点是简单的代码,我建议使用TcpClient和TcpListener类。它们都使套接字更易于使用。虽然它们自从.NET Framework 1.1以来就存在,但它们已经得到更新,并且仍然是您最好的选择。
在编写简单代码时如何利用.NET Framework 4.0,Tasks是我首先想到的东西。它们使编写异步代码变得不那么痛苦,而且一旦C#5发布(新async和await关键字),迁移代码将变得更加容易。以下是Tasks简化代码的示例:
与其使用 tcpListener.BeginAcceptTcpClient(AsyncCallback callback, object state);
并提供一个回调方法,该方法会调用 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(线程池的抽象)进行长时间运行的操作。线程池很有用,因为创建新线程的开销是不可忽略的,对于读取或写入数据以及处理客户端连接等短期任务,优先选择Tasks。
您必须记住,线程池是共享资源,具有专门的功能(避免花费更多时间创建线程而不是实际使用它)。因为它是共享的,如果您使用了一个线程,另一个资源将无法使用,这可能会迅速导致线程池饥饿和死锁情况。