我正在编写一个基于 I/O 多路复用(选择)方式的 TCP 服务器类。 这段代码解释了基本思想:
GenericApp.cpp
TServer *server = new Tserver(/*parameters*/);
server->mainLoop();
目前服务器的行为独立于上下文,但在某种程度上我需要改进。
实际状态
receive(sockFd , buffer);
MSGData * msg= MSGFactory::getInstance()->createMessage(Utils::getHeader(buffer,1024));
EventHandler * rightHandler =eventBinder->getHandler(msg->type());
rightHandler->callback(msg);
在这个版本中,主循环从套接字读取,实例化正确类型的消息对象并调用适当的处理程序(有些东西可能无法正常工作,因为它编译但我没有测试过)。 正如您所注意到的,这允许程序员定义他的消息类型和适当的处理程序,但是一旦主循环启动,就什么也做不了了。 我需要使服务器的这一部分更具可定制性,以使此类适应更大的 问题的数量。
主循环代码
void TServer::mainLoop()
{
int sockFd;
int connFd;
int maxFd;
int maxi;
int i;
int nready;
maxFd = listenFd;
maxi = -1;
for(i = 0 ; i< FD_SETSIZE ; i++) clients[i] = -1; //Should be in the constructor?
FD_ZERO(&allset); //Should be in the constructor?
FD_SET(listenFd,&allset); //Should be in the constructor?
for(;;)
{
rset = allset;
nready = select (maxFd + 1 , &rset , NULL,NULL,NULL);
if(FD_ISSET( listenFd , &rset ))
{
cliLen = sizeof(cliAddr);
connFd = accept(listenFd , (struct sockaddr *) &cliAddr, &cliLen);
for (i = 0; i < FD_SETSIZE; i++)
{
if (clients[i] < 0)
{
clients[i] = connFd; /* save descriptor */
break;
}
}
if (i == FD_SETSIZE) //!!HANDLE ERROR
FD_SET(connFd, &allset); /* add new descriptor to set */
if (connFd > maxFd) maxFd = connFd; /* for select */
if (i > maxi) maxi = i; /* max index in client[] array */
if (--nready <= 0) continue;
}
for (i = 0; i <= maxi; i++)
{
/* check all clients for data */
if ( (sockFd = clients[i]) < 0) continue;
if (FD_ISSET(sockFd, &rset))
{
//!!SHOULD CLEAN BUFFER BEFORE READ
receive(sockFd , buffer);
MSGData * msg = MSGFactory::getInstance()->createMessage(Utils::getHeader(buffer,1024));
EventHandler * rightHandler =eventBinder->getHandler(msg->type());
rightHandler->callback(msg);
}
if (--nready <= 0) break; /* no more readable descriptors */
}
}
}
您对执行此操作的好方法有什么建议吗? 谢谢。
最佳答案
您的问题不仅仅是一个堆栈溢出问题。你可以在这些书中找到好的想法:
- http://www.amazon.com/Pattern-Oriented-Software-Architecture-Concurrent-Networked/dp/0471606952/ref=sr_1_2?s=books&ie=UTF8&qid=1405423386&sr=1-2&keywords=pattern+oriented+software+architecture
- http://www.amazon.com/Unix-Network-Programming-Volume-Networking/dp/0131411551/ref=sr_1_1?ie=UTF8&qid=1405433255&sr=8-1&keywords=unix+network+programming
基本上您要做的是一个 react 器
。您可以找到实现此模式的开源库。例如:
如果您希望您的处理程序有可能进行更多处理,您可以为他们提供对您的 TCPServer 的引用以及为以下事件注册套接字的方法:
read
,套接字准备好读取write
,套接字准备好写入accept
,监听套接字准备接受(用select
读取)close
,套接字关闭timeout
,等待下一个事件到期的时间(select
允许指定超时)
以便处理程序可以实现各种协议(protocol)半双工
或全双工
:
- 在您的示例中,处理程序无法回答收到的消息。这是
write
事件的作用,让处理程序知道它何时可以在套接字上发送。 read
事件也是如此。它不应在您的主循环中,而应在套接字read
处理程序中。- 您可能还想添加为具有
超时
的事件注册处理程序的可能性,以便您可以实现计时器
并丢弃空闲连接。
这会导致一些问题:
- 您的处理程序必须实现一个
状态机
来对网络事件使用react并更新它想要接收的事件。 - 您的处理程序可能想要创建和连接新套接字(考虑 Web 代理服务器、带 DCC 的 IRC 客户端、FTP 服务器等...)。为此,它必须能够创建一个套接字并将其注册到您的主循环中。这意味着处理程序现在可以接收两个套接字之一的回调,并且应该有一个参数告诉回调它是哪个套接字。或者您必须为每个套接字实现一个处理程序,它们将与消息的
queue
进行通信。队列是必需的,因为一个套接字的就绪状态独立于另一个套接字的就绪状态。您可能会在一个上阅读某些内容,但还没有准备好在另一个上发送它。 - 您必须管理每个处理程序指定的超时时间,这些处理程序可能不同。您最终可能会遇到超时的
优先级队列
如您所见,这不是一个简单的问题。您可能希望减少框架的通用性以简化其设计。 (例如仅处理 half-duplex
协议(protocol),如简单的 HTTP)
关于c++ - 上下文无关的 C++ TCP 服务器类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24755809/