我正在用 C 编写一个 TCP 服务器,一旦侦听 fd 收到"打开的文件太多"错误,就会发现会发生一些异常情况。accept
调用不再阻塞,并始终返回 -1。
我还尝试关闭侦听 fd 并重新打开,重新绑定它,但似乎不起作用。
我的问题是为什么accept
在这种情况下一直返回-1,我应该怎么做才能阻止它并使服务器能够在任何旧客户端关闭后接受新连接?(当某些连接关闭时,插座当然能够再次正确accept
)
=== 更新:澄清 ======
出现此问题只是因为活动客户端的数量超过了打开的 fds 的限制,所以我没有在示例代码中close
任何接受的 fd,只是为了让它更快地重现。
我每次返回输出时添加时间戳accept
并将connect
频率减慢到每 2 秒一次,然后我发现实际上"打开的文件太多"错误在最近一次成功后立即发生accept
。所以我认为这是因为当达到最大 fds 时,每次调用 accept
都会立即返回,返回值为 -1。(我认为accept
仍然会阻塞,但在下一个传入connect
返回 -1。在这种情况下,accept
的行为是我自己的理论,而不是来自手册页。如果错误,请告诉我)。
因此,对于我的第二个问题,要使其停止,我认为这是一个解决方案,可以在任何连接close
之前停止调用accept
。
同时更新示例代码。感谢您的帮助。
====== 示例代码 ======
这是我测试它的方式。首先将ulimit -n
设置为较低的值(如 16),然后运行从以下 C 源编译的服务器程序;然后使用 Python 脚本创建多个连接
/* TCP server; bind :5555 */
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define BUFSIZE 1024
#define PORT 5555
void error(char const* msg)
{
perror(msg);
exit(1);
}
int listen_port(int port)
{
int parentfd; /* parent socket */
struct sockaddr_in serveraddr; /* server's addr */
int optval; /* flag value for setsockopt */
parentfd = socket(AF_INET, SOCK_STREAM, 0);
if (parentfd < 0) {
error("ERROR opening socket");
}
optval = 1;
setsockopt(parentfd, SOL_SOCKET, SO_REUSEADDR,
(const void *)&optval , sizeof(int));
bzero((char *) &serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons((unsigned short)port);
if (bind(parentfd, (struct sockaddr *) &serveraddr, sizeof(serveraddr)) < 0) {
error("ERROR on binding");
}
if (listen(parentfd, 5) < 0) {
error("ERROR on listen");
}
printf("Listen :%dn", port);
return parentfd;
}
int main(int argc, char **argv)
{
int parentfd; /* parent socket */
int childfd; /* child socket */
int clientlen; /* byte size of client's address */
struct sockaddr_in clientaddr; /* client addr */
int accept_count; /* times of accept called */
accept_count = 0;
parentfd = listen_port(PORT);
clientlen = sizeof(clientaddr);
while (1) {
childfd = accept(parentfd, (struct sockaddr *) &clientaddr, (socklen_t*) &clientlen);
printf("accept returns ; count=%d ; time=%u ; fd=%dn", accept_count++, (unsigned) time(NULL), childfd);
if (childfd < 0) {
perror("error on accept");
/* the following 2 lines try to close the listening fd and re-open it */
// close(parentfd);
// parentfd = listen_port(PORT);
// the following line let the program exit at the first error
error("--- error on accept");
}
}
}
用于创建连接的 Python 程序
import time
import socket
def connect(host, port):
s = socket.socket()
s.connect((host, port))
return s
if __name__ == '__main__':
socks = []
try:
try:
for i in xrange(100):
socks.append(connect('127.0.0.1', 5555))
print ('connect count: ' + str(i))
time.sleep(2)
except IOError as e:
print ('error: ' + str(e))
print ('stop')
while True:
time.sleep(10)
except KeyboardInterrupt:
for s in socks:
s.close()
为什么在这种情况下接受一直返回 -1
因为您的文件描述符已经用完了,就像错误消息所说的那样。
我应该怎么做才能停止它并使服务器能够在任何旧客户端关闭后接受新连接?
关闭客户端。问题不在于返回 -1 accept()
,而在于一旦完成接受的套接字,您就不会关闭它们。
关闭侦听套接字不是解决方案。这只是另一个问题。
编辑 "完成它们"是指以下几件事之一:
- 他们已经完成了你的工作,这可以通过
recv()
返回零来显示。 - 您已经完成了它们,例如在发送最终回复之后。
- 当您在发送或接收除 EAGAIN/EWILLBLOCK 之外的他们时出错时。
- 当您遇到其他一些内部致命错误,阻止您进一步处理该客户端时,例如收到无法解析的请求,或者其他一些致命的应用程序错误,导致连接或会话或整个客户端无效。
在所有这些情况下,您应该关闭接受的套接字。
EJP的答案是正确的,但它没有告诉你如何处理这种情况。您实际上要做的是对您作为接受返回获得的套接字执行一些操作。简单地呼叫关闭他们,你当然不会收到任何东西,但它会处理资源枯竭问题。要获得正确的实现,您必须做的是开始在接受的套接字上接收并继续接收,直到收到 0 字节。如果您收到 0 个字节,则表明对等方已完成使用他的套接字侧。这也是您在套接字上调用关闭并处理资源问题的触发器。
你不必停止倾听。这将阻止您的服务器处理新请求,这不是这里的问题。
我在这里实现的解决方案是查看新(接受的)fd
的值,如果该值等于或高于允许的服务器容量,则发送"忙"消息并关闭新连接。
此解决方案非常有效,可让您通知客户端服务器的状态。