我有一个以每个客户端线程方式构建的服务器。最近,我遇到了一个问题,我很难找到解决方案,所以我想找一些帮助。
我的服务器主办一个大厅,大堂有很多房间(所有房间都由用户拥有),房间里有玩家。每个房间都有一个管理员,当管理员选择离开时 - 房间关闭,所有用户都应该返回大厅。
现在,我已经有了一个正常工作的代码 - 但问题是,我不知道如何让其他客户也离开房间。线程中运行的代码如下:
while(in_lobby)
{
//Receive a message
//Do stuff
//In certain cases change the Boolean to fit to the situation
//Send a comeback
}
while(in_room)
{
//Receive a message
//Do stuff
//In certain cases change the Boolean to fit to the situation
//Send a comeback
}
while(in_game)
{
//When a game started
//Not practical right now, though
}
从一个客户端的线程到另一个客户端的线程没有问题(因为它们不是本地变量,它们是我可以通过处理管理员选择关闭房间的线程更改的几个条件)
当条件DOES改变时,问题发生,并且while循环应该在下一次迭代之前退出。它为什么会发生?因为在迭代开始时有recv()
次呼叫,等待客户的消息。
现在,状况还可以,一切正常,但循环不会继续(所以它不会到达下一次迭代 - 看到条件为假),直到服务器收到某个来自客户的消息(它不应该,因为关闭房间并不依赖于普通用户 - 用户只接收并通过管理员的线程发送警报,关于房间被关闭)。
我的问题是:
我怎样才能表现自己想要的?如何让这些用户突破循环,返回大厅(管理员这样做是没有问题的,因为他的线程就是那个完成所有事情并且他成功返回大厅的线程)而不改变整体从每个客户端的线程架构?
答案 0 :(得分:1)
“因为在迭代开始时有一个recv()调用,等待客户端的消息。”
可能你不想在第一名的时候拨打recv()
电话
Windows Socket API中有select()
函数,您可以使用它来观察许多套接字文件描述符的状态更改。
基本上,您将有一个运行循环的线程,并轮询select()
调用的返回值。不要使用紧密循环来占用CPU,应该指定合理的超时值,但是可以使用零超时,并且仅轮询可用状态。
如果返回值表示某个观察到的套接字(select() > 0
)有某些操作可用,则可以检查观察到的套接字文件描述符(如果它们是使用
FD_ISSET(s, *set)
宏。
答案 1 :(得分:1)
因为在迭代开始时有一个recv()调用,等待客户端的消息。
如果你使用的是什么,我不熟悉这些细节,但是在类似情况下可以使用的两个技巧是:
recv()
读取的流中。意图是其中任何一个都会强制recv()
返回。
如果你不能用winsock做这个,那么另一个解决方案是添加另一个层。您编写了一个包装类,它管理从套接字读取的复杂性,同时仍然能够从其他来源接收通知。然后您的客户端只使用包装类来接收通信。
从长远来看,这个解决方案可能比直接使用recv()
更好,因为它分散了关注点;客户端代码只关心如何处理客户端而不是如何进行强健通信的细节,通信代码只需要处理如何接收和中继通信。
答案 2 :(得分:0)
除了πάνταῥεῖ的评论,你可以尝试实现Provider-Consumer pattern。
使用队列存储客户端消息,并使用该循环从中读取消息,如果没有要读取的消息继续。这样循环不会等待消息到达,即“提供者”作业(循环是消费者,因为它正在消耗来自队列的消息)。
因此,将调用recv
的代码移出循环并使用它来提供队列。
Queue queue; // This has to be thread save.
class Provider: Thread
{
void run(queue)
{
while(1)
{
message = recv(); // This is where waiting occurs.
queue.push(message);
}
}
}
// This looks like it fits inside another thread. ;)
while (some_condition)
{
message = queue.pop(); // This returns immediately.
if (message)
{
//... so some things.
}
}
答案 3 :(得分:0)
重构您的代码,以便在recv
上阻止您只有一个地方。然后,您可以随心所欲地移动客户端,而不必破坏recv
中阻止的线程。如果客户端发送一条消息,您仍然希望收到消息,对吗?
因此,当一个房间关闭时,关闭房间的线程可以将客户端移出它,而不需要打扰等待来自这些客户端的消息的线程。