我有一个TCP服务器在无限循环中运行,如下面的伪代码:
while (true)
{
auto new_connected_socket = accept(listening_socket,...);
// Here it may block for a long time
std::thread(fn_new_connection_handler, new_connected_socket);
}
如果管理员想要停止TCP服务器,那么很好地打破无限循环是必不可少的。
我想到的一种方法是使用特殊套接字连接到TCP服务器。套接字“特殊”的原因是它的源IP是“特殊的”,例如,255.255.255.255显然不是有效的源IP。
如果它是合法的,那么客户端伪造的代码可能如下所示:
auto special_socket = socket(...);
bind(special_socket, "255.255.255.255", ...);
connect(special_socket, tcp_server_addr, ...);
和服务器端伪代码可以如下:
while (true)
{
auto new_connected_socket = accept(listening_socket,...);
// Here it may block for a long time
if (IsSpecialSourceIp(new_connected_socket))
{
break; // Exit the server thread.
}
else
{
std::thread(fn_new_connection_handler, new_connected_socket);
}
}
这种疗法是否可行?
答案 0 :(得分:2)
如manpage of accept(man 2 accept
)中所述,您可以标记套接字以进行非阻止操作,并使用select或poll查看连接是否在等待。或者甚至反复调用accept并检查EAGAIN或EWOULDBLOCK作为返回值,其间有足够的暂停。
当然,选择或轮询是更好的选择,因为您不需要“忙等待”并且您可以获得新连接的即时通知(较低的延迟)。与阻止代码执行某些模糊的套接字魔法来关闭服务器相比,这当然是一个更好的解决方案。
答案 1 :(得分:0)
不要试图使用“特殊”地址,只需在应用程序的内存中设置while
循环可以查看的标记,然后connect()
绑定到任何地址的普通套接字服务器机器的本地IP(让connect()
为您处理绑定,您不需要手动调用bind()
),例如:
bool shutting_down = false;
while (!shutting_down)
{
auto new_connected_socket = accept(listening_socket,...);
// Here it may block for a long time
if (shutting_down)
{
closesocket(new_connected_socket);
break;
}
std::thread(fn_new_connection_handler, new_connected_socket);
}
shutting_down = true;
if (server is listening)
{
sockaddr_in server_addr = {0};
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(my server port);
server_addr.sin_addr.s_ddr = inet_addr("127.0.0.1");
SOCKET wakeup_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
connect(wakeup_socket, (sockaddr*)&server_addr, sizeof(server_addr));
closesocket(wakeup_socket);
}
更好的方法是简单地将您的侦听套接字置于非阻塞模式,并在循环中使用select()
超时,以便它可以定期唤醒自己:
while (!shutting_down)
{
fd_set rds;
FD_ZERO(&rds);
FD_SET(listening_socket, &rds);
timeval t;
t.tv_sec = 5;
t.tv_usec = 0;
if ((select(0, &rds, NULL, NULL, &t) > 0) && (!shutting_down))
{
auto new_connected_socket = accept(listening_socket,...);
std::thread(fn_new_connection_handler, new_connected_socket);
}
}
更好的是,使用WSACreateEvent()
和WSAWaitForMultipleEvents()
代替select()
:
WSAEVENT hShutdownEvent = WSACreateEvent();
WSAEVENT hAcceptEvent = WSACreateEvent();
WSAEventSelect(listening_socket, hAcceptEvent, FD_ACCEPT);
while (true)
{
WSAEVENT hEvents[2];
hEvents[0] = hAcceptEvent;
hEvents[1] = hShutdownEvent;
DWORD dwRet = WSAWaitForMultipleEvents(2, hEvents, FALSE, INFINITE, FALSE);
if (dwRet == WSA_WAIT_EVENT_0)
{
auto new_connected_socket = accept(listening_socket,...);
std::thread(fn_new_connection_handler, new_connected_socket);
}
else
break;
}
WSACloseEvent(hAcceptEvent);
if (server is listening)
WSASetEvent(hShutdownEvent);