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

时间:2015-03-25 15:43:30

标签: c# sockets networking single-threaded

典型的服务器应用程序将为每个传入连接创建一个套接字,从而产生一个新线程。

但是,是否可以在单个线程中自行解复用?我想要的是应用程序为每个端点保留一个状态机,当数据在单个线程中可用时,它会对消息进行反序列化。

线程是最简单的,我有一个有效的实现。但是,每个线程都会相当快地增加开销,而延迟连接将需要资源。我想要的是,对于每个连接,状态机建立一条消息(线程代码已经完成了),并在完成时将反序列化的消息调度到工作队列。

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

3 个答案:

答案 0 :(得分:2)

  

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

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

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

但恕我直言,只有生产服务器真正与这个问题相关,因为它们显示了良好的设计和实现。对于那些人来说,每个连接的线程绝对是少数。

  

然而,是否可以在一个线程中自行进行解复用?

使用Socket.Select()可以使用一个专用于处理多个套接字的线程。

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

我使用当前.NET 4.5和C#5.0功能的首选方法是将套接字包装在NetworkStream的实例中,以便我可以使用ReadAsync()WriteAsync()方法,这反过来允许在C#代码中使用await。这使得异步代码更容易阅读和实现。

但你可以使用例如Socket.BeginReceive()Socket.ReceiveAsync()(后者对于需要极高可扩展性的服务器非常有用......即使前者仍然比每个连接的线程更具可扩展性)。

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

答案 1 :(得分:1)

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

答案 2 :(得分:1)

使用Socket.Select方法。

调用方法时,会传递三个套接字列表。一个是您正在等待读取的套接字列表,一个是您正在等待写入的列表(如果写入缓冲区已满,您将不得不等待写入),一个是您列出的列表&# 39;等待错误。您还告诉它等待的最长时间,并且它将阻塞那么长,直到其中一个套接字准备就绪。当函数返回时,它将修改列表以删除尚未准备好的套接字。

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

Socket.Select()返回时,您只需要遍历读取列表中剩余的套接字并根据服务器的需要处理数据。循环写入并推出任何剩余的排队数据。循环错误并关闭这些套接字或处理错误。在再次调用select之前,请记得将空闲套接字放回列表中!