
设置完成后就跳出循环,或者遍历到 1024 后跳出循环。如果遍历到 1024 会报错,没有便利到 1024 就会在刚刚修改 fd 的位置,再设置 events 等于 POLLIN 就完成了
for 循环是用来处理 cfd

上来先做一个异常的处理,保证代码健壮性
if(Client[].revents & POLLIN) 为真,表示读事件满足了,就判断读的返回值
read 的返回值有以下这些

如果返回值等于 -1,其实应该依次处理这些内容,但是函数中只对 ECONNRESET 这种情况进行了处理,其他的直接打印出错
如果返回值等于 0,就停用对应的文件描述符
如果返回值大于 0,就说明读到了数据,就进行处理
2、代码(代码有BUG,后面有机会再改)
void perr_exit(const char* str)
int i, j, maxi, listenfd, connfd, sockfd, nready, opt;
char buf[MAXLINE], str[INET_ADDRSTRLEN];
struct pollfd client[OPEN_MAX];
struct sockaddr_in cliaddr, servaddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
client[0].events = POLLIN;
for (i = 1; i < OPEN_MAX; i++) {
nready = poll(client, maxi + 1, -1);
if (client[0].revents & POLLIN) {
clilen = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port));
for (i = 1; i < OPEN_MAX; i++) {
perr_exit("too many clients");
client[i].events = POLLIN;
for (i = 1; i <= maxi; i++) {
if ((sockfd = client[i].fd) < 0) {
if (client[i].revents & POLLIN) {
if ((n = read(sockfd, buf, MAXLINE)) < 0) {
if(errno == ECONNRESET) {
printf("client[%d] aborted connection\n", i);
printf("client[%d] closed connection\n", i);
for (j = 0; j < n; j++) {
buf[j] = toupper(buf[j]);

1.4 优点和缺点

二、epell 函数
2.1 简介
epoll 是 Linux 下多路复用 IO 接口 select/poll 的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统 CPU 利用率,因为它会复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核 IO 事件异步唤醒而加入 Ready 队列的描述符集合就行了
目前 epoll 是 linux 大规模并发网络程序中的热门首选模型。
epoll 除了提供 select/poll 那种 IO 事件的电平触发 (Level Triggered) 外,还提供了边沿触发 (Edge Triggered),这就使得用户空间程序有可能缓存 lO 状态,减少 epoll_wait/epoll_pwait 的调用,提高应用程序效率。
2.2 文件描述符上限
1、可以使用 cat 命令查看一个进程可以打开的 socket 描述符上限。
cat /proc/sys/fs/file-max (当前计算机所能打开的最大文件个数,受硬件影响)

ulimit -a (当前用户下的进程,默认打开文件描述符个数)

2、如有需要,可以通过修改配置文件的方式修改该上限值。
sudo vi /etc/security/limits.conf
在文件尾部写入以下配置,soft 软限制,hard 硬限制。如下图所示。
* soft nofile 65536
* hard nofile 100000

在这里修改不能超过 hard

2.3 API
前面只要一个函数就可以了,epoll 需要三个函数
第一个函数是 poll_create

第二个函数是 epoll_ctl

第三个函数是 epoll_wait

数组中存储就是连接上的内容

2.4 代码
首先代码创建一个根结点,再创建 lfd 结点,用于监听,把 lfd 插入到根结点的子节点上。根据epoll_wait 返回的数字,循环遍历数组,处理读事件。判断读事件是否是请求联立连接,如果是请求连接的,就使用 epoll_ctl 插入结点,其他的就是数据读写事件。具体代码如下
void perr_exit(const char* str)
int i, listenfd, connfd, sockfd, n;
ssize_t nready, efd, res;
char buf[MAXLINE], str[INET_ADDRSTRLEN];
struct sockaddr_in cliaddr, servaddr;
struct epoll_event tep, ep[OPEN_MAX];
listenfd = socket(AF_INET, SOCK_STREAM, 0);
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
efd = epoll_create(OPEN_MAX);
perr_exit("epoll_create error");
res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep);
perr_exit("epoll_ctl error");
nready = epoll_wait(efd, ep, OPEN_MAX, -1);
perr_exit("epoll_wait error");
for (i = 0; i < nready; i++) {
if (!(ep[i].events & EPOLLIN)) {
if (ep[i].data.fd == listenfd) {
clilen = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port));
printf("cdf %d--client %d\n", connfd, ++num);
res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep);
perr_exit("epoll_ctl error");
n = read(sockfd, buf, MAXLINE);
res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);
perr_exit("epoll_stl error");
printf("Client[%d] closed connection\n", sockfd);
perror("read n < 0 error");
res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);
for (i = 0; i < n; i++) {
buf[i] = toupper(buf[i]);
write(STDOUT_FILENO, buf, n);


三、epoll 事件模型
3.1 分类
ET模式:边沿触发
缓冲区剩余未读尽的数据不会导致 epoll_wait 返回
event.events = EPOLLIN | EPOLLET;
LT模式:水平触发 —— 默认采用模式
缓冲区剩余未读尽的数据会导致 epoll_wait 返回
event.events = EPOLLIN
3.2 查看他们之间的区别
1、ET模式每 5 秒写一次,缓冲区中还有数据,但是不会去读数据,只有等到下次有10个字符数据来的时候才会继续读缓冲区后面 5 个字符的数据

char buf[MAXLINE], ch = 'a';
for (i = 0; i < MAXLINE / 2; i++) {
for ( ; i < MAXLINE; i++) {
write(pfd[1], buf, sizeof(buf));
struct epoll_event event;
struct epoll_event resevent[10];
event.events = EPOLLIN | EPOLLET;
epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event);
res = epoll_wait(efd, resevent, 10, -1);
if (resevent[0].data.fd == pfd[0]) {
len = read(pfd[0], buf, MAXLINE / 2);
write(STDOUT_FILENO, buf, len);
2、LT模式只要缓冲区中还有数据,就回去读数据,每次读 5 个字节的数据,一次性连续读两次。

3.3 网络中的两种模式
struct sockaddr_in servaddr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
for (i = 0; i < MAXLINE / 2; i++) {
for ( ; i < MAXLINE; i++) {
write(sockfd, buf, sizeof(buf));
struct sockaddr_in servaddr, cliaddr;
char str[INET_ADDRSTRLEN];
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
struct epoll_event event;
struct epoll_event resevent[10];
event.events = EPOLLIN | EPOLLET;
printf("Aceepting connections ...\n");
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port));
epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);
res = epoll_wait(efd, resevent, 10, -1);
if (resevent[0].data.fd == connfd) {
len = read(connfd, buf, MAXLINE / 2);
write(STDOUT_FILENO, buf, len);

如下图,在 ET 模式中,也是每次客户端发送过来信息,服务端才执行一次写操作

改成 LT 模式后,每次发送消息后,服务端先打印出 5 个字符,然后服务端再次触发 epoll_wait 返回,又会再次打印剩下的 5 个字符
3.4 结论:
epoll 的 ET 模式,高效模式,但是只支持非阻塞模式。
struct epoll_event event;
event.events = EPOLLIN | EPOLLET;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &event);
int flg = fcntl(cfd, F_GETFL);
flg |= O_ NONBLOCK;
fcntl(cfd, F_SETFL, flg);
优点:
高效。突破1024文件描述符。
缺点:
不能跨平台。Linux.