(1)书里提出了疑问,epoll 函数的工作方式,区分为水平触发与边缘触发 :
(2) 11 1 5-5 伊圣雨老师的 epoll 教学范例:回声客户端代码与错误处理函数 error_handling () :
++ 源代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 100
void error_handling(char * str)//此函具有换行功能
{ // int fputs(const char *str,FILE *stream);
fputs(str,stderr); // fputs()写入字符串到文件中
fputc('\n',stderr);// 但不会主动添加换行符。
exit(1); // int fputc(int char,FILE *stream);
}
int main(int argc,char * argv[])
{ // 回声客户端,三个参数,argc = 3
int sock, str_len; char message[BUF_SIZE];
struct sockaddr_in serv_adr;
if(argc != 3) { printf("参数不是3个\n");exit(1); }
sock = socket(PF_INET,SOCK_STREAM,0);
if(sock == -1)
error_handling("创建套接字socket() 失败\n");
memset(&serv_adr,0,sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
// serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
// inet_addr() 的语义不明,不好
// 处理文本地址,只需使用 inet_pton() 与 inet_ntop() 即可。
// int inet_pton(int af, const char *src, void *dst);
inet_pton(AF_INET, argv[1], &serv_adr.sin_addr.s_addr);
serv_adr.sin_port = htons(atoi(argv[2]));
if(connect(sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)
error_handling("connect() 失败");
else
puts("客户端套接字连接至服务器成功\n");
while(1) // 函 fputs() 不会自动添加换行符
{ // int fputs(const char *str,FILE *stream);
fputs("Input message(Q to quit):", stdout);
fgets(message, BUF_SIZE, stdin);
// char *fgets(char *str, int n, FILE *stream);
// 函 fgets 会保留换行符在字符串中。可手动去除
if( !strcmp(message,"Q\n") || !strcmp(message,"q\n") )
break;
write(sock, message, strlen(message));
str_len = read(sock, message, BUF_SIZE - 1);
message[str_len] = 0; // read()不会主动添加空字符。
printf("服务器端回响过来的信息:%s",message);
}
close(sock);
return 0;
}
(3) 11 2 5-5 伊圣雨老师的 epoll 教学范例:回声服务器端代码:使用 epoll ,大缓存,水平触发:
++
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#define BUF_SIZE 100
#define EPOLL_SIZE 50
void error_handling(char * str)//此函具有换行功能
{ // int fputs(const char *str,FILE *stream);
fputs(str,stderr);
fputc('\n',stderr);// int fputc(int char,FILE *stream);
exit(1);
}
int main(int argc,char * argv[])
{ // 最基本版本的 epoll() 实现的回声服务器端,argc = 2
int serv_sock, clnt_sock, str_len, i, epfd, event_cnt;
struct sockaddr_in serv_adr,clnt_adr;
socklen_t adr_sz; char buf[BUF_SIZE]; // 100
struct epoll_event event, * ep_events;
if(argc != 2) { printf("参数不是2个\n");exit(1); }
serv_sock = socket(PF_INET,SOCK_STREAM,0);
memset(&serv_adr,0,sizeof(serv_adr));
serv_adr.sin_family = AF_INET; // 协议
serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);//IP地址
serv_adr.sin_port = htons(atoi(argv[1])); //端口号
if(bind(serv_sock,(struct sockaddr *)&serv_adr, sizeof(serv_adr))==-1)
error_handling("bind() error");
// 不必再处理字符串换行问题
if(listen(serv_sock,5)==-1) // 绑定,开启监听
error_handling("listen() error");
epfd = epoll_create(EPOLL_SIZE); // EPOLL_SIZE = 50
event.events = EPOLLIN;
event.data.fd = serv_sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);
ep_events = malloc(sizeof(struct epoll_event) * EPOLL_SIZE);
while (1) // 此循环在正常情况下是不会退出的。
{ event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
if(-1 == event_cnt) { // 防止少写 ==
puts("epoll_wait() 出错"); // 结束循环,进程退出
break; //puts() 会自动换行
}
for(i = 0 ; i < event_cnt ; i++)
{ if(ep_events[i].data.fd == serv_sock)//监听套接字
{ adr_sz = sizeof(clnt_adr);
clnt_sock = accept(serv_sock,(struct sockaddr *)&clnt_adr,&adr_sz);
event.events = EPOLLIN;
event.data.fd = clnt_sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
printf("创建了通信套接字 id: %d\n", clnt_sock);
} else { // read() 是不会为接收的字符串添加空字符 '\0' 的
str_len = read(ep_events[i].data.fd, buf, BUF_SIZE);
if(0 == str_len) // FIN 报文
{ epoll_ctl(epfd, EPOLL_CTL_DEL,ep_events[i].data.fd,NULL);
close(ep_events[i].data.fd);
printf("关闭了通信套接字 id: %d\n",ep_events[i].data.fd);
}
else write(ep_events[i].data.fd, buf, str_len);
}
} // for(...)
} // while(...)
close(serv_sock); // 如此,监听套接字
close(epfd); //serv_sock 会被关闭两次。
return 0;
}
++ 11 3 5-5 伊圣雨老师的 epoll 教学范例:图 2 的回声服务器的测试效果 :
(4) 11 4 5-5 伊圣雨老师的 epoll 教学范例:回声服务器端代码:使用 epoll ,小缓存,验证默认的水平触发模式:
++
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#define BUF_SIZE 3
#define EPOLL_SIZE 50
void error_handling(char * str)//此函具有换行功能
{ // int fputs(const char *str,FILE *stream);
fputs(str,stderr);
fputc('\n',stderr);// int fputc(int char,FILE *stream);
exit(1);
}
int main(int argc,char * argv[])
{ //验证 epoll 的默认的水平触发模式的回声服务器端,argc = 2
int serv_sock, clnt_sock, str_len, i, epfd, event_cnt;
struct sockaddr_in serv_adr,clnt_adr;
socklen_t adr_sz; char buf[BUF_SIZE]; // 100
struct epoll_event event, * ep_events;
if(argc != 2) { printf("参数不是2个\n");exit(1); }
serv_sock = socket(PF_INET,SOCK_STREAM,0);
memset(&serv_adr,0,sizeof(serv_adr));
serv_adr.sin_family = AF_INET; // 协议
serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);//IP地址
serv_adr.sin_port = htons(atoi(argv[1])); //端口号
if(bind(serv_sock,(struct sockaddr *)&serv_adr, sizeof(serv_adr))==-1)
error_handling("bind() error");
// 不必再处理字符串换行问题
if(listen(serv_sock,5)==-1) // 绑定,开启监听
error_handling("listen() error");
epfd = epoll_create(EPOLL_SIZE); // EPOLL_SIZE = 50
event.events = EPOLLIN;
event.data.fd = serv_sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);
ep_events = malloc(sizeof(struct epoll_event) * EPOLL_SIZE);
while (1) // 此循环在正常情况下是不会退出的。
{ event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
if(-1 == event_cnt) { // 防止少写 ==
puts("epoll_wait() 出错"); // 结束循环,进程退出
break; //puts() 会自动换行
} //增加 puts() 行统计 epoll_wait() 的返回次数,其余部分不变。
puts("从 epoll_wait() 返回了");
for(i = 0 ; i < event_cnt ; i++)
{ if(ep_events[i].data.fd == serv_sock)//监听套接字
{ adr_sz = sizeof(clnt_adr);
clnt_sock = accept(serv_sock,(struct sockaddr *)&clnt_adr,&adr_sz);
event.events = EPOLLIN;
event.data.fd = clnt_sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
printf("创建了通信套接字 id: %d\n", clnt_sock);
} else { // read() 是不会为接收的字符串添加空字符 '\0' 的
str_len = read(ep_events[i].data.fd, buf, BUF_SIZE);
if(0 == str_len) // FIN 报文
{ epoll_ctl(epfd, EPOLL_CTL_DEL,ep_events[i].data.fd,NULL);
close(ep_events[i].data.fd);
printf("关闭了通信套接字 id: %d\n",ep_events[i].data.fd);
} else write(ep_events[i].data.fd, buf, str_len);
}
} // for(...)
} // while(...)
close(serv_sock); // 如此,监听套接字
close(epfd); //serv_sock 会被关闭两次。
return 0;
}
++ 11 5 5-5 伊圣雨老师的 epoll 教学范例:图 4 的回声服务器的默认水平触发模式的测试效果:
(5) 11 6 5-5 伊圣雨老师的 epoll 教学范例:回声服务器端代码:使用 epoll ,小缓存,通信套接字使用非阻塞的边缘触发模式:
++
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <fcntl.h> //增加这俩头文件
#include <errno.h>
#define BUF_SIZE 3
#define EPOLL_SIZE 50
void error_handling(char * str)//此函具有换行功能
{ // int fputs(const char *str,FILE *stream);
fputs(str,stderr);
fputc('\n',stderr);// int fputc(int char,FILE *stream);
exit(1);
}
void setnonblockingmode(int fd) //非阻塞
{ int flag = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flag | O_NONBLOCK); }
int main(int argc,char * argv[])
{ //验证 epoll 的边缘触发模式的回声服务器端,argc = 2
int serv_sock, clnt_sock, str_len, i, epfd, event_cnt;
struct sockaddr_in serv_adr,clnt_adr;
socklen_t adr_sz; char buf[BUF_SIZE]; // 100
struct epoll_event event, * ep_events;
if(argc != 2) { printf("参数不是2个\n");exit(1); }
serv_sock = socket(PF_INET,SOCK_STREAM,0);
setnonblockingmode(serv_sock);//设置监听套接字为非阻塞
memset(&serv_adr,0,sizeof(serv_adr));
serv_adr.sin_family = AF_INET; // 协议
serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);//IP地址
serv_adr.sin_port = htons(atoi(argv[1])); //端口号
if(bind(serv_sock,(struct sockaddr *)&serv_adr,
sizeof(serv_adr))==-1)
error_handling("bind() error");
// 不必再处理字符串换行问题
if(listen(serv_sock,5)==-1) // 绑定,开启监听
error_handling("listen() error");
epfd = epoll_create(EPOLL_SIZE); // EPOLL_SIZE = 50
event.events = EPOLLIN; // 监听套接字仍为水平触发模式
event.data.fd = serv_sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);
ep_events = malloc(sizeof(struct epoll_event) * EPOLL_SIZE);
while (1) // 此循环在正常情况下是不会退出的。
{ event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
if(-1 == event_cnt) { // 防止少写 ==
puts("epoll_wait() 出错"); // 结束循环,进程退出
break; //puts() 会自动换行
}
puts("从 epoll_wait() 返回了");//统计epoll_wait()的返回次数
for(i = 0 ; i < event_cnt ; i++) //依次处理所有发生了事件的套接字
{ if(ep_events[i].data.fd == serv_sock)//监听套接字
{ adr_sz = sizeof(clnt_adr);
clnt_sock = accept(serv_sock,(struct sockaddr *)&clnt_adr,&adr_sz);
setnonblockingmode(clnt_sock);
event.events = EPOLLIN | EPOLLET; //通信套接字边缘触发
event.data.fd = clnt_sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
printf("创建了通信套接字 id: %d\n", clnt_sock);
} else while(1) { // 从通信套接字读取所有数据
str_len = read(ep_events[i].data.fd, buf, BUF_SIZE);
if(0 == str_len) // FIN 报文
{ epoll_ctl(epfd, EPOLL_CTL_DEL,ep_events[i].data.fd,NULL);
close(ep_events[i].data.fd);
printf("关闭了通信套接字 id: %d\n",ep_events[i].data.fd);
break; // 下面的是读完了接收缓存中的数据,返回-1,并设置errno
} else if(str_len < 0){ if(EAGAIN == errno) break; } //跳出内循环
else write(ep_events[i].data.fd, buf, str_len);
} //内层 while(...)
} // for(...)
} // while(...)
close(serv_sock); // 如此,监听套接字
close(epfd); //serv_sock 会被关闭两次。
return 0;
}
++11 7 5-5 伊圣雨老师的 epoll 教学范例:图 6 的回声服务器的非阻塞边缘触发模式的测试效果:
(6)
谢谢