非阻塞Tcp服务器

时间:2010-04-24 21:09:44

标签: c# sockets tcp nonblocking

这不是一个问题,我只是在寻找一些指导方针:) 我正在编写一些抽象的tcp服务器,它应尽可能少地使用线程。

目前它以这种方式工作。我有一个线程在做侦听和一些工作线程。监听器线程只是坐着等待客户端连接我希望每个服务器实例都有一个监听器线程。工作线程正在客户端套接字上执行所有读/写/处理作业。

所以我的问题在于建立有效的工作流程。我遇到了一些我无法解决的问题。工作人员代码是这样的(代码非常简单,只是为了显示我遇到问题的地方):

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 
}

它可以全部接受100%CPU利用率,因为while循环再次循环,如果我让我的客户端正在执行send-&gt; receive-&gt;断开例程它不是那么痛苦,但如果我试图保持还活着做发送 - >接收 - >发送 - >再次收到它真的吃掉了所有的CPU。所以我的第一个想法是在那里睡觉,我检查是否所有套接字都发送了他们的数据,然后将Thread.Sleep放入POINT2只持续了10ms,但是这10ms之后产生了10ms的巨大延迟,当我想接下来的时候来自客户端套接字的命令..例如,如果我不尝试“保持活动”命令正在10-15ms内执行并且保持活动状态,它将在至少10ms内变得更糟:(

也许这只是一个糟糕的架构?可以做什么,以便我的处理器不会获得100%的利用率,我的服务器会尽快对客户端套接字中出现的内容做出反应?也许有人可以指出它应该维护的非阻塞服务器和架构的一个很好的例子吗?

4 个答案:

答案 0 :(得分:4)

首先看一下TcpListener课程。它有一个不会阻塞的BeginAccept方法,并会在有人连接时调用你的一个函数。

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

您可以通过搜索这些方法和异步套接字教程在Google上找到更多信息。例如Here's how以这种方式实现TCP客户端。即使对于您的服务器,它的工作方式基本相同。

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

答案 1 :(得分:1)

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

异步BeginSend和BeginReceive是要走的路,你需要实现这些事件,但是一旦你做对了就会很快。

可能不希望将发送和接收超时设置得太高,但是应该有一个超时,这样如果在一定时间后没有收到任何内容,它将会从块中出来,你可以在那里处理它。

答案 2 :(得分:1)

Microsoft有一个很好的异步TCP服务器示例。需要一点时间来绕过它。在我能够基于这个例子为我自己的程序创建基本TCP框架之前,这是我自己的几个小时。

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

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

提供的回调方法ReadCallback是异步调用的主要工作“循环”(有效的递归异步调用)。我松散地使用术语“循环”,因为每个方法调用实际上都已完成,但在调用下一个异步方法之前不会。实际上,你有一堆异步调用都相互调用,你传递了你的“状态”对象。这个对象是你自己的对象,你可以用它做任何你想做的事情。

每个回调方法只会在操作系统调用方法时返回两件事:

1)表示连接的套接字对象

2)用于逻辑的状态对象

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

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

P.S。据我所知,你不想用这些方法做任何艰苦的工作,只是处理数据的移动。由于线程池是您的网络IO的池并由其他程序共享,因此您应该通过threads / tasks / async卸载任何繁重的工作,以免导致套接字线程池陷入困境。

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

答案 3 :(得分: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 
}
相关问题