一个典型的服务器应用程序会为每个传入连接创建一个套接字,从而产生一个新线程。
但是,是否可以在单个线程中自己进行解复用?我想要的是应用程序为每个端点保留一个状态机,当数据在单个线程中可用时反序列化消息。
线程是最简单的,我有一个可行的实现。然而,每个线程都会很快增加开销,并且延迟连接将需要资源。我想要的是,相反,状态机为每个连接构建一条消息(线程代码已经这样做了),并在完成后将反序列化的消息分派到工作队列。
这在.NET中可能吗?P/Invoke 也是一个可接受的解决方案。
一个典型的服务器应用程序会为每个传入连接创建一个套接字,从而产生一个新线程。
但是,是否可以在单个线程中自己进行解复用?我想要的是应用程序为每个端点保留一个状态机,当数据在单个线程中可用时反序列化消息。
线程是最简单的,我有一个可行的实现。然而,每个线程都会很快增加开销,并且延迟连接将需要资源。我想要的是,相反,状态机为每个连接构建一条消息(线程代码已经这样做了),并在完成后将反序列化的消息分派到工作队列。
这在.NET中可能吗?P/Invoke 也是一个可接受的解决方案。
一个典型的服务器应用程序会为每个传入连接创建一个套接字,从而产生一个新线程。
这不是一个真实的说法。很少有服务器——只有那些不需要扩展到大量连接的客户端的服务器,甚至不是所有的服务器——会将整个线程专用于单个连接。
我想说只有最基本的服务器会使用每个连接线程的设计。我想,由于实验服务器(即学习编写网络代码的爱好者)比生产服务器多得多,因此绝对数量可能偏向于每个连接的线程。
但恕我直言,只有生产服务器与该问题真正相关,因为它们显示了良好的设计和实施将是什么。对于那些人来说,每个连接的线程肯定会是少数。
但是,是否可以在单个线程中自己进行解复用?
使用Socket.Select()
一个线程可以有一个专用于处理多个套接字的线程。
然而,更典型的做法是使用可用于套接字的几个异步编程 API 之一,它使用 I/O 完成端口将 I/O 操作的处理外包给专用于该目的的线程池。这允许并发但有效地使用线程以最小化上下文切换。
我对当前 .NET 4.5 和 C# 5.0 特性的首选方法是将套接字包装在 的实例中NetworkStream
,以便我可以使用ReadAsync()
andWriteAsync()
方法,这反过来又允许await
在 C# 代码中使用。这使得异步代码更容易阅读和实现。
但是您也可以使用 eg Socket.BeginReceive()
or Socket.ReceiveAsync()
(后者对于需要极高可扩展性的服务器很有用……即使前者仍然比每个连接的线程更具可扩展性)。
不管使用什么 API,典型的服务器实现都会为每个连接包含一个状态对象类。这可以通过每个连接的线程设计来完成,就像使用异步 I/O 设计一样容易。您的状态机将驻留在该状态对象类中,以便套接字上的 I/O 操作处理可以使用状态机。
Use the Socket.Select method.
When you call the method, you pass it three lists of sockets. One is the list of sockets you're waiting for reads, one is a list you're waiting for writes (if the write buffer is full you will have to wait to write), and one is a list you're waiting for errors. You also tell it the maximum amount of time to wait, and it will block that long until one of the sockets is ready. When the function returns, it will modify the lists to remove the sockets that aren't ready.
When you create your read array, put the master/mother socket in so that you will know when there's a new connection. Also put in any existing connections. Put all of those in the error array also. For the write array, you only need to put sockets in when the write buffer is full. Probably this is something you can ignore in your first iteration, but it will be important when you start putting serious traffic through.
When Socket.Select()
returns, you need only loop through the sockets remaining in the read list and process the data as your server requires. Loop through the writes and push out any remaining queued data. Loop through the errors and close those sockets or handle errors. Remember to put the idle sockets back in the list before you call select again!
Absolutely, the trick is to use asynchorous IO (i.e. BeginAccept, EndAccept, BeginRead, EndRead, BeginWrite, and EndWrite). This allows you to handle multiple clients without the need of threads. This is the way that react works.