IO讀寫(xiě)基本原理
用戶程序進(jìn)行IO操作實(shí)際依賴于linux系統(tǒng)內(nèi)核read()捆昏、write()函數(shù)
read()函數(shù)的調(diào)用并不是直接從網(wǎng)卡把數(shù)據(jù)讀取到用戶內(nèi)存中,而是把內(nèi)核緩沖區(qū)中的數(shù)據(jù)復(fù)制到用戶緩沖區(qū)中
write()函數(shù)的調(diào)用也并不是直接把數(shù)據(jù)寫(xiě)入網(wǎng)卡中毙沾,而是把用戶緩沖區(qū)的數(shù)據(jù)寫(xiě)入到內(nèi)核緩沖區(qū)中
網(wǎng)卡與內(nèi)核緩沖區(qū)數(shù)據(jù)的讀寫(xiě)則是由操作系統(tǒng)內(nèi)核完成
阻塞IO和非阻塞IO
網(wǎng)卡同步數(shù)據(jù)到內(nèi)核緩沖區(qū)骗卜,如果內(nèi)核緩沖區(qū)中的數(shù)據(jù)未準(zhǔn)備好,用戶進(jìn)程發(fā)起read操作左胞,阻塞則會(huì)一直等待內(nèi)存緩沖區(qū)數(shù)據(jù)完整后再解除阻塞寇仓,而非阻塞則會(huì)立即返回不會(huì)等待
而內(nèi)核緩沖區(qū)與用戶緩沖區(qū)之間的讀寫(xiě)操作肯定是阻塞的
同步和異步
同步:調(diào)用者主動(dòng)發(fā)起請(qǐng)求,調(diào)用者主動(dòng)等待這個(gè)結(jié)果返回罩句,一但調(diào)用就必須有返回值
異步:調(diào)用發(fā)出后直接返回焚刺,所以沒(méi)有返回結(jié)果。被調(diào)用者處理完成后通知回調(diào)门烂、通知等機(jī)制來(lái)通知調(diào)用者
同步阻塞IO
讀取數(shù)據(jù)流程
- 用戶進(jìn)程調(diào)用read()系統(tǒng)函數(shù)乳愉,用戶進(jìn)程進(jìn)入阻塞狀態(tài)
- 系統(tǒng)內(nèi)核收到read()系統(tǒng)調(diào)用,網(wǎng)卡開(kāi)始準(zhǔn)備接收數(shù)據(jù)屯远,在一開(kāi)始內(nèi)核緩沖區(qū)數(shù)據(jù)為空蔓姚,內(nèi)核在等待接收數(shù)據(jù),用戶進(jìn)程同步阻塞等待
- 內(nèi)核緩沖區(qū)中有完整的數(shù)據(jù)后慨丐,內(nèi)核會(huì)將內(nèi)核緩沖區(qū)中的數(shù)據(jù)復(fù)制到用戶緩沖區(qū)
- 直到用戶緩沖區(qū)中有數(shù)據(jù)坡脐,用戶進(jìn)程才能解除阻塞狀態(tài)繼續(xù)執(zhí)行
同步阻塞IO底層實(shí)現(xiàn)
// 創(chuàng)建socket
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
// 綁定
bind(listenfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
// 監(jiān)聽(tīng)
listen(listenfd, 5);
// 接受客戶端連接
int socketFd = accept(listenfd, (struct sockaddr*) &clientaddr, &clientaddrlen)
// 接收客戶端數(shù)據(jù)
recv(socketFd, buf, 256, 0);
同步阻塞IO的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 開(kāi)發(fā)簡(jiǎn)單,由于accept()房揭、recv()都是阻塞的备闲,為了服務(wù)于多個(gè)客戶端請(qǐng)求,新的連接創(chuàng)建一個(gè)線程去處理即可
- 阻塞的時(shí)候捅暴,線程掛起恬砂,不消耗CPU資源
缺點(diǎn):
- 每新來(lái)一個(gè)IO請(qǐng)求,都需要新建一個(gè)線程對(duì)應(yīng)蓬痒,高并發(fā)下系統(tǒng)開(kāi)銷大泻骤,多線程上下文切換頻繁
- 創(chuàng)建線程太多,內(nèi)存消耗大
同步阻塞IO缺點(diǎn)帶來(lái)的思考
因?yàn)閍ccept()、recv()函數(shù)都是阻塞的狱掂,如果系統(tǒng)想要支持多個(gè)IO請(qǐng)求演痒,就創(chuàng)建更多的線程,如果去解決這個(gè)問(wèn)題呢趋惨?
如果可以把a(bǔ)ccept鸟顺、recv函數(shù)變成非阻塞的方式,是不是就可以避免創(chuàng)建多個(gè)線程了器虾?這就引入了我們的同步非阻塞IO
同步非阻塞IO
讀取數(shù)據(jù)流程
- 用戶進(jìn)程發(fā)起請(qǐng)求調(diào)用read()函數(shù)诊沪,系統(tǒng)內(nèi)核收到read()系統(tǒng)調(diào)用,網(wǎng)卡開(kāi)始準(zhǔn)備接收數(shù)據(jù)
- 內(nèi)核緩沖區(qū)數(shù)據(jù)沒(méi)有準(zhǔn)備好曾撤,請(qǐng)求立即返回,用戶進(jìn)程不斷的重試查詢內(nèi)核緩沖區(qū)數(shù)據(jù)有沒(méi)有準(zhǔn)備好
- 當(dāng)內(nèi)核緩沖區(qū)數(shù)據(jù)準(zhǔn)備好了之后晕粪,用戶進(jìn)程阻塞挤悉,內(nèi)核開(kāi)始將內(nèi)核緩沖區(qū)數(shù)據(jù)復(fù)制到用戶緩沖區(qū)
- 復(fù)制完成后,用戶進(jìn)程解除阻塞巫湘,讀取數(shù)據(jù)繼續(xù)執(zhí)行
同步非阻塞IO底層實(shí)現(xiàn)
// 創(chuàng)建socket
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
// 綁定
bind(listenfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
// 監(jiān)聽(tīng)
listen(listenfd, 5);
// 設(shè)置為非阻塞
ioctl(listenfd, FIONBIO, 1);
// 接受客戶端連接
int socketFd = accept(listenfd, (struct sockaddr*) &clientaddr, &clientaddrlen);
// 設(shè)置為非阻塞
ioctl(socketFd, FIONBIO, 1);
while (1) {
int fd;
// 循環(huán)遍歷
for (fd : fds) {
// 接收客戶端數(shù)據(jù)
recv(fd, buf, 256, 0);
}
}
同步非阻塞IO的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 非阻塞装悲, accept()、recv()均不阻塞尚氛,用戶線程立即返回
- 規(guī)避了同步阻塞模式的多線程問(wèn)題
缺點(diǎn):
- 假如現(xiàn)在有1萬(wàn)個(gè)客戶端連接诀诊,但只有1個(gè)客戶端發(fā)送數(shù)據(jù)過(guò)來(lái),為了獲取這個(gè)1個(gè)客戶端發(fā)送的消息阅嘶,我需要循環(huán)向內(nèi)核發(fā)送1萬(wàn)遍recv()系統(tǒng)調(diào)用属瓣,而這其中有9999次是無(wú)效的請(qǐng)求,浪費(fèi)CPU資源
同步非阻塞IO缺點(diǎn)帶來(lái)的思考
針對(duì)同步非阻塞IO的缺點(diǎn)讯柔,設(shè)想如果內(nèi)核提供一個(gè)方法抡蛙,可以一次性把1萬(wàn)個(gè)客戶端socket連接傳入,在內(nèi)核中去遍歷魂迄,如果沒(méi)有數(shù)據(jù)這個(gè)方法就一直阻塞粗截,一但有數(shù)據(jù)這個(gè)方法解除阻塞并把所有有數(shù)據(jù)的socket返回,把這個(gè)遍歷的過(guò)程交給內(nèi)核去處理捣炬,是不是就可以避免空跑熊昌,避免1萬(wàn)次用戶態(tài)到內(nèi)核態(tài)的切換呢?
IO多路復(fù)用模型
什么是IO多路復(fù)用湿酸?
一個(gè)線程監(jiān)測(cè)多個(gè)IO操作
IO多路復(fù)用實(shí)現(xiàn)原理
IO多路復(fù)用模型是建立在內(nèi)核提供的多路分離函數(shù)select基礎(chǔ)之上的婿屹,使用select函數(shù)可以避免同步非阻塞IO模型中輪詢等待的問(wèn)題,即一次性將N個(gè)客戶端socket連接傳入內(nèi)核然后阻塞稿械,交由內(nèi)核去輪詢选泻,當(dāng)某一個(gè)或多個(gè)socket連接有事件發(fā)生時(shí),解除阻塞并返回事件列表,用戶進(jìn)程在循環(huán)遍歷處理有事件的socket連接页眯。這樣就避免了多次調(diào)用recv()系統(tǒng)調(diào)用梯捕,避免了用戶態(tài)到內(nèi)核態(tài)的切換。
IO多路復(fù)用的三種實(shí)現(xiàn)
select函數(shù)
select函數(shù)僅僅知道有幾個(gè)I/O事件發(fā)生了窝撵,但并不知道具體是哪幾個(gè)socket連接有I/O事件傀顾,還需要輪詢?nèi)ゲ檎遥瑫r(shí)間復(fù)雜度為O(n)碌奉,處理的請(qǐng)求數(shù)越多短曾,所消耗的時(shí)間越長(zhǎng)。
select函數(shù)執(zhí)行流程
- 從用戶空間拷貝fd_set(注冊(cè)的事件集合)到內(nèi)核空間
- 遍歷所有fd文件赐劣,并將當(dāng)前進(jìn)程掛到每個(gè)fd的等待隊(duì)列中嫉拐,當(dāng)某個(gè)fd文件設(shè)備收到消息后,會(huì)喚醒設(shè)備等待隊(duì)列上睡眠的進(jìn)程魁兼,那么當(dāng)前進(jìn)程就會(huì)被喚醒
- 如果遍歷完所有的fd沒(méi)有I/O事件婉徘,則當(dāng)前進(jìn)程進(jìn)入睡眠,當(dāng)有某個(gè)fd文件有I/O事件或當(dāng)前進(jìn)程睡眠超時(shí)后咐汞,當(dāng)前進(jìn)程重新喚醒再次遍歷所有fd文件
select函數(shù)接口定義
#include <sys/select.h>
#include <sys/time.h>
// 最大支持1024個(gè)連接
#define FD_SETSIZE 1024
#define NFDBITS (8 * sizeof(unsigned long))
#define __FDSET_LONGS (FD_SETSIZE/NFDBITS)
/**
* 數(shù)據(jù)結(jié)構(gòu) (bitmap)
* fd_set保存了相關(guān)的socket事件
*/
typedef struct {
unsigned long fds_bits[__FDSET_LONGS];
} fd_set;
/**
* select是一個(gè)阻塞函數(shù)
*/
// 返回值就緒描述符的數(shù)目
int select(
int max_fd, // 最大的文件描述符值盖呼,遍歷時(shí)取0-max_fd
fd_set *readset, // 讀事件列表
fd_set *writeset, // 寫(xiě)事件列表
fd_set *exceptset, // 異常列表
struct timeval *timeout // 阻塞超時(shí)時(shí)間
)
FD_ZERO(int fd, fd_set* fds) // 清空集合
FD_SET(int fd, fd_set* fds) // 將給定的描述符加入集合
FD_ISSET(int fd, fd_set* fds) // 判斷指定描述符是否在集合中
FD_CLR(int fd, fd_set* fds) // 將給定的描述符從文件中刪除
select使用示例
#include<stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
void server() {
// 創(chuàng)建socket連接
int lfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in my_addr;
bzero(&my_addr, sizeof(my_addr));
my_addr.sin_family = AF_INET; // ipv4
my_addr.sin_port = htons(9090);
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// 綁定端口
bind(lfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
// 監(jiān)聽(tīng)連接請(qǐng)求
listen(lfd, 128);
printf("listen client @port=%d...\n", 9090);
int lastfd = lfd;
// 定義文件描述符集
fd_set read_fd_set, all_fd_set;
// 服務(wù)socket描述符加入set集合中
FD_ZERO(&all_fd_set);
FD_SET(lfd, &all_fd_set);
printf("準(zhǔn)備進(jìn)入while循環(huán)\n");
while (1) {
read_fd_set = all_fd_set;
printf("阻塞中... lastfd=%d\n", lastfd);
int nready = select(lastfd+1, &read_fd_set, NULL, NULL, NULL);
switch (nready) {
case 0 :
printf("select time out ......\n");
break;
case -1 :
perror("select error \n");
break;
default:
// 監(jiān)聽(tīng)到新的客戶端連接
if (FD_ISSET(lfd, &read_fd_set)) {
struct sockaddr_in client_addr;
socklen_t cliaddr_len = sizeof(client_addr);
char cli_ip[INET_ADDRSTRLEN] = "";
// 肯定有連接不會(huì)阻塞
int clientfd = accept(lfd, (struct sockaddr*)&client_addr, &cliaddr_len);
inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
printf("----------------------------------------------\n");
printf("client ip=%s,port=%d\n", cli_ip, ntohs(client_addr.sin_port));
// 將clientfd加入讀集合
FD_SET(clientfd, &all_fd_set);
lastfd = clientfd;
if(0 == --nready) {
continue;
}
}
int i;
for (i = lfd + 1;i <= lastfd; i++) {
// 處理讀事件
if (FD_ISSET(i, &read_fd_set)) {
char recv_buf[512] = "";
int rs = read(i, recv_buf, sizeof(recv_buf));
if (rs == 0 ) {
close(i);
FD_CLR(i, &all_fd_set);
} else {
printf("%s\n",recv_buf);
// 給每一個(gè)服務(wù)端寫(xiě)數(shù)據(jù)
int j;
for (j = lfd + 1;j <= lastfd; j++) {
if (j != i) {
write(j, recv_buf, strlen(recv_buf));
}
}
}
}
}
}
}
}
int main(){
server();
return 0;
}
select函數(shù)的缺點(diǎn)
- 單個(gè)進(jìn)程所打開(kāi)的FD是有限制的,通過(guò)
FD_SETSIZE
設(shè)置化撕,默認(rèn)1024 - 每次調(diào)用 select几晤,都需要把 fd 集合從用戶態(tài)拷貝到內(nèi)核態(tài),這個(gè)開(kāi)銷在 fd 很多時(shí)會(huì)很大
- 每次調(diào)用select都需要將進(jìn)程加入到所有監(jiān)視socket的等待隊(duì)列植阴,每次喚醒都需要從每個(gè)隊(duì)列中移除
- select函數(shù)在每次調(diào)用之前都要對(duì)參數(shù)進(jìn)行重新設(shè)定蟹瘾,這樣做比較麻煩,而且會(huì)降低性能
- 進(jìn)程被喚醒后墙贱,程序并不知道哪些socket收到數(shù)據(jù)热芹,還需要遍歷一次
poll
poll本質(zhì)上和select沒(méi)有區(qū)別,它將用戶傳入的數(shù)組拷貝到內(nèi)核空間惨撇,然后查詢每個(gè)fd對(duì)應(yīng)的設(shè)備狀態(tài)伊脓, 但是它沒(méi)有最大連接數(shù)的限制,原因是它是基于鏈表來(lái)存儲(chǔ)的
poll函數(shù)接口
#include <poll.h>
// 數(shù)據(jù)結(jié)構(gòu)
struct pollfd {
int fd; // 需要監(jiān)視的文件描述符
short events; // 需要內(nèi)核監(jiān)視的事件
short revents; // 實(shí)際發(fā)生的事件魁衙,1:表示有事件發(fā)生报腔,0:沒(méi)有事件發(fā)生
};
// 阻塞方法
int poll(struct pollfd fds[], // 需要監(jiān)聽(tīng)的文件描述符列表
nfds_t nfds, // 文件描述符個(gè)數(shù)
int timeout // 超時(shí)時(shí)間
);
poll示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>
#include <unistd.h>
#include <sys/time.h>
#define MAX_POLLFD_LEN 4096
#define PORT 9108
void server() {
// 創(chuàng)建socket連接
int lfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in my_addr;
bzero(&my_addr, sizeof(my_addr));
my_addr.sin_family = AF_INET; // ipv4
my_addr.sin_port = htons(PORT);
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// 綁定端口
bind(lfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
// 監(jiān)聽(tīng)連接請(qǐng)求
listen(lfd, 128);
printf("listen client @port=%d...\n",PORT);
// 定義pollfd對(duì)象
struct pollfd fds[MAX_POLLFD_LEN];
memset(fds, 0, sizeof(fds));
// 添加socket服務(wù)監(jiān)聽(tīng)
fds[0].fd = lfd;
fds[0].events = POLLIN;
int nfds = 1;
int i;
for(i = 1; i < MAX_POLLFD_LEN; i++) {
fds[i].fd = -1;
}
int maxFds = 0;
printf("準(zhǔn)備進(jìn)入while循環(huán)\n");
while (1) {
printf("阻塞中, [maxFds=%d]...\n", maxFds);
int nready = poll(fds, maxFds + 1, -1);
switch (nready) {
case 0 :
printf("select time out ......\n");
break;
case -1 :
perror("select error \n");
break;
default:
// 監(jiān)聽(tīng)到新的客戶端連接
if (fds[0].revents & POLLIN) {
struct sockaddr_in client_addr;
socklen_t cliaddr_len = sizeof(client_addr);
char cli_ip[INET_ADDRSTRLEN] = "";
// 肯定有連接不會(huì)阻塞
int clientfd = accept(lfd, (struct sockaddr*)&client_addr, &cliaddr_len);
inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
printf("----------------------------------------------\n");
printf("client ip=%s,port=%d\n", cli_ip, ntohs(client_addr.sin_port));
// 將clientfd加入讀集合
int j;
for (j = 1; j < MAX_POLLFD_LEN; ++j) {
if (fds[j].fd < 0) {
fds[j].fd = clientfd;
fds[j].events = POLLIN;
printf("添加客戶端成功...\n");
maxFds++;
break;
}
if(j == MAX_POLLFD_LEN){
printf("too many clients");
exit(1);
}
}
if(--nready <= 0) {
continue;
}
}
int i;
printf("maxFds=%d\n", maxFds);
for (i = 1; i <= maxFds; i++) {
printf("i=%d\n", i);
// 處理讀事件
if (fds[i].revents & POLLIN) {
int sockfd = fds[i].fd;
char recv_buf[512] = "";
int rs = read(sockfd, recv_buf, sizeof(recv_buf));
if (rs == 0) {
close(sockfd);
fds[i].fd = -1;
} else {
printf("%s\n",recv_buf);
// 給每一個(gè)服務(wù)端寫(xiě)數(shù)據(jù)
int j;
for (j = 1;j <= maxFds; j++) {
if (j != i) {
write(fds[j].fd, recv_buf, strlen(recv_buf));
}
}
}
}
}
}
}
}
int main(){
server();
return 0;
}
epoll
epoll可以理解為event pool,不同與select剖淀、poll的輪詢機(jī)制纯蛾,epoll采用的是事件驅(qū)動(dòng)機(jī)制,每個(gè)fd上有注冊(cè)有回調(diào)函數(shù)纵隔,當(dāng)網(wǎng)卡接收到數(shù)據(jù)時(shí)會(huì)回調(diào)該函數(shù)翻诉,同時(shí)將該fd的引用放入rdlist就緒列表中炮姨。
當(dāng)調(diào)用epoll_wait檢查是否有事件發(fā)生時(shí),只需要檢查eventpoll對(duì)象中的rdlist雙鏈表中是否有epitem元素即可碰煌。如果rdlist不為空舒岸,則把發(fā)生的事件復(fù)制到用戶態(tài),同時(shí)將事件數(shù)量返回給用戶芦圾。
epoll函數(shù)的接口定義
#include <sys/epoll.h>
// 數(shù)據(jù)結(jié)構(gòu)
// 每一個(gè)epoll對(duì)象都有一個(gè)獨(dú)立的eventpoll結(jié)構(gòu)體
// 用于存放通過(guò)epoll_ctl方法向epoll對(duì)象中添加進(jìn)來(lái)的事件
// epoll_wait檢查是否有事件發(fā)生時(shí)蛾派,只需要檢查eventpoll對(duì)象中的rdlist雙鏈表中是否有epitem元素即可
struct eventpoll {
/*紅黑樹(shù)的根節(jié)點(diǎn),這顆樹(shù)中存儲(chǔ)著所有添加到epoll中的需要監(jiān)控的事件*/
struct rb_root rbr;
/*雙鏈表中則存放著將要通過(guò)epoll_wait返回給用戶的滿足條件的事件*/
struct list_head rdlist;
};
// API
// 內(nèi)核中間加一個(gè) ep 對(duì)象个少,把所有需要監(jiān)聽(tīng)的socket都放到ep對(duì)象中
int epoll_create(int size);
// epoll_ctl 負(fù)責(zé)把 socket 增加洪乍、刪除到內(nèi)核紅黑樹(shù)
int epoll_ctl(int epfd, // 創(chuàng)建的ep對(duì)象
int op, // 操作類型 新增、刪除等
int fd, // 要操作的對(duì)象
struct epoll_event *event // 事件
);
// epoll_wait 負(fù)責(zé)檢測(cè)可讀隊(duì)列夜焦,沒(méi)有可讀 socket 則阻塞進(jìn)程
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
epoll執(zhí)行流程
- 調(diào)用epoll_create()創(chuàng)建一個(gè)ep對(duì)象壳澳,即紅黑樹(shù)的根節(jié)點(diǎn),返回一個(gè)文件句柄
- 調(diào)用epoll_ctl()向這個(gè)ep對(duì)象(紅黑樹(shù))中添加茫经、刪除钾埂、修改感興趣的事件
- 調(diào)用epoll_wait()等待,當(dāng)有事件發(fā)生時(shí)網(wǎng)卡驅(qū)動(dòng)會(huì)調(diào)用fd上注冊(cè)的函數(shù)并將該fd添加到rdlist中科平,解除阻塞
示例代碼
#include<stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
void server() {
// 創(chuàng)建socket連接
int lfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in my_addr;
bzero(&my_addr, sizeof(my_addr));
my_addr.sin_family = AF_INET; // ipv4
my_addr.sin_port = htons(8088);
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// 綁定端口
bind(lfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
// 監(jiān)聽(tīng)連接請(qǐng)求
listen(lfd, 128);
printf("listen client @port=%d...\n", 8088);
int epct, i;
struct epoll_event event;
struct epoll_event events[100];
memset(events, 0, 100 * sizeof(struct epoll_event));
int epfd = epoll_create(1);
event.data.fd = lfd;
event.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &event);
while (1) {
printf("阻塞中....\n");
int nready = epoll_wait(epfd, events, 20, -1);
int i;
for (i = 0; i < nready; ++i) {
// 監(jiān)聽(tīng)到新的客戶端連接
if (events[i].data.fd == lfd) {
struct sockaddr_in client_addr;
socklen_t cliaddr_len = sizeof(client_addr);
char cli_ip[INET_ADDRSTRLEN] = "";
// 肯定有連接不會(huì)阻塞
int clientfd = accept(lfd, (struct sockaddr*)&client_addr, &cliaddr_len);
inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
event.data.fd = clientfd;
event.events = EPOLLIN | EPOLLET;
epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &event);
printf("----------------------------------------------\n");
printf("client ip=%s,port=%d\n", cli_ip, ntohs(client_addr.sin_port));
} else {
char recv_buf[512] = "";
int rs = read(events[i].data.fd, recv_buf, sizeof(recv_buf));
if (rs < 0) {
close(events[i].data.fd);
epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, &event);
continue;
}
printf("%s\n",recv_buf);
}
}
}
}
int main(){
server();
return 0;
}
epoll總結(jié)
- EPOLL支持的最大文件描述符上限是整個(gè)系統(tǒng)最大可打開(kāi)的文件數(shù)目, 1G內(nèi)存理論上最大創(chuàng)建10萬(wàn)個(gè)文件描述符
- 每個(gè)文件描述符上都有一個(gè)callback函數(shù),當(dāng)socket有事件發(fā)生時(shí)會(huì)回調(diào)這個(gè)函數(shù)將該fd的引用添加到就緒列表中姜性,select和poll并不會(huì)明確指出是哪些文件描述符就緒瞪慧,而epoll會(huì)。造成的區(qū)別就是部念,系統(tǒng)調(diào)用返回后弃酌,調(diào)用select和poll的程序需要遍歷監(jiān)聽(tīng)的整個(gè)文件描述符找到是誰(shuí)處于就緒,而epoll則直接處理即可
- select儡炼、poll采用輪詢的方式來(lái)檢查文件描述符是否處于就緒態(tài)妓湘,而epoll采用回調(diào)機(jī)制。造成的結(jié)果就是乌询,隨著fd的增加榜贴,select和poll的效率會(huì)線性降低,而epoll不會(huì)受到太大影響妹田,除非活躍的socket很多