暂时停止监听套接字
Temporarily stop a socket from listening
我正在用 C++ 编写服务器,使用 POSIX 套接字 api。
这是 运行 作为 GUI 应用程序的一部分,它需要能够
停止和启动服务器侦听和向客户端发送数据。
服务器的主要部分基本上如下所示(我排除了很多代码,因为其中一些与这个问题无关。)
if (listen(listener_fd, backlog) < 0) {
std::perror("listen");
exit(EXIT_FAILURE);
}
while (true) {
/* This part sets up the FD set */
FD_ZERO(&read_fds);
FD_SET(0, &read_fds); // stdin (for server commands)
FD_SET(listener_fd, &read_fds);
FD_SET(read_pipe, &read_fds);
for (auto it = client_socks.begin(); it != client_socks.end(); it++) {
FD_SET(*it, &read_fds); // listen on each of the open client-server sockets
}
max_fd = 0;
if (client_socks.size() > 0) {
max_fd = *std::max_element(client_socks.begin(), client_socks.end());
}
if (listener_fd > max_fd) max_fd = listener_fd;
if (read_pipe > max_fd) max_fd = read_pipe;
std::fill_n(in_buf, IN_BUF_LENGTH, 0);
// wait for input on stdin or any of the sockets, with a timeout of 20s
sel_ret = select(max_fd + 1, &read_fds, NULL, NULL, &read_timeout);
if (sel_ret == -1) {
std::perror("select");
exit(EXIT_FAILURE);
}
else if (sel_ret) {
if (FD_ISSET(0, &read_fds)) { /* stdin */
// send stdin to all clients
// excl. for brev.
}
else if (FD_ISSET(listener_fd, &read_fds) && serving) { /* listen(...) can accept, but only bother if the server is listening */
int newclient_sock = accept(listener_fd, (struct sockaddr*)&addr, &addrlen);
std::cout << "New client: " << newclient_sock << std::endl;
client_socks.push_back(newclient_sock);
}
else if (FD_ISSET(read_pipe, &read_fds)) { /* someone sent some data down the pipe - probably GUI info, like a button press */
// here i've got a message from the GUI, which
// is either starting or stopping the server
// excluded here for brevity
}
else if (serving) { /* one of the sockets, but only if the server is listening */
// here i've got data from a client socket
// this handles the message that they send to me
// again, excluded here for brevity
}
}
}
或者换句话说:
- 使套接字监听连接
- 为 select() 调用设置 fds
- 使用标准输入调用 select(),所有连接客户端套接字,以及获取从 GUI 发送的任何数据的管道
- 如果 stdin 处于活动状态,则将键入的数据发送到所有客户端
- 如果侦听器处于活动状态,则将此新客户端添加到客户端列表中。
- 如果数据管道处于活动状态,则根据它拥有的数据启动或停止服务器。
- 如果客户端发送了数据,则将该数据发送给所有客户端,或者如果它们已断开连接,则将它们从客户端列表中删除。
- 转到 2.
我的问题
我需要能够让服务器基本上停止,直到我告诉它重新启动。澄清一下,我所说的 stopping 是什么意思:
- 不监听任何客户端
- 无法从任何客户端接收数据
- 所有连接的客户端都已断开连接。
我试过的
我的第一个想法是用布尔值 serving
来跟踪它是否应该 运行。
在 while(true) 的每个循环开始时,我这样做:
if (serving) {
// listen(...), same as I did it originally
} else {
shutdown(listener_fd, SHUT_RD);
}
但这根本不起作用。它工作得如此糟糕,以至于很难说它做了什么,对此深表歉意。
另一个想法是使用 close(listener_fd)
调用,当然这只会让 fd 不再可用。
我考虑的另一个选择是,在接受连接之前,首先检查是否设置了 serving
。如果是,则接受连接。接收和发送数据也是如此。这种种有效,但没有通知客户我没有服务。
套接字不支持您所要求的 "temporary" 停用。您需要:
close()
listening socket,然后在准备再次启动时创建一个新的listening socket。
保持监听套接字打开并运行正常,但立即close()
接受任何新客户端。
对于已被接受的客户,您需要单独 shutdown()
+close()
他们,可以选择先向他们发送再见消息,具体取决于您的协议是否允许。
我正在用 C++ 编写服务器,使用 POSIX 套接字 api。
这是 运行 作为 GUI 应用程序的一部分,它需要能够 停止和启动服务器侦听和向客户端发送数据。
服务器的主要部分基本上如下所示(我排除了很多代码,因为其中一些与这个问题无关。)
if (listen(listener_fd, backlog) < 0) {
std::perror("listen");
exit(EXIT_FAILURE);
}
while (true) {
/* This part sets up the FD set */
FD_ZERO(&read_fds);
FD_SET(0, &read_fds); // stdin (for server commands)
FD_SET(listener_fd, &read_fds);
FD_SET(read_pipe, &read_fds);
for (auto it = client_socks.begin(); it != client_socks.end(); it++) {
FD_SET(*it, &read_fds); // listen on each of the open client-server sockets
}
max_fd = 0;
if (client_socks.size() > 0) {
max_fd = *std::max_element(client_socks.begin(), client_socks.end());
}
if (listener_fd > max_fd) max_fd = listener_fd;
if (read_pipe > max_fd) max_fd = read_pipe;
std::fill_n(in_buf, IN_BUF_LENGTH, 0);
// wait for input on stdin or any of the sockets, with a timeout of 20s
sel_ret = select(max_fd + 1, &read_fds, NULL, NULL, &read_timeout);
if (sel_ret == -1) {
std::perror("select");
exit(EXIT_FAILURE);
}
else if (sel_ret) {
if (FD_ISSET(0, &read_fds)) { /* stdin */
// send stdin to all clients
// excl. for brev.
}
else if (FD_ISSET(listener_fd, &read_fds) && serving) { /* listen(...) can accept, but only bother if the server is listening */
int newclient_sock = accept(listener_fd, (struct sockaddr*)&addr, &addrlen);
std::cout << "New client: " << newclient_sock << std::endl;
client_socks.push_back(newclient_sock);
}
else if (FD_ISSET(read_pipe, &read_fds)) { /* someone sent some data down the pipe - probably GUI info, like a button press */
// here i've got a message from the GUI, which
// is either starting or stopping the server
// excluded here for brevity
}
else if (serving) { /* one of the sockets, but only if the server is listening */
// here i've got data from a client socket
// this handles the message that they send to me
// again, excluded here for brevity
}
}
}
或者换句话说:
- 使套接字监听连接
- 为 select() 调用设置 fds
- 使用标准输入调用 select(),所有连接客户端套接字,以及获取从 GUI 发送的任何数据的管道
- 如果 stdin 处于活动状态,则将键入的数据发送到所有客户端
- 如果侦听器处于活动状态,则将此新客户端添加到客户端列表中。
- 如果数据管道处于活动状态,则根据它拥有的数据启动或停止服务器。
- 如果客户端发送了数据,则将该数据发送给所有客户端,或者如果它们已断开连接,则将它们从客户端列表中删除。
- 转到 2.
我的问题
我需要能够让服务器基本上停止,直到我告诉它重新启动。澄清一下,我所说的 stopping 是什么意思:
- 不监听任何客户端
- 无法从任何客户端接收数据
- 所有连接的客户端都已断开连接。
我试过的
我的第一个想法是用布尔值 serving
来跟踪它是否应该 运行。
在 while(true) 的每个循环开始时,我这样做:
if (serving) {
// listen(...), same as I did it originally
} else {
shutdown(listener_fd, SHUT_RD);
}
但这根本不起作用。它工作得如此糟糕,以至于很难说它做了什么,对此深表歉意。
另一个想法是使用 close(listener_fd)
调用,当然这只会让 fd 不再可用。
我考虑的另一个选择是,在接受连接之前,首先检查是否设置了 serving
。如果是,则接受连接。接收和发送数据也是如此。这种种有效,但没有通知客户我没有服务。
套接字不支持您所要求的 "temporary" 停用。您需要:
close()
listening socket,然后在准备再次启动时创建一个新的listening socket。保持监听套接字打开并运行正常,但立即
close()
接受任何新客户端。
对于已被接受的客户,您需要单独 shutdown()
+close()
他们,可以选择先向他们发送再见消息,具体取决于您的协议是否允许。