- 水平觸發模式 -- 默認就是這種模式(如上一篇所寫)
- 邊沿阻塞觸發模式
- 邊沿非阻塞工作模式 -- 效率最高
先來個需求吧:
針對一個客戶端(進程間管道通信)對應一個服務器來說
如果客戶端發送的信息有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;
}