2010-04-24 180 views
4

这不是一个问题,说真的,我只是在寻找一些准则:) 我目前正在写这应该使用的线程低数量,因为它可以一些抽象的TCP服务器。非阻塞TCP服务器

目前它的工作方式如下。我有一个线程正在监听和一些工作线程。 Listener线程只是坐等客户端连接我希望每个服务器实例有一个单一的监听器线程。工作线程正在客户端套接字上执行所有读/写/处理任务。

所以我的问题是建立有效的工作进程。我遇到了一些我仍然无法解决的问题。工人代码是类似的东西(代码是非常简单的只是为了显示一个地方,我有我的问题):

List<Socket> readSockets = new List<Socket>(); 
List<Socket> writeSockets = new List<Socket>(); 
List<Socket> errorSockets = new List<Socket>(); 

while(true){ 
    Socket.Select(readSockets, writeSockets, errorSockets, 10); 

    foreach(readSocket in readSockets){ 
     // do reading here 
    } 

    foreach(writeSocket in writeSockets){ 
     // do writing here 
    } 

    // POINT2 and here's the problem i will describe below 
} 

它的工作原理都smothly接受,因为while循环是循环的100%的CPU使用率一遍,如果我让我的客户端执行send-> receive-> disconnect例程,这并不是那么痛苦,但是如果我试着保持活动,再次send-> receive-> send->再接收一遍,它确实会吃掉所有的CPU。所以我的第一个想法是在那里安排睡眠,我检查是否所有的套接字都有数据发送,然后将Thread.Sleep放在POINT2中,持续10ms,但是这10ms之后,当我想要接收下一个时间时,会产生10ms的巨大延迟命令从客户端套接字。例如,如果我不试图“保持活着”命令正在执行10-15毫秒内,并保持活着它变得更糟的至少10毫秒:(

也许这只是一个糟糕的架构?有什么办法可以让我的处理器无法获得100%的利用率,而我的服务器会尽快对客户端插座上出现的问题做出反应?也许有人可以指出一个非阻塞服务器和它应该维护的体系结构的一个好例子吗?

回答

3

先看看TcpListener课程,它有一个BeginAccept方法不会阻止,并且会在有人连接时调用您的某个函数。

而且看一看的Socket类及其Begin方法。这些工作方式相同。每当某个事件触发时,就会调用其中一个函数(callback function),然后您可以处理该事件。所有的Begin方法都是异步的,所以它们不会被阻塞,它们也不应该使用100%的CPU。基本上你想BeginReceive阅读和BeginSend写我相信。

您可以通过搜索这些方法和异步插座教程找到更多关于谷歌。例如,用这种方式实现一个TCP客户端,例如:Here's how。即使对于您的服务器,它的工作方式也基本相同。

这样,你不需要任何的无限循环,它的所有事件驱动。

+2

它会产生大量的线程,不是吗?我使用循环,所以我可以通过读/写一个小缓冲区来处理一个线程中的大量套接字。 – hoodoos 2010-04-25 18:38:26

+0

我想它会的。你确定这是一个问题吗?在任何情况下,你可以发布你的确切代码,导致100%的CPU?避免它的一个想法是有一个特殊的“保持活着”的消息,像服务器发送“ping?”的东西。给客户**每x秒**并期待“pong!”在下一个“平?”之前?应该发送。如果它不来,则假设连接断开。这样你就没有睡觉了。保持活力只是每60秒完成一次,而不是等。例如,您可以使用AutoResetEvent来指示何时应发送保持活动状态,具体取决于代码我认为 – IVlad 2010-04-25 18:55:40

+0

@hoodoos您可以在单个线程上使用TcpListener BeginAccept,在下一次调用BeginAccept()你的AsyncCallback。在听另一个连接之前,您可以完全处理一个连接。 – khargoosh 2016-08-16 01:23:59

1

您正在创建点对点应用程序还是客户端服务器应用程序?你必须考虑你通过套接字输入的数据量。

异步BeginSend和BeginReceive是去,你将需要实现事件的方式,但它的速度快,一旦你得到它的权利。

也许不希望将您的发送和接收超时设置得太高,但应该有一个超时,以便如果在一段时间后没有收到任何内容,它将从该块中出来并且您可以处理它那里。

+0

我正在创建服务器应用程序。最后,我实现了一些抽象工作者,这些工作人员正在分阶段地开发抽象协议链,并且实际上我设法使其工作得很好。现在我需要让它处理活着的连接,而不用吃那么多的CPU。目前它可以处理250个并发连接。我想我会在谷歌上发布代码,所以你可以查看它。谢谢。在我的情况下,开始接收/发送将不起作用,因为我想让我的应用程序不是线程饿,尝试处理2个核心CPU上的100个并发客户端。你会发现它在上下文切换时吃了很多CPU – hoodoos 2010-04-29 19:14:56

0

微软有一个不错的异步TCP服务器的例子。它需要一点点围绕它。在我能够基于这个例子为我自己的程序创建基本的TCP框架之前的几个小时。

http://msdn.microsoft.com/en-us/library/fx6588te.aspx

程序逻辑是有点像这样。有一个线程调用listener.BeginAccept,然后在allDone.WaitOne上阻塞。 BeginAccept是一个异步调用,它被卸载到线程池并由OS处理。当新连接进来时,操作系统调用从BeginAccept传入的回调方法。该方法翻转allDone让主听线程知道它可以再次听。回调方法只是一个过渡方法,并继续调用另一个异步调用来接收数据。

提供的回调方法ReadCallback是异步调用的主要工作“循环”(有效递归异步调用)。我宽泛地使用术语“循环”,因为每个方法调用实际上完成,但不是在调用下一个异步方法之前。实际上,你有大量的异步调用都会互相调用,并传递你的“状态”对象。这个对象是你自己的对象,你可以随心所欲地做任何事情。当操作系统调用你的方法

逢回调方法将只能得到两件事情返回:

1)Socket对象代表的连接与你使用你的逻辑

2)State对象

使用状态对象和套接字对象,可以有效地异步处理“连接”。操作系统非常擅长。

另外,因为您的主循环会阻塞等待连接,并通过异步调用将这些连接卸载到线程池,因此它大部分时间都保持空闲状态。您的套接字的线程池由操作系统通过完成端口进行处理,因此在数据进入之前它们不会执行任何实际工作。使用很少的CPU并且通过线程池进行有效线程化。

P.S.据我所知,你不想用这些方法做任何艰苦的工作,只是处理数据的移动。由于线程池是网络IO的池,并且被其他程序共享,所以您应该通过线程/任务/异步卸载任何艰苦工作,以免导致套接字线程池陷入停滞状态。

P.P.S.我没有找到关闭监听连接的方法,而不是仅仅处理“监听者”。由于调用了beginListen的异步调用,所以在连接进入之前,该方法永远不会返回,这意味着,我无法告诉它停止,直到它返回。我想我会在MSDN上发布一个关于它的问题。如果我得到了很好的回应,我会联系。

0

一切都很好是你的代码exept超时值。你将它设置为10微秒(10 * 10^-6),这样你的while例程经常迭代。你应该设置和足够的价值(例如10秒),你的代码不会吃100%的CPU。

List<Socket> readSockets = new List<Socket>(); 
List<Socket> writeSockets = new List<Socket>(); 
List<Socket> errorSockets = new List<Socket>(); 

while(true){ 
    Socket.Select(readSockets, writeSockets, errorSockets, 10*1000*1000); 

    foreach(readSocket in readSockets){ 
     // do reading here 
    } 

    foreach(writeSocket in writeSockets){ 
     // do writing here 
    } 

    // POINT2 and here's the problem i will describe below 
}