Linux C socket在使用epoll时如何防止2个forked进程接受同一个连接?
Linux C socket how to prevent 2 forked process from accepting the same connection when using epoll?
for (int t = 0; t < physicalCoreCount; t++) {
int pid = fork();
if (pid==0) {
// setting up epoll
epoll_fd = epoll_create1(0);
event.data.fd = listenSocketfd;
event.events = EPOLLIN | EPOLLET;
ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listenSocketfd, &event);
events = (epoll_event*)calloc(LISTENQ, sizeof(event));
//*****/
while (1) {
int ndfs = epoll_wait(epoll_fd, events, curfdCount, -1);
if (ndfs==-1) {
continue;
}
for (int i=0; i < ndfs; i++) {
if (events[i].data.fd == listenSocketfd) { // original listener
int new_connfd = accept(events[i].data.fd, (sockaddr*)&clientaddr, &clientlen);
if (new_connfd==-1) {
if (errno==EAGAIN || errno==EWOULDBLOCK) {
continue;
}
else exitPerrorLog("accept()");
}
set_non_block(new_connfd);
event.events = EPOLLIN | EPOLLET;
event.data.fd = new_connfd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_connfd, &event) < 0)
exitPerrorLog("epoll_ctl");
clientaddrOfFd[new_connfd] = new sockaddr_in();
memcpy(clientaddrOfFd[new_connfd], &clientaddr, sizeof(clientaddr));
curfdCount++;
}
else {
process(events[i].data.fd, clientaddrOfFd[events[i].data.fd]);
//epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, &event);
if (curfdCount > 10000) curfdCount = 10000; //curfdCount--;
}
}
}
}
}
问题出现是因为我正在尝试实现持久连接(响应后不关闭)。但是,child 0 可以 accept() 一个套接字文件描述符 X,处理它,但后来 child 1 可以 accept() 相同的文件描述符。由于我没有关闭 child 0 上的连接(实现 HTTP1.1 keep-alive),现在 2 children 是 reading/writing 到同一个文件描述符。
有什么方法可以避免这个问题?谢谢。
编辑: 重要更新:所以主要问题是我认为 2 个相同的 FD 意味着它是相同的连接,我想避免这种情况,因为它会导致 2 children reading/writing 同理。如果这种情况没有发生(read/write 重叠),那么我认为问题就解决了。有人可以确认吗?
您正在创建多个 epoll 实例并在每个实例的侦听套接字上注册 edge-triggered 事件。自然地,当一个新的连接可以接受时,你会从每个连接中得到一个事件。但是,只有一个进程可以成功接受每个连接。正如评论中所观察到的,两个不同的 children 可能会接受在各自进程中分配了相同文件描述符编号的连接,但这并不意味着它们引用相同的套接字。
您有多种选择,但其中突出的是:
使用单个 epoll 实例,由所有进程共享。您可以通过让 parent 在分叉任何 children 之前创建它来自动获得它。在这种情况下,只有一个 child 会收到每个 edge-triggered 事件。当然,如果 children 打算注册只应该由他们接收的事件,那么这将不会很好地工作。
只接受(没有双关语意)多个进程将在连接可用时接收事件,并处理它。这似乎就是你现在正在做的(通过忽略 accept()
中的 EAGAIN
和 EWOULDBLOCK
错误),我看不出你不应该继续这样做的特别原因。
for (int t = 0; t < physicalCoreCount; t++) {
int pid = fork();
if (pid==0) {
// setting up epoll
epoll_fd = epoll_create1(0);
event.data.fd = listenSocketfd;
event.events = EPOLLIN | EPOLLET;
ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listenSocketfd, &event);
events = (epoll_event*)calloc(LISTENQ, sizeof(event));
//*****/
while (1) {
int ndfs = epoll_wait(epoll_fd, events, curfdCount, -1);
if (ndfs==-1) {
continue;
}
for (int i=0; i < ndfs; i++) {
if (events[i].data.fd == listenSocketfd) { // original listener
int new_connfd = accept(events[i].data.fd, (sockaddr*)&clientaddr, &clientlen);
if (new_connfd==-1) {
if (errno==EAGAIN || errno==EWOULDBLOCK) {
continue;
}
else exitPerrorLog("accept()");
}
set_non_block(new_connfd);
event.events = EPOLLIN | EPOLLET;
event.data.fd = new_connfd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_connfd, &event) < 0)
exitPerrorLog("epoll_ctl");
clientaddrOfFd[new_connfd] = new sockaddr_in();
memcpy(clientaddrOfFd[new_connfd], &clientaddr, sizeof(clientaddr));
curfdCount++;
}
else {
process(events[i].data.fd, clientaddrOfFd[events[i].data.fd]);
//epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, &event);
if (curfdCount > 10000) curfdCount = 10000; //curfdCount--;
}
}
}
}
}
问题出现是因为我正在尝试实现持久连接(响应后不关闭)。但是,child 0 可以 accept() 一个套接字文件描述符 X,处理它,但后来 child 1 可以 accept() 相同的文件描述符。由于我没有关闭 child 0 上的连接(实现 HTTP1.1 keep-alive),现在 2 children 是 reading/writing 到同一个文件描述符。
有什么方法可以避免这个问题?谢谢。
编辑: 重要更新:所以主要问题是我认为 2 个相同的 FD 意味着它是相同的连接,我想避免这种情况,因为它会导致 2 children reading/writing 同理。如果这种情况没有发生(read/write 重叠),那么我认为问题就解决了。有人可以确认吗?
您正在创建多个 epoll 实例并在每个实例的侦听套接字上注册 edge-triggered 事件。自然地,当一个新的连接可以接受时,你会从每个连接中得到一个事件。但是,只有一个进程可以成功接受每个连接。正如评论中所观察到的,两个不同的 children 可能会接受在各自进程中分配了相同文件描述符编号的连接,但这并不意味着它们引用相同的套接字。
您有多种选择,但其中突出的是:
使用单个 epoll 实例,由所有进程共享。您可以通过让 parent 在分叉任何 children 之前创建它来自动获得它。在这种情况下,只有一个 child 会收到每个 edge-triggered 事件。当然,如果 children 打算注册只应该由他们接收的事件,那么这将不会很好地工作。
只接受(没有双关语意)多个进程将在连接可用时接收事件,并处理它。这似乎就是你现在正在做的(通过忽略
accept()
中的EAGAIN
和EWOULDBLOCK
错误),我看不出你不应该继续这样做的特别原因。