我写了一个简单的客户端-服务器应用程序。当我开始测试时,我注意到当为套接字fd设置EPOLLET标志时,并非所有事件都能正确处理。
在循环中,我连接到服务器并向其发送了一些数据,为了测试,我进行了10,000次连接,从服务器端我统计了每个事件,无论是套接字描述符上的事件还是客户端的事件,并且总是根据日志服务器花费的时间比预期的要少。(大约10000次迭代(~ 9200)。我不明白这和什么有关。
我是否正确地处理了事件,或者我是否遗漏了什么?也许我的测试方法不太正确(每当套接字fd上发生什么事情时,我都会计算输出的行数)
Short compiled code
客户端c:
#include <assert.h>
#include <netdb.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#define DEF_IP "127.0.0.1"
#define DEF_PORT "3940"
#define DEF_MESSAGES_COUNT 10000
typedef struct addrinfo addrinfo;
static int conn_init();
int main() {
int i, fd_socket;
ssize_t bytes_send;
uint64_t num;
for (i = 0; i < DEF_MESSAGES_COUNT; i++) {
fd_socket = conn_init();
num = htobe64(i);
bytes_send = send(fd_socket, &num, sizeof(num), 0);
assert(bytes_send > -1);
printf("I! Client: sent [%d], bytes: [%ld]\n", i, bytes_send);
close(fd_socket);
}
printf("sended: %d messages ([0] - [%d])\n", i, i - 1);
return 0;
}
static int conn_init() {
addrinfo info_hints = {0};
addrinfo *info_server;
int fd_socket, ret;
info_hints.ai_family = AF_UNSPEC;
info_hints.ai_socktype = SOCK_STREAM;
ret = getaddrinfo(DEF_IP, DEF_PORT, &info_hints, &info_server);
assert(ret == 0);
fd_socket = socket(info_server->ai_family, info_server->ai_socktype, info_server->ai_protocol);
assert(fd_socket > -1);
ret = connect(fd_socket, info_server->ai_addr, info_server->ai_addrlen);
assert(ret == 0);
freeaddrinfo(info_server);
return fd_socket;
}
server.c:
#include <assert.h>
#include <fcntl.h>
#include <netdb.h>
#include <stdbool.h>
#include <stdio.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
typedef struct addrinfo addrinfo;
typedef struct epoll_event epoll_event;
typedef struct sockaddr_storage sockaddr_storage;
typedef struct sockaddr sockaddr;
#define DEF_IP "127.0.0.1"
#define DEF_PORT "3940"
#define DEF_MAX_EVENTS 1000
#define DEF_BACKLOG 1000
static int conn_handle_socket(int fd_socket);
static void epoll_add(int fd_epoll, int fd, uint32_t flag);
int main() {
int fd_socket, fd_connect, fd_epoll, ret;
epoll_event events[DEF_MAX_EVENTS];
addrinfo info_hints = {0};
addrinfo * info_server;
info_hints.ai_family = AF_UNSPEC; // IPv4 или IPv6
info_hints.ai_socktype = SOCK_STREAM; // TCP
info_hints.ai_flags = AI_PASSIVE; // use my IP
ret = getaddrinfo(DEF_IP, DEF_PORT, &info_hints, &info_server);
assert(ret == 0);
fd_socket = socket(info_server->ai_family, info_server->ai_socktype, info_server->ai_protocol);
assert(fd_socket > -1);
ret = setsockopt(fd_socket, SOL_SOCKET, SO_REUSEADDR, (const char *)&(int){1}, sizeof(int));
assert(ret == 0);
ret = bind(fd_socket, info_server->ai_addr, info_server->ai_addrlen);
assert(ret == 0);
freeaddrinfo(info_server);
ret = listen(fd_socket, DEF_BACKLOG);
assert(ret == 0);
// init epoll
fd_epoll = epoll_create1(0);
assert(fd_epoll > 0);
// monitor socket fd
epoll_add(fd_epoll, fd_socket, EPOLLET);
printf("I! Server: is ready\n");
while (1) {
int num_events = epoll_wait(fd_epoll, events, DEF_MAX_EVENTS, -1);
assert(num_events > -1);
for (int i = 0; i < num_events; i++) {
int fd_tmp = events[i].data.fd;
if (fd_tmp == fd_socket) {
printf("I! Server: SOCKET FD\n");
fflush(stdout);
// accept client
fd_connect = conn_handle_socket(fd_socket);
epoll_add(fd_epoll, fd_connect, EPOLLONESHOT);
} else if (events[i].events & EPOLLIN) {
printf("I! Server: CLIENT FD\n");
fflush(stdout);
// 'processing' clients
assert(epoll_ctl(fd_epoll, EPOLL_CTL_DEL, fd_tmp, NULL) == 0);
close(fd_tmp);
}
}
}
assert(epoll_ctl(fd_epoll, EPOLL_CTL_DEL, fd_socket, NULL) == 0);
assert(epoll_ctl(fd_epoll, EPOLL_CTL_DEL, STDIN_FILENO, NULL) == 0);
close(fd_socket);
close(fd_epoll);
return 0;
}
static int conn_handle_socket(int fd_socket) {
int fd_connect;
sockaddr_storage addr_connected;
socklen_t sin_size;
sin_size = sizeof(addr_connected);
fd_connect = accept(fd_socket, (sockaddr *)&addr_connected, &sin_size);
assert(fd_connect > -1); // ignore EAGAIN || EWOULDBLOCK
return fd_connect;
}
static void epoll_add(int fd_epoll, int fd, uint32_t flag) {
int flags, ret;
epoll_event ev = {0};
ev.events = EPOLLIN;
if (flag == EPOLLET || flag == EPOLLONESHOT) {
ev.events |= flag;
}
ev.data.fd = fd;
ret = epoll_ctl(fd_epoll, EPOLL_CTL_ADD, fd, &ev);
assert(ret == 0);
flags = fcntl(fd, F_GETFL, 0);
assert(flags > -1);
flags |= O_NONBLOCK;
ret = fcntl(fd, F_SETFL, flags);
assert(ret > -1);
}
1条答案
按热度按时间btxsgosb1#
在ET(边缘触发)模式下,如果同一fd上生成多个事件,则仅触发一次。
对于每个epoll_wait返回,它只接受一个客户端。其他客户端仍然在listenfd的backlog中。
有两种解决方案:
1.使用LT(级别触发)。因为在LT模式下,epoll_wait将保持返回,只要FD有传入的事件要处理。
1.根据epoll的man(7)
使用EPOLLET标志的应用程序应该使用非阻塞文件描述符,以避免阻塞读取或写入使处理多个文件描述符的任务陷入饥饿。建议使用epoll作为边缘触发(EPOLLET)接口的方法如下:a)使用非阻塞文件描述符;以及B)通过仅在read(2)或write(2)返回EAGAIN之后等待事件。并且接受也是一种读取。