如何使用 C# 4.0 编写可缩放的套接字服务器

本文关键字:缩放 套接字 服务器 何使用 | 更新日期: 2023-09-27 18:10:22

我想编写一个简单的套接字服务器,但我希望它是垂直可扩展的,例如,不为每个连接或长时间运行的任务创建一个线程,这可能会消耗所有线程。

服务器接收包含查询的请求,并流式传输任意大的结果。

我希望使用C# 4中可用的技术和库的惯用方法来执行此操作,重点是简单的代码,而不是原始性能。

重新套接字服务器是可扩展系统的有用部分。如果要水平缩放,可以使用不同的技术。如果您从未创建过套接字服务器,则可能无法回答此问题。

如何使用 C# 4.0 编写可缩放的套接字服务器

我已经在做类似的事情一两个星期了,所以希望我能帮助你一点。

如果你的重点是简单的代码,我建议使用 TcpClient 和 TcpListener 类。它们都使插座更容易使用。虽然它们自 .NET Framework 1.1 以来就已存在,但它们已更新,仍然是您最好的选择。

在如何利用 .NET Framework 4.0 编写简单代码方面,首先想到的是任务。它们使编写异步代码变得不那么痛苦,并且在 C# 5 发布后迁移代码将变得更加容易(新的 async 和 await 关键字(。下面是任务如何简化代码的示例:

C# 4 不是使用 tcpListener.BeginAcceptTcpClient(AsyncCallback callback, object state); 并提供一个调用EndAcceptTcpClient();并选择性地强制转换状态对象的回调方法,而是允许您利用闭包、lambda 和任务来使此过程更具可读性和可伸缩性。下面是一个示例:

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,直到现在这些繁琐都是必要的邪恶。

在文章的开头,您提到不想为每个连接创建一个线程或创建运行时间很长的任务,我想我会在这一点上解决这个问题。就个人而言,我认为为每个连接创建一个线程没有问题。

但是,您必须小心的是将任务(线程池上的抽象(用于长时间运行的操作。ThreadPool 非常有用,因为创建新线程的开销不可忽略,对于读取或写入数据以及处理客户端连接等短任务,首选任务。

您必须记住,ThreadPool 是具有专用功能的共享资源(避免花费更多时间创建线程而不是实际使用它的开销(。因为它是共享的,所以如果您使用了一个线程,则另一个资源不能,这可能会很快导致线程池不足和死锁情况。