- 水平觸發(fā)模式 -- 默認(rèn)就是這種模式(如上一篇所寫(xiě))
- 邊沿阻塞觸發(fā)模式
- 邊沿非阻塞工作模式 -- 效率最高
先來(lái)個(gè)需求吧:
針對(duì)一個(gè)客戶端(進(jìn)程間管道通信)對(duì)應(yīng)一個(gè)服務(wù)器來(lái)說(shuō)
如果客戶端發(fā)送的信息有100字節(jié), 而服務(wù)器每次接收只接收50字節(jié), 那么剩下的50字節(jié)怎么處理?
分析:
- 默認(rèn)執(zhí)行流程: 對(duì)應(yīng)的緩沖區(qū)存放了發(fā)送來(lái)的100字節(jié),系統(tǒng)epoll監(jiān)聽(tīng)到了對(duì)應(yīng)的文件描述符的變化, 此時(shí)服務(wù)端去讀數(shù)據(jù), 但是只讀了50字節(jié), 那么緩沖區(qū)中就還留有50字節(jié)
此時(shí)有兩種說(shuō)法: 事實(shí)是第二種
1.系統(tǒng)為提高效率, 不會(huì)再去調(diào)用epoll_wait函數(shù), 那么50字節(jié)數(shù)據(jù)就只能等下次客戶端發(fā)送信息的時(shí)候接收(為了提高效率, 減少該函數(shù)的調(diào)用次數(shù))
2.系統(tǒng)會(huì)再次調(diào)用epoll_wait函數(shù), 將數(shù)據(jù)讀出來(lái).(后面會(huì)附帶上代碼) - 邊沿阻塞觸發(fā)模式: 會(huì)想上述的第1種情況那樣, 但是會(huì)導(dǎo)致緩沖區(qū)里每次都?xì)埩魯?shù)據(jù), 并且越來(lái)越多...
- 邊沿非阻塞(O_NONBLOCK)觸發(fā)模式: 該效率最高, 主要是因?yàn)閷⒖蛻舳藢?duì)應(yīng)的那個(gè)文件描述符即緩沖區(qū)(管道)設(shè)置成非阻塞模式, 此時(shí)接受(讀取)信息的時(shí)候就需要循環(huán)去讀取, 當(dāng)read/recv返回值為0時(shí)表示讀取完畢.再加上邊沿模式(只調(diào)用一次epoll_wait函數(shù))所以效率高
設(shè)置非阻塞:
1.open的時(shí)候設(shè)置參數(shù);
2.fcntl設(shè)置
//文件打開(kāi)之后修改文件屬性 先獲勝設(shè)置的屬性 flags
//獲取flags:
int flags = fcntl(fd, F_GETFL);
//設(shè)置flags:
flags = flags | O_NONBLOCK;
fcntl(fd, F_SETFL, flags);
對(duì)應(yīng)的三種模式的代碼:
- 利用管道-父子進(jìn)程之間通信實(shí)現(xiàn)前兩種模式:
切換在代碼中注釋的地方, 輸出的格式如上文描述那樣, 這里模擬的是10字節(jié)和5字節(jié)
#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];
//使用管道實(shí)現(xiàn),管道創(chuàng)建需要一個(gè)數(shù)組,存放的一個(gè)對(duì)應(yīng)讀,一個(gè)對(duì)應(yīng)寫(xiě)
int pfd[2];
//創(chuàng)建匿名管道
pipe(pfd);
//創(chuàng)建子進(jìn)程
pid_t pid = fork();
if(pid == 0) { //子進(jìn)程
//不需要讀操作,關(guān)閉讀的文件描述符,確保管道單項(xiàng)傳輸數(shù)據(jù)
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';
//此時(shí)數(shù)組中存放的是aaaa\nbbbb\n
//一次發(fā)送10字節(jié)
write(pfd[1], buf, sizeof(buf));
sleep(3);
}
close(pfd[1]);
} else if(pid > 0) {//父進(jìn)程
close(pfd[1]);
//創(chuàng)建epoll模型,指向根節(jié)點(diǎn),句柄
int efd = epoll_create(10);
//將要監(jiān)聽(tīng)的掛載到根節(jié)點(diǎn)上
struct epoll_event event;
//設(shè)置邊沿觸發(fā)如下:
event.events = EPOLLIN | EPOLLET;
/*
* //默認(rèn)就是水平觸發(fā)
* event.events = EPOLLIN;
*/
event.data.fd = pfd[0];//寫(xiě)端
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字節(jié)
write(STDOUT_FILENO, readbuf, len);
}
}
close(pfd[0]);
close(efd);
} else {
perror("fork error");
exit(1);
}
return 0;
}
- 利用c/s模型實(shí)現(xiàn)的邊沿阻塞觸發(fā), 這里做的是只對(duì)應(yīng)一個(gè)客戶端進(jìn)行監(jiān)聽(tīng)
#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));
// 創(chuàng)建紅黑樹(shù)根節(jié)點(diǎn)
int efd = epoll_create(10);
// 檢測(cè)的事件設(shè)置
#if 0
/* ET 邊沿觸發(fā) */
event.events = EPOLLIN | EPOLLET;
#else
/* 默認(rèn) LT 水平觸發(fā) */
event.events = EPOLLIN;
#endif
// 需要檢測(cè)的文件描述符
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一樣只對(duì)客戶端進(jìn)行監(jiān)聽(tīng), 不過(guò)這里要注意的是不能再根據(jù)read的返回值去判斷是客戶端斷開(kāi)了連接還是讀取失敗還是讀到的內(nèi)容, 需要另外的思路去設(shè)計(jì)程序, 比如利用data里面的void *指針來(lái)做, 存放一個(gè)時(shí)間, 如果長(zhǎng)時(shí)間沒(méi)有聯(lián)系, 則斷開(kāi)連接...或者利用心跳包
#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 邊沿觸發(fā),默認(rèn)是水平觸發(fā) */
event.events = EPOLLIN | EPOLLET;
event.data.fd = connfd;
//將connfd加入監(jiān)聽(tīng)紅黑樹(shù)
epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);
while (1) {
int len = 0;
printf("epoll_wait begin\n");
//最多10個(gè), 阻塞監(jiān)聽(tīng)
int res = epoll_wait(efd, resevent, 10, -1);
printf("epoll_wait end res %d\n", res);
if (resevent[0].data.fd == connfd) {
// 非阻塞讀, 輪詢
// epoll_wait 觸發(fā)一次,剩余數(shù)據(jù)循環(huán)讀取
while ((len = read(connfd, buf, MAXLINE/2)) >0 ) {
write(STDOUT_FILENO, buf, len);
}
}
}
return 0;
}