- 水平触发模式 -- 默认就是这种模式(如上一篇所写)
- 边沿阻塞触发模式
- 边沿非阻塞工作模式 -- 效率最高
先来个需求吧:
针对一个客户端(进程间管道通信)对应一个服务器来说
如果客户端发送的信息有100字节, 而服务器每次接收只接收50字节, 那么剩下的50字节怎么处理?
分析:
- 默认执行流程: 对应的缓冲区存放了发送来的100字节,系统epoll监听到了对应的文件描述符的变化, 此时服务端去读数据, 但是只读了50字节, 那么缓冲区中就还留有50字节
此时有两种说法: 事实是第二种
1.系统为提高效率, 不会再去调用epoll_wait函数, 那么50字节数据就只能等下次客户端发送信息的时候接收(为了提高效率, 减少该函数的调用次数)
2.系统会再次调用epoll_wait函数, 将数据读出来.(后面会附带上代码) - 边沿阻塞触发模式: 会想上述的第1种情况那样, 但是会导致缓冲区里每次都残留数据, 并且越来越多...
- 边沿非阻塞(O_NONBLOCK)触发模式: 该效率最高, 主要是因为将客户端对应的那个文件描述符即缓冲区(管道)设置成非阻塞模式, 此时接受(读取)信息的时候就需要循环去读取, 当read/recv返回值为0时表示读取完毕.再加上边沿模式(只调用一次epoll_wait函数)所以效率高
设置非阻塞:
1.open的时候设置参数;
2.fcntl设置
//文件打开之后修改文件属性 先获胜设置的属性 flags
//获取flags:
int flags = fcntl(fd, F_GETFL);
//设置flags:
flags = flags | O_NONBLOCK;
fcntl(fd, F_SETFL, flags);
对应的三种模式的代码:
- 利用管道-父子进程之间通信实现前两种模式:
切换在代码中注释的地方, 输出的格式如上文描述那样, 这里模拟的是10字节和5字节
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <error.h>
int main(int argc, char *argv[]) {
char buf[10];
//使用管道实现,管道创建需要一个数组,存放的一个对应读,一个对应写
int pfd[2];
//创建匿名管道
pipe(pfd);
//创建子进程
pid_t pid = fork();
if(pid == 0) { //子进程
//不需要读操作,关闭读的文件描述符,确保管道单项传输数据
close(pfd[0]);
while(1) {
int i = 0;
for(i = 0; i < 10/2; i++) {
buf[i] = 'a';
}
buf[i-1] = '\n';
for(; i < 10; i++) {
buf[i] = 'b';
}
buf[i-1] = '\n';
//此时数组中存放的是aaaa\nbbbb\n
//一次发送10字节
write(pfd[1], buf, sizeof(buf));
sleep(3);
}
close(pfd[1]);
} else if(pid > 0) {//父进程
close(pfd[1]);
//创建epoll模型,指向根节点,句柄
int efd = epoll_create(10);
//将要监听的挂载到根节点上
struct epoll_event event;
//设置边沿触发如下:
event.events = EPOLLIN | EPOLLET;
/*
* //默认就是水平触发
* event.events = EPOLLIN;
*/
event.data.fd = pfd[0];//写端
epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event);
struct epoll_event resevents[10];
char readbuf[5];
while(1) {
int res = epoll_wait(efd, resevents, 10, -1);
printf("res:%d\n", res);
if(resevents[0].data.fd == pfd[0]) {
int len = read(pfd[0], readbuf, 5);//一次读5字节
write(STDOUT_FILENO, readbuf, len);
}
}
close(pfd[0]);
close(efd);
} else {
perror("fork error");
exit(1);
}
return 0;
}
- 利用c/s模型实现的边沿阻塞触发, 这里做的是只对应一个客户端进行监听
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>
#define MAXLINE 10
#define SERV_PORT 9000
int main(void) {
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
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));
listen(listenfd, 20);
struct epoll_event event;
struct epoll_event resevent[10];
int res, len;
printf("Accepting 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));
// 创建红黑树根节点
int efd = epoll_create(10);
// 检测的事件设置
#if 0
/* ET 边沿触发 */
event.events = EPOLLIN | EPOLLET;
#else
/* 默认 LT 水平触发 */
event.events = EPOLLIN;
#endif
// 需要检测的文件描述符
event.data.fd = connfd;
epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);
while (1) {
res = epoll_wait(efd, resevent, 10, -1);
printf("========res %d\n", res);
if (resevent[0].data.fd == connfd) {
len = read(connfd, buf, MAXLINE/2);
write(STDOUT_FILENO, buf, len);
}
}
return 0;
}
- 同2一样只对客户端进行监听, 不过这里要注意的是不能再根据read的返回值去判断是客户端断开了连接还是读取失败还是读到的内容, 需要另外的思路去设计程序, 比如利用data里面的void *指针来做, 存放一个时间, 如果长时间没有联系, 则断开连接...或者利用心跳包
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>
#define MAXLINE 10
#define SERV_PORT 8000
int main(void) {
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
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));
listen(listenfd, 20);
///////////////////////////////////////////////////////////////////////
struct epoll_event event;
struct epoll_event resevent[10];
int efd = epoll_create(10);
//event.events = EPOLLIN;
printf("Accepting 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));
/* 修改connfd为非阻塞读 */
int flag = fcntl(connfd, F_GETFL);
flag |= O_NONBLOCK;
fcntl(connfd, F_SETFL, flag);
/* ET 边沿触发,默认是水平触发 */
event.events = EPOLLIN | EPOLLET;
event.data.fd = connfd;
//将connfd加入监听红黑树
epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);
while (1) {
int len = 0;
printf("epoll_wait begin\n");
//最多10个, 阻塞监听
int res = epoll_wait(efd, resevent, 10, -1);
printf("epoll_wait end res %d\n", res);
if (resevent[0].data.fd == connfd) {
// 非阻塞读, 轮询
// epoll_wait 触发一次,剩余数据循环读取
while ((len = read(connfd, buf, MAXLINE/2)) >0 ) {
write(STDOUT_FILENO, buf, len);
}
}
}
return 0;
}