一、epoll概述
epoll是Linux內(nèi)核中的一個(gè)事件驅(qū)動(dòng)I/O機(jī)制,用于處理多個(gè)文件描述符上的事件。它是一個(gè)高效且強(qiáng)大的I/O多路復(fù)用工具,可以用于處理大量文件描述符的I/O操作。epoll的主要優(yōu)點(diǎn)是它只占用較少的資源,并且比傳統(tǒng)的select和poll更易于使用。
epoll的工作原理是通過(guò)一個(gè)事件表來(lái)跟蹤所有需要監(jiān)控的文件描述符。當(dāng)某個(gè)文件描述符上有事件發(fā)生時(shí),epoll會(huì)通知程序去處理這些事件。這種方式可以確保程序在等待某個(gè)文件描述符上有事件發(fā)生時(shí)只占用較少的資源,而不是像select和poll那樣整個(gè)程序都阻塞。
----來(lái)自CodeGeex
二、epoll
1.epoll API 介紹
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
常見(jiàn)的Epoll檢測(cè)事件:
- EPOLLIN
- EPOLLOUT
- EPOLLERR
// 對(duì)epoll實(shí)例進(jìn)行管理:添加文件描述符信息,刪除信息,修改信息
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
- 參數(shù):
- epfd : epoll實(shí)例對(duì)應(yīng)的文件描述符
- op : 要進(jìn)行什么操作
EPOLL_CTL_ADD: 添加
EPOLL_CTL_MOD: 修改
EPOLL_CTL_DEL: 刪除
- fd : 要檢測(cè)的文件描述符
- event : 檢測(cè)文件描述符什么事情
// 檢測(cè)函數(shù)----檢測(cè)epoll樹(shù)中是否有就緒的文件描述符
// 創(chuàng)建了epfd,設(shè)置好某個(gè)fd上需要檢測(cè)事件并將該fd綁定到epfd上去后,就可以調(diào)用epoll_wait
// 檢測(cè)事件了
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
- 參數(shù):
- epfd : epoll實(shí)例對(duì)應(yīng)的文件描述符
- events : 傳出參數(shù),保存了發(fā)送了變化的文件描述符的信息
- maxevents : 第二個(gè)參數(shù)結(jié)構(gòu)體數(shù)組的大小
- timeout : 阻塞時(shí)間
- 0 : 不阻塞
- -1 : 阻塞,直到檢測(cè)到fd數(shù)據(jù)發(fā)生變化,解除阻塞
- > 0 : 阻塞的時(shí)長(zhǎng)(毫秒)
- 返回值:
- 成功,返回發(fā)送變化的文件描述符的個(gè)數(shù) > 0
- 失敗 -1
// 創(chuàng)建epoll實(shí)例,通過(guò)一棵紅黑樹(shù)管理待檢測(cè)集合
// 參數(shù) size 從 Linux 2.6.8 以后就不再使用,但是必須設(shè)置一個(gè)大于 0 的值。epoll_create 函數(shù)調(diào)用成功返回一個(gè)非負(fù)值的 epollfd,調(diào)用失敗返回 -1。
int epoll_create(int size);
>>epoll_wait 缺點(diǎn):
① epoll_wait 調(diào)用之后,需要將所有fd的event參數(shù)重新設(shè)置一遍,
如果fd比較多的話,會(huì)比較消耗性能。----來(lái)自CodeGeeX
>>epoll_wait 優(yōu)點(diǎn):
① epoll_wait 調(diào)用之后,直接在event參數(shù)中拿到所有有事件就緒的fd,直接處理即可。
② 一般在fd數(shù)量比較多,但某段時(shí)間內(nèi),就緒事件fd數(shù)量較少的情況下,epoll_wait才會(huì)
體現(xiàn)出它的優(yōu)勢(shì),也就是說(shuō)socket連接數(shù)量較大時(shí)而活躍連接較少時(shí)epoll模型更高效。
// epoll 的使用
// 操作步驟
// 在服務(wù)器使用 epoll 進(jìn)行 IO 多路轉(zhuǎn)接的操作步驟如下:
1.創(chuàng)建監(jiān)聽(tīng)的套接字
int lfd = socket(AF_INET, SOCK_STREAM, 0);
2.設(shè)置端口復(fù)用(可選)
int opt = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
3.使用本地的IP與端口和監(jiān)聽(tīng)的套接字進(jìn)行綁定
int ret = bind(lfd, (struct sockaddr*)&saddr, sizeof(saddr));
4.給監(jiān)聽(tīng)的套接字設(shè)置監(jiān)聽(tīng)
listen(lfd, 128);
5.創(chuàng)建 epoll 實(shí)例
int epfd = epoll_create(100);
6.將用于監(jiān)聽(tīng)的套接字添加到 epoll 實(shí)例中
struct epoll_event ev;
ev.events = EPOLLIN; //檢測(cè)lfd讀緩沖區(qū)是否有數(shù)據(jù)
ev.data.fd = lfd;
int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
接著創(chuàng)建一個(gè)數(shù)組,用于存儲(chǔ)epoll_wait()返回的文件描述符
struct epoll_event evs[1024];
7.檢測(cè)添加到epoll實(shí)例中的文件描述符是否已經(jīng)就緒,并將這些已就緒的文件描述符進(jìn)行處理
int num = epoll_wait(epfd, evs, size, -1);
① 如果監(jiān)聽(tīng)的是文件描述符,和新客戶端建立連接,將得到的文件描述符添加到epoll實(shí)例中
int cfd = accept(curfd,NULL,NULL);
ev.events = EPOLLIN;
ev.data.fd = cfd;
新得到的文件描述符添加到epoll模型中,下一輪循環(huán)的時(shí)候就可以被檢測(cè)了
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
② 如果是通信的文件描述符,和對(duì)應(yīng)的客戶端通信,如果連接已斷開(kāi),將該文件描述符從epoll實(shí)例中刪除
int len = recv(curfd,buf,sizeof(buf),0);
if(len == 0) {
// 將這個(gè)文件描述符從epoll實(shí)例中刪除
epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL);
close(curfd);
}else if(len > 0) {
send(curfd,buf,len,0);
}
8.重復(fù)第 7 步的操作
往期文章推薦:
IO多路轉(zhuǎn)接(復(fù)用)多線程 select 并發(fā)_呵呵噠( ̄▽ ̄)"的博客-CSDN博客https://blog.csdn.net/weixin_41987016/article/details/132497986?spm=1001.2014.3001.5501
?
select/poll低效的原因之一是將“添加/維護(hù)待檢測(cè)任務(wù)”和“阻塞進(jìn)程/線程”兩個(gè)步驟合二為一。每次調(diào)用select都需要這兩步操作,然而大多數(shù)應(yīng)用場(chǎng)景中,需要監(jiān)視的socket個(gè)數(shù)相對(duì)固定,并不需要每次都修改。epoll將這兩個(gè)操作分開(kāi),先用epoll_ctl()維護(hù)等待隊(duì)列,再調(diào)用epoll_wait()阻塞進(jìn)程(解耦)。通過(guò)下圖的對(duì)比顯而易見(jiàn),epoll的效率得到了提升。
作者: 蘇丙榅
鏈接: https://subingwen.cn/linux/epoll/
來(lái)源: 愛(ài)編程的大丙
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。
?第一種 IO多路轉(zhuǎn)接技術(shù):select/poll
?
?
server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
int main() {
// 創(chuàng)建socket
int lfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(9999);
saddr.sin_addr.s_addr = INADDR_ANY;
// 綁定
int ret = bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(ret == -1) {
perror("bind");
exit(-1);
}
// 監(jiān)聽(tīng)
ret = listen(lfd,8);
if(ret == -1) {
perror("listen");
exit(-1);
}
// 用epoll_create()創(chuàng)建一個(gè)epoll實(shí)例
int epfd = epoll_create(100);
// 將監(jiān)聽(tīng)的文件描述符相關(guān)的檢測(cè)信息添加到epoll實(shí)例中
struct epoll_event epev;
epev.events = EPOLLIN;
epev.data.fd = lfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&epev);
// 創(chuàng)建一個(gè)數(shù)組,用于存儲(chǔ)epoll_wait()返回的文件描述符
struct epoll_event epevs[1024];
while (1) {
ret = epoll_wait(epfd,epevs,1024,-1);
if(ret == -1) {
perror("epoll_wait");
exit(-1);
}
printf("ret = %d\n",ret);
for(int i = 0;i < ret;i++) {
int curfd = epevs[i].data.fd;
if(curfd == lfd) {
// 監(jiān)聽(tīng)的文件描述符有數(shù)據(jù)到達(dá),有客戶端連接
struct sockaddr_in caddr;
int len = sizeof(caddr);
int cfd = accept(lfd,(struct sockaddr*)&caddr,&len);
// epev.events = EPOLLIN | EPOLLOUT;
epev.events = EPOLLIN;
epev.data.fd = cfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&epev);
} else {
// if(epevs[i].events & EPOLLOUT) {
// continue;
// }
// 有數(shù)據(jù)到達(dá),需要通信
char buf[1024] = {0};
int len = read(curfd,buf,sizeof(buf));
if (len == -1) {
perror("read");
exit(-1);
} else if(len == 0) {
printf("client closed...\n");
epoll_ctl(epfd,EPOLL_CTL_DEL,curfd,NULL);
close(curfd);
} else if(len > 0) {
printf("recv buf = %s\n",buf);
write(curfd,buf,strlen(buf) + 1);
}
}
}
}
close(lfd);
close(epfd);
return 0;
}
client.c
#include <stdio.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char* argv[]) {
int fd = socket(AF_INET,SOCK_STREAM,0);
if(fd == -1) {
perror("socket");
return -1;
}
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(9999);
inet_pton(AF_INET,"127.0.0.1",&saddr.sin_addr.s_addr);
// 連接服務(wù)器
int ret = connect(fd,(struct sockaddr*)&saddr,sizeof(saddr));
if(ret == -1) {
perror("connect");
return -1;
}
int num = 0;
while (1) {
char sendBuf[1024] = {0};
sprintf(sendBuf,"send data %d",num++);
write(fd,sendBuf,strlen(sendBuf) + 1);
// 接收
int len = read(fd,sendBuf,sizeof(sendBuf));
if(len == -1) {
perror("read");
return -1;
}else if(len > 0) {
printf("read buf = %s\n",sendBuf);
}else{
printf("服務(wù)器已經(jīng)斷開(kāi)連接...\n");
break;
}
// sleep(1);
usleep(1000);
}
close(fd);
return 0;
}
2.epoll 的兩種工作模式?
Epoll 的工作模式:
LT 模式 (水平觸發(fā))
假設(shè)委托內(nèi)核檢測(cè)讀事件 -> 檢測(cè)fd的讀緩沖區(qū)
讀緩沖區(qū)有數(shù)據(jù) - > epoll檢測(cè)到了會(huì)給用戶通知
a.用戶不讀數(shù)據(jù),數(shù)據(jù)一直在緩沖區(qū),epoll 會(huì)一直通知
b.用戶只讀了一部分?jǐn)?shù)據(jù),epoll會(huì)通知
c.緩沖區(qū)的數(shù)據(jù)讀完了,不通知
LT(level - triggered)是缺省的工作方式,并且同時(shí)支持 block 和 no-block socket。在這
種做法中,內(nèi)核告訴你一個(gè)文件描述符是否就緒了,然后你可以對(duì)這個(gè)就緒的 fd 進(jìn)行 IO 操
作。如果你不作任何操作,內(nèi)核還是會(huì)繼續(xù)通知你的。
ET 模式(邊沿觸發(fā))
假設(shè)委托內(nèi)核檢測(cè)讀事件 -> 檢測(cè)fd的讀緩沖區(qū)
讀緩沖區(qū)有數(shù)據(jù) - > epoll檢測(cè)到了會(huì)給用戶通知
a.用戶不讀數(shù)據(jù),數(shù)據(jù)一直在緩沖區(qū)中,epoll下次檢測(cè)的時(shí)候就不通知了
b.用戶只讀了一部分?jǐn)?shù)據(jù),epoll不通知
c.緩沖區(qū)的數(shù)據(jù)讀完了,不通知
ET(edge - triggered)是高速工作方式,只支持 no-block socket。在這種模式下,當(dāng)描述
符從未就緒變?yōu)榫途w時(shí),內(nèi)核通過(guò)epoll告訴你。然后它會(huì)假設(shè)你知道文件描述符已經(jīng)就緒,
并且不會(huì)再為那個(gè)文件描述符發(fā)送更多的就緒通知,直到你做了某些操作導(dǎo)致那個(gè)文件描述
符不再為就緒狀態(tài)了。但是請(qǐng)注意,如果一直不對(duì)這個(gè) fd 作 IO 操作(從而導(dǎo)致它再次變成
未就緒),內(nèi)核不會(huì)發(fā)送更多的通知(only once)。
ET 模式在很大程度上減少了 epoll 事件被重復(fù)觸發(fā)的次數(shù),因此效率要比 LT 模式高。epoll
工作在 ET 模式的時(shí)候,必須使用非阻塞套接口,以避免由于一個(gè)文件句柄的阻塞讀/阻塞寫(xiě)
操作把處理多個(gè)文件描述符的任務(wù)餓死。
綜上所述:epoll的邊沿模式下 epoll_wait檢測(cè)到文件描述符有新事件才會(huì)通知,
如果不是新的事情就不通知,通知的次數(shù)比水平模式少,效率比水平模式高。
【注意】 ET模式需要配合循環(huán)+非阻塞
(1)LT 模式
epoll_lt.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
int main() {
// 創(chuàng)建socket
int lfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(9999);
saddr.sin_addr.s_addr = INADDR_ANY;
// 綁定
int ret = bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(ret == -1) {
perror("bind");
exit(-1);
}
// 監(jiān)聽(tīng)
ret = listen(lfd,8);
if(ret == -1) {
perror("listen");
exit(-1);
}
// 用epoll_create()創(chuàng)建一個(gè)epoll實(shí)例
int epfd = epoll_create(100);
// 將監(jiān)聽(tīng)的文件描述符相關(guān)的檢測(cè)信息添加到epoll實(shí)例中
struct epoll_event epev;
epev.events = EPOLLIN;
epev.data.fd = lfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&epev);
// 創(chuàng)建一個(gè)數(shù)組,用于存儲(chǔ)epoll_wait()返回的文件描述符
struct epoll_event epevs[1024];
while (1) {
ret = epoll_wait(epfd,epevs,1024,-1);
if(ret == -1) {
perror("epoll_wait");
exit(-1);
}
printf("ret = %d\n",ret);
for(int i = 0;i < ret;i++) {
int curfd = epevs[i].data.fd;
if(curfd == lfd) {
// 監(jiān)聽(tīng)的文件描述符有數(shù)據(jù)到達(dá),有客戶端連接
struct sockaddr_in caddr;
int len = sizeof(caddr);
int cfd = accept(lfd,(struct sockaddr*)&caddr,&len);
// epev.events = EPOLLIN | EPOLLOUT;
epev.events = EPOLLIN;
epev.data.fd = cfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&epev);
} else {
// if(epevs[i].events & EPOLLOUT) {
// continue;
// }
// 有數(shù)據(jù)到達(dá),需要通信
char buf[5] = {0};
int len = read(curfd,buf,sizeof(buf));
if (len == -1) {
perror("read");
exit(-1);
} else if(len == 0) {
printf("client closed...\n");
epoll_ctl(epfd,EPOLL_CTL_DEL,curfd,NULL);
close(curfd);
} else if(len > 0) {
printf("recv buf = %s\n",buf);
write(curfd,buf,strlen(buf) + 1);
}
}
}
}
close(lfd);
close(epfd);
return 0;
}
(2)ET 模式
>> epoll在邊沿模式下非阻塞接收數(shù)據(jù)
循環(huán)接收數(shù)據(jù)的處理方式:對(duì)于每次接收的buffer多小都不重要了,只不過(guò)我們需要多接收幾次數(shù)據(jù)。
效率相對(duì)來(lái)說(shuō)低一些;如果說(shuō)buffer稍微大一點(diǎn),接收數(shù)據(jù)的次數(shù)就少一些,效率相對(duì)來(lái)說(shuō)高一些;
可以把recv寫(xiě)到一個(gè)while循環(huán)里,通過(guò)while循環(huán),每次讀取5個(gè)字節(jié),直到把客戶端發(fā)過(guò)來(lái)的數(shù)據(jù)全部都讀到本地。
【思考】這種方式的弊端在哪里?
【思考】進(jìn)行套接字通信時(shí)阻塞的還是非阻塞的?
【回答】很顯然默認(rèn)情況下進(jìn)行套接字通信,這個(gè)處理流程是阻塞的。如果是阻塞的,
當(dāng)這個(gè)服務(wù)器端循環(huán)接收客戶端發(fā)過(guò)來(lái)的數(shù)據(jù),假設(shè)客戶端發(fā)來(lái)了100個(gè)字節(jié)的數(shù)據(jù),
在服務(wù)端接收了20次,就把客戶端發(fā)過(guò)來(lái)的數(shù)據(jù)全部讀到本地了,但是在做第21次讀
數(shù)據(jù)的時(shí)候,這個(gè)recv它還能讀到數(shù)據(jù)嗎?
沒(méi)有了,也就是說(shuō)這個(gè)文件描述符對(duì)應(yīng)的讀緩沖區(qū)里邊是空的。如果說(shuō)這個(gè)文件描述符
對(duì)應(yīng)的讀緩沖區(qū)里邊是空的。這個(gè)recv再去接收數(shù)據(jù)的話,服務(wù)器端的線程或者服務(wù)器
端的進(jìn)程它就阻塞了。如果這個(gè)線程/進(jìn)程阻塞了,就不能干別的事情了。如果說(shuō)寫(xiě)的
這個(gè)程序里邊就是單線程或者單進(jìn)程的程序,在這里阻塞了,就不能夠去做其他的事情
了,整個(gè)程序就停止在這里了。
【問(wèn)題】如何讓while循環(huán)中的break起作用?
修改文件描述符為非阻塞,而不是修改read/recv函數(shù),因?yàn)檫@函數(shù)時(shí)基于文件描述符
去進(jìn)行數(shù)據(jù)的接收操作,所以說(shuō)需要修改一下這個(gè)文件描述符的屬性,把這個(gè)文件描述
符的默認(rèn)阻塞屬性修改為非阻塞屬性。再次調(diào)用recv/read函數(shù)的時(shí)候,它們也就不會(huì)阻塞了
【思考】如何把這個(gè)文件描述符修改為非阻塞屬性?
解決阻塞問(wèn)題,需要將套接字默認(rèn)的阻塞行為修改為非阻塞,需要使用fcntl()函數(shù)進(jìn)行處理
// 設(shè)置完成之后,讀寫(xiě)都變成了非阻塞模式
int flag = fcntl(cfd,F_GETFL);
flag |= O_NOBLOCK;
fcntl(cfd,F_SETFL,flag);
>>什么時(shí)候使用EWOULDBLOCK?
如果對(duì)于一個(gè)非阻塞socket,如果使用epoll邊緣模式去檢測(cè)數(shù)據(jù)是否可讀,觸發(fā)可讀
事件,一定要一次性把socket上的數(shù)據(jù)收取干凈才行,也就是一定要循環(huán)調(diào)用recv函數(shù)
直到recv出錯(cuò),錯(cuò)誤碼是EWOULDBLOCK,這個(gè)錯(cuò)誤碼表示的就是沒(méi)有數(shù)據(jù)可讀了,
這個(gè)時(shí)候才能退出循環(huán),退出循環(huán)之后才能去處理可讀事件。
如果使用水平模式,則不用,你可以根據(jù)業(yè)務(wù)一次性收取固定的字節(jié)數(shù),或者
收完為止。
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
常見(jiàn)的Epoll檢測(cè)事件:
- EPOLLIN
- EPOLLOUT
- EPOLLERR
- EPOLLET
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>
int main() {
// 創(chuàng)建socket
int lfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(9999);
saddr.sin_addr.s_addr = INADDR_ANY;
// 綁定
int ret = bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(ret == -1) {
perror("bind");
exit(-1);
}
// 監(jiān)聽(tīng)
ret = listen(lfd,8);
if(ret == -1) {
perror("listen");
exit(-1);
}
// 用epoll_create()創(chuàng)建一個(gè)epoll實(shí)例
int epfd = epoll_create(100);
// 將監(jiān)聽(tīng)的文件描述符相關(guān)的檢測(cè)信息添加到epoll實(shí)例中
struct epoll_event epev;
epev.events = EPOLLIN;
epev.data.fd = lfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&epev);
// 創(chuàng)建一個(gè)數(shù)組,用于存儲(chǔ)epoll_wait()返回的文件描述符
struct epoll_event epevs[1024];
while (1) {
ret = epoll_wait(epfd,epevs,1024,-1);
if(ret == -1) {
perror("epoll_wait");
exit(-1);
}
printf("ret = %d\n",ret);
for(int i = 0;i < ret;i++) {
int curfd = epevs[i].data.fd;
if(curfd == lfd) {
// 監(jiān)聽(tīng)的文件描述符有數(shù)據(jù)到達(dá),有客戶端連接
struct sockaddr_in caddr;
int len = sizeof(caddr);
int cfd = accept(lfd,(struct sockaddr*)&caddr,&len);
// 設(shè)置cfd屬性非阻塞
int flag = fcntl(cfd,F_GETFL);
flag |= O_NONBLOCK;
fcntl(cfd,F_SETFL,flag);
epev.events = EPOLLIN | EPOLLET;// 設(shè)置邊沿觸發(fā)
epev.data.fd = cfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&epev);
} else {
if(epevs[i].events & EPOLLOUT) {
continue;
}
// 循環(huán)讀取出所有的數(shù)據(jù)
char buf[5];
int len = 0;
while ((len = read(curfd,buf,sizeof(buf))) > 0) {
// 打印數(shù)據(jù)
// printf("recv data : %s\n",buf);
write(STDOUT_FILENO,buf,len);
write(curfd,buf,len);
}
if(len == 0) {
printf("client closed...\n");
}else if(len == -1) {
if(errno == EAGAIN) {
printf("data over......\n");
} else {
perror("read");
exit(-1);
}
}
}
}
}
close(lfd);
close(epfd);
return 0;
}
client.c
#include <stdio.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char* argv[]) {
int fd = socket(AF_INET,SOCK_STREAM,0);
if(fd == -1) {
perror("socket");
return -1;
}
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(9999);
inet_pton(AF_INET,"127.0.0.1",&saddr.sin_addr.s_addr);
// 連接服務(wù)器
int ret = connect(fd,(struct sockaddr*)&saddr,sizeof(saddr));
if(ret == -1) {
perror("connect");
return -1;
}
int num = 0;
while (1) {
char sendBuf[1024] = {0};
// sprintf(sendBuf,"send data %d",num++);
fgets(sendBuf,sizeof(sendBuf),stdin);
write(fd,sendBuf,strlen(sendBuf) + 1);
// 接收
int len = read(fd,sendBuf,sizeof(sendBuf));
if(len == -1) {
perror("read");
return -1;
}else if(len > 0) {
printf("read buf = %s\n",sendBuf);
}else{
printf("服務(wù)器已經(jīng)斷開(kāi)連接...\n");
break;
}
// sleep(1);
// usleep(1000);
}
close(fd);
return 0;
}
推薦和參考文章:
IO多路轉(zhuǎn)接(復(fù)用)之epoll | 愛(ài)編程的大丙 (subingwen.cn)https://subingwen.cn/linux/epoll/
網(wǎng)絡(luò)通信基礎(chǔ)重難點(diǎn)解析 12 :Linux epoll 模型-騰訊云開(kāi)發(fā)者社區(qū)-騰訊云 (tencent.com)https://cloud.tencent.com/developer/article/1419519文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-675685.html
IO多路復(fù)用之select、poll、epoll之間的區(qū)別總結(jié)_io多路復(fù)用select,poll,epoll的區(qū)別_linux大本營(yíng)的博客-CSDN博客https://blog.csdn.net/qq_40989769/article/details/128647476文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-675685.html
到了這里,關(guān)于epoll() 多路復(fù)用 和 兩種工作模式的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!