简体   繁体   English

如何使用C#4.0编写可扩展的套接字服务器?

[英]How do I write a scalable socket server using C# 4.0?

I want to write a simple socket server, however I'd like it to be vertically scalable, for example, not creating a thread per connection or very long running tasks, which may consume all threads. 我想编写一个简单的套接字服务器,但是我希望它是可垂直扩展的,例如,不是每个连接创建一个线程,也不是长时间运行的任务,这可能会占用所有线程。

The server receives a request containing a query and streams an arbitrarily large result. 服务器接收包含查询的请求并流式传输任意大的结果。

I would like the idiomatic way to do this using techniques and libraries that are available in C# 4, with an emphasis on simple code, rather than raw performance. 我希望使用C#4中提供的技术和库来实现这一目标的惯用方法,重点是简单的代码,而不是原始性能。

Reopening A socket server is a useful part of a scalable system. 重新打开套接字服务器是可伸缩系统的有用部分。 If you want to scale horizontally, there are different techniques. 如果要水平缩放,则有不同的技术。 You should probably won't be able to answer this question if you have never created a socket server. 如果您从未创建套接字服务器,则应该无法回答此问题。

I've been working on something similar for a week or two now so hopefully I'll be able to help you out a bit. 我已经在类似的工作了一两个星期,所以希望我能够帮助你一点点。

If your focus is on simple code, I'd recommend using the TcpClient and TcpListener classes. 如果您专注于简单代码,我建议使用TcpClient和TcpListener类。 They both make sockets much easier to work with. 它们都使插座更容易使用。 While they have existed since .NET Framework 1.1 they have been updated and are still your best bet. 虽然它们自.NET Framework 1.1以来就已存在,但它们已经更新,仍然是您最好的选择。

In terms of how to utilize the .NET Framework 4.0 in writing simplistic code, Tasks are the first thing that come to mind. 在如何利用.NET Framework 4.0编写简单代码方面,任务首先浮现在脑海中。 They make writing asynchronous code much less painful and it will become much easier to migrate your code once C# 5 comes out ( new async and await keywords ). 它们使编写异步代码变得更加痛苦,并且一旦C#5出现( 新的异步和等待关键字 ),迁移代码将变得更加容易。 Here is an example of how Tasks can simplify your code: 以下是Tasks如何简化代码的示例:

Instead of using tcpListener.BeginAcceptTcpClient(AsyncCallback callback, object state); 而不是使用tcpListener.BeginAcceptTcpClient(AsyncCallback callback, object state); and providing a callback method which would call EndAcceptTcpClient(); 并提供一个调用EndAcceptTcpClient();的回调方法EndAcceptTcpClient(); and optionally cast your state object, C# 4 allows you to utilize closures, lambdas, and Tasks to make this process much more readable and scalable. 并且可选地转换你的状态对象,C#4允许你利用闭包,lambda和Tasks来使这个过程更具可读性和可扩展性。 Here is an example: 这是一个例子:

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 is very useful as Microsoft has provided many overloads that can simplify common asynchronous operations. FromAsync非常有用,因为Microsoft提供了许多可以简化常见异步操作的重载。 Here's another example: 这是另一个例子:

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 is just a user-defined class that usually just contains the TcpClient instance, the data (byte array), and perhaps the bytes read as well. State只是一个用户定义的类,通常只包含TcpClient实例,数据(字节数组),也可能包含读取的字节。

As you can see, ContinueWith can be used to replace a lot of cumbersome try-catches that until now were a necessary evil. 正如你所看到的,ContinueWith可以用来代替许多繁琐的try-catches ,直到现在这是一个必要的邪恶。

At the beginning of your post you mentioned not wanting to create a thread per connection or create very long running tasks and I thought I would address that at this point. 在你的帖子开头你提到不想为每个连接创建一个线程或创建很长时间运行的任务,我想我会在这一点上解决这个问题。 Personally, I don't see the problem with creating a thread for each connection. 就个人而言,我没有看到为每个连接创建一个线程的问题。

What you must be careful with, however, is using Tasks (an abstraction over the ThreadPool) for long-running operations. 但是,您必须小心使用Tasks(对ThreadPool的抽象)来进行长时间运行。 The ThreadPool is useful because the overhead of creating a new Thread is not negligible and for short tasks such as reading or writing data and handling a client connection, Tasks are preferred. ThreadPool很有用,因为创建新线程的开销不可忽略,对于诸如读取或写入数据和处理客户端连接等简短任务,首选任务。

You must remember that the ThreadPool is a shared resource with a specialized function (avoiding the overhead of spending more time creating a thread than actually using it). 您必须记住ThreadPool是具有专用函数的共享资源(避免花费更多时间创建线程而不是实际使用它的开销)。 Because it is shared, if you used a thread, another resource cannot and this can quickly lead to thread-pool starvation and deadlock scenarioes. 因为它是共享的,如果你使用了一个线程,另一个资源就不能,这很快就会导致线程池饥饿和死锁情况。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM