4

这真的不是一个问题,我只是在寻找一些指导方针:) 我目前正在编写一些抽象的 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 
}

由于while循环再次循环,它可以顺利接受100%的CPU利用率,如果我让我的客户执行send->receive->disconnect例程,那并不是那么痛苦,但是如果我尝试保持活力,则执行send->接收->发送->重新接收,它真的吃光了所有的 CPU。所以我的第一个想法是在那里休眠,我检查是否所有套接字都发送了数据,然后将 Thread.Sleep 放入 POINT2 仅 10 毫秒,但是这 10 毫秒后会在我想接收下一个时产生 10 毫秒的巨大延迟来自客户端套接字的命令..例如,如果我不尝试“保持活动”命令正在 10-15 毫秒内执行,并且保持活动状态至少 10 毫秒会变得更糟:(

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

4

4 回答 4

4

先看一下TcpListener类。它有一个不会阻塞的 BeginAccept 方法,并且会在有人连接时调用您的函数之一。

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

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

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

于 2010-04-24T21:28:00.837 回答
1

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

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

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

于 2010-04-28T06:58:59.403 回答
1

Microsoft 有一个不错的异步 TCP 服务器示例。你需要花点时间把头绕在它周围。我花了几个小时才能够根据这个例子为我自己的程序创建基本的 TCP 框架。

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

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

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

当操作系统调用您的方法时,每个回调方法只会返回两件事:

1) 表示连接的 Socket 对象

2) 用于逻辑的状态对象

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

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

PS据我了解,您不想用这些方法做任何艰苦的工作,只是处理数据的移动。由于线程池是网络 IO 的池并且由其他程序共享,因此您应该通过线程/任务/异步卸载任何繁重的工作,以免导致套接字线程池陷入困境。

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

于 2011-10-04T14:04:16.433 回答
0

一切都很好,只是您的代码除外超时值。您将其设置为 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 
}
于 2015-04-23T12:06:47.080 回答