select,poll,epoll的探索

lkpalu Lv3

select

今天写的这三个主要是为了解决普通网络编程中一个连接创建一个线程处理的方式,这种方式不适合高并发的情况,select可以在单线程中处理多个请求

缺点:

1.参数较多,有五个参数,都需要单独进行管理

2.每次吧待检测io集合,复制进内核,影响性能

3.对io的数量有限制,最大1024

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// select
// int nearby = select(maxfd,rset,wset,eset,time_out);
// maxfd表示最大的连接序号,用于内部遍历判断该连接是否可读可写
// rset为可读集合,内部为bit数组
// wset为可写集合
// eset为错误集合
// timeout为超时时间
// 该函数在请求可读或可写时都会返回,若不可读可写,超时时间后返回
int maxfd = sockfd;
fd_set rset, rfds;
FD_ZERO(&rfds);
FD_SET(sockfd, &rfds);

// int nready = select(maxfd + 1, rset, NULL, NULL, NULL);

while (1)
{
rset = rfds;
int nready = select(maxfd + 1, &rset, NULL, NULL, NULL);
if (FD_ISSET(sockfd, &rset))
{
struct sockaddr_in cltaddr;
memset(&cltaddr, 0, sizeof(struct sockaddr_in));
socklen_t len = sizeof(struct sockaddr_in);
int clientfd = accept(sockfd, (struct sockaddr *)&cltaddr, &len); // 接收阻塞
maxfd = clientfd;
FD_SET(clientfd, &rfds);
}

for (int i = sockfd + 1; i <= maxfd; i++)
{
if (FD_ISSET(i, &rset))
{
char buf[128] = {0};

int count = recv(i, buf, 128, 0);
if (count == 0)
{
FD_CLR(i, &rfds);
close(i);
break;
}

send(i, buf, count, 0);
printf("clientfd %d,count %d,buffer:%s", i, count, buf);
}
}
}

poll

解决了select参数过多的问题,简化了函数参数,但因为底层仍是select,依然没有解决每次都要复制以及对io有数量限制的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// int nready = poll(fds, maxfd+1, -1);
// 简化了select的参数
// 底层还是select,没用改善select多次复制的缺点
struct pollfd fds[1024] = {0}; // 保持和select最大连接数一致

fds[sockfd].fd = sockfd;
fds[sockfd].events = POLLIN;

int maxfd = sockfd; // 非必须,缩短循环次数

while (1)
{
int nready = poll(fds, maxfd + 1, -1);

if (fds[sockfd].revents & POLLIN)
{
struct sockaddr_in cltaddr;
memset(&cltaddr, 0, sizeof(struct sockaddr_in));
socklen_t len = sizeof(struct sockaddr_in);
int clientfd = accept(sockfd, (struct sockaddr *)&cltaddr, &len); // 接收阻塞
maxfd = clientfd;
fds[clientfd].fd = clientfd;
fds[clientfd].events = POLLIN;
}

for (int i = sockfd + 1; i <= maxfd; i++)
{
if (fds[i].revents & POLLIN)
{
char buf[128] = {0};

int count = recv(i, buf, 128, 0);
if (count == 0)
{
fds[i].fd = -1;
fds[i].events = 0;
close(i);
break;
}

send(i, buf, count, 0);
printf("clientfd %d,count %d,buffer:%s", i, count, buf);
}
}
}

epoll

相较于select和poll,epoll是事件驱动,支持两种触发方式,LT(水平触发),ET(边沿触发),epoll对与io只需要复制一次,不需要每次都进行复制,大大提高了性能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
int epfd = epoll_create(1);// 函数中的size是为了兼容旧的接口,大于一即可,内部已经不再使用传入的size,返回一个用于epoll的句柄

struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = sockfd;

epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);//kv绑定,一个fd绑定一个event

struct epoll_event events[1024] = {0};

while (1)
{
int nready = epoll_wait(epfd, events, 1024, -1);
for (int i = 0; i < nready; i++)
{
int connfd = events[i].data.fd;

if (sockfd == connfd)
{
struct sockaddr_in cltaddr;
memset(&cltaddr, 0, sizeof(struct sockaddr_in));
socklen_t len = sizeof(struct sockaddr_in);
int clientfd = accept(sockfd, (struct sockaddr *)&cltaddr, &len); // 接收阻塞
ev.events = EPOLLIN | EPOLLET;//默认为水平触发,不需要 | EPOOLET
ev.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);
}
else if (events[i].events & EPOLLIN)
{
char buf[128] = {0};

int count = recv(connfd, buf, 128, 0);
if (count == 0)
{
epoll_ctl(epfd, EPOLL_CTL_DEL, connfd, NULL);
close(i);
break;
}

send(connfd, buf, count, 0);
printf("clientfd %d,count %d,buffer:%s", connfd, count, buf);
}
}
}

水平触发:读取多次,直到读不到数据

边沿触发:只读取一次,下次发送时,从原先数据位读取后一位开始读取数据

  • 标题: select,poll,epoll的探索
  • 作者: lkpalu
  • 创建于 : 2024-12-06 16:02:14
  • 更新于 : 2024-12-06 16:15:00
  • 链接: https://redefine.ohevan.com/2024/12/06/select-poll-epoll的探索/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论
目录
select,poll,epoll的探索