C# 是否可以为多个客户端创建一个单套接字、单线程、TCP/IP数据包读取器?

C# 是否可以为多个客户端创建一个单套接字、单线程、TCP/IP数据包读取器?,c#,sockets,networking,single-threaded,C#,Sockets,Networking,Single Threaded,典型的服务器应用程序会为每个传入连接创建一个套接字,并生成一个新线程 然而,有可能在一个线程中自己进行解复用吗?我想要的是,应用程序为每个端点保留一个状态机,当数据在单个线程中可用时,该状态机将反序列化消息 线程是最简单的,我有一个可行的实现。然而,每个线程都会相当快地增加开销,并且延迟连接将需要资源。我想要的是,对于每个连接,状态机都会构建一条消息(线程化代码已经这样做了),并在完成后将反序列化消息分派到工作队列 这在.NET中可能吗?P/Invoke也是一个可接受的解决方案 典型的服务器应用

典型的服务器应用程序会为每个传入连接创建一个套接字,并生成一个新线程

然而,有可能在一个线程中自己进行解复用吗?我想要的是,应用程序为每个端点保留一个状态机,当数据在单个线程中可用时,该状态机将反序列化消息

线程是最简单的,我有一个可行的实现。然而,每个线程都会相当快地增加开销,并且延迟连接将需要资源。我想要的是,对于每个连接,状态机都会构建一条消息(线程化代码已经这样做了),并在完成后将反序列化消息分派到工作队列

这在.NET中可能吗?P/Invoke也是一个可接受的解决方案

典型的服务器应用程序会为每个传入连接创建一个套接字,并生成一个新线程

这不是一个真实的说法。很少有服务器——只有那些不需要扩展到大量连接的客户端的服务器,甚至不是所有的服务器——将整个线程专用于单个连接

我想说的是,只有最基本的服务器才会使用线程连接设计。我认为,由于实验性服务器(即学习编写网络代码的业余爱好者)比生产性服务器多得多,因此每个连接的线程数量可能会更多

但是IMHO只有生产服务器才真正与这个问题相关,因为它们展示了什么是好的设计和实现。对于那些人来说,每个连接的线程数肯定是少数

然而,有可能在一个线程中自己进行解复用吗

使用
Socket.Select()

然而,更典型的是使用几种可用于套接字的异步编程API中的一种,它使用I/O完成端口将I/O操作的处理分配给专用于此目的的线程池。这允许并发但有效地使用线程来最小化上下文切换

当前.NET 4.5和C#5.0特性的首选方法是将套接字封装在
NetworkStream
的实例中,这样我就可以使用
ReadAsync()
WriteAsync()
方法,这反过来又允许在C#代码中使用
wait
。这使得异步代码更易于阅读和实现

但您也可以使用,例如,
Socket.BeginReceive()
Socket.ReceiveAsync()
(后者对于需要极高可扩展性的服务器非常有用……尽管前者的可扩展性仍然比每个连接的线程高得多)


无论使用何种API,典型的服务器实现都将为每个连接包含一个状态对象类。这可以通过每个连接的线程设计来完成,就像异步I/O设计一样简单。您的状态机将驻留在该状态对象类中,因此套接字上的I/O操作的处理可以使用状态机。

当然,诀窍是使用异步IO(即BeginAccept、EndAccept、BeginRead、EndRead、BeginWrite和EndWrite)。这允许您在不需要线程的情况下处理多个客户端。这就是反应的工作方式。

使用该方法。

当调用该方法时,将向其传递三个套接字列表。一个是等待读取的套接字列表,一个是等待写入的列表(如果写入缓冲区已满,则必须等待写入),一个是等待错误的列表。您还可以告诉它等待的最长时间,它将阻塞该时间,直到其中一个套接字就绪。当函数返回时,它将修改列表以删除未准备好的套接字

创建读取阵列时,请插入主/母套接字,以便知道何时有新连接。还可以插入任何现有的连接。将所有这些也放在错误数组中。对于写数组,您只需要在写缓冲区已满时放入套接字。这可能是您在第一次迭代中可以忽略的事情,但当您开始投入大量流量时,这将非常重要

Socket.Select()
返回时,您只需循环读取列表中剩余的套接字,并按照服务器的要求处理数据。循环执行写操作并推出所有剩余的排队数据。遍历错误并关闭这些套接字或处理错误。再次调用select之前,请记住将空闲套接字放回列表中