1. poll詳解
函數(shù)原型
intpoll(struct?pollfd *fd,?nfds_t?nfds,?int?timeout);
函數(shù)參數(shù)
fd:數(shù)組的地址,struct pollfd all[120]; 其中struct pollfd結(jié)構(gòu)體如下
structpollfd {
int? ?fd; ? ? ? ??/* 文件描述符 */
short?events; ? ??/* 等待的事件 */
short?revents; ? ?/* 實(shí)際發(fā)生的事件 */
? ? };
結(jié)構(gòu)體紅各項(xiàng)含義如下:
文件描述符fd:表示要堅(jiān)持測的fd,通過 open("a.txt", O_wronly | O_append); 獲得。
events:要等待的事件
revents:實(shí)際發(fā)生的事件,它是內(nèi)核給的反饋,在select的時(shí)候,會(huì)有一個(gè)備份來供內(nèi)核修改并傳出。
nfds:數(shù)組的最大長度, 數(shù)組中最后一個(gè)使用的元素下標(biāo)+1
內(nèi)核會(huì)輪詢檢測fd數(shù)組的每個(gè)文件描述符
timeout:
1:永久阻塞
0:調(diào)用完成立即返回
>0:等待的時(shí)長毫秒
函數(shù)返回值:IO發(fā)生變化的文件描述符的個(gè)數(shù)。
2. epoll詳解
(1)API介紹
intepoll_create(int?size);
函數(shù)功能:生成一個(gè)epoll專用的文件描述符,實(shí)際上就是生成一個(gè)epoll樹的根結(jié)點(diǎn)。
函數(shù)參數(shù):size,epoll樹上能掛的最大文件描述符數(shù)量。表示我想在這個(gè)樹節(jié)點(diǎn)上掛size個(gè)節(jié)點(diǎn),假如實(shí)際上的節(jié)點(diǎn)大于size的話epoll會(huì)自動(dòng)擴(kuò)展,所以這個(gè)大小可以隨便傳,不用太在意。但是這個(gè)擴(kuò)展也是有上限的,如果電腦內(nèi)存是1G,那么擴(kuò)展的上限是10萬(2G就是20萬。。。通過加內(nèi)存可以擴(kuò)大上限)。
函數(shù)返回值:函數(shù)返回值是樹的根節(jié)點(diǎn),在后面用到epft參數(shù)的時(shí)候,都是指這個(gè)返回值,也就是樹的根節(jié)點(diǎn)。
intepoll_ctl(int?epfd,?int?op,?int?fd,?struct?epoll_event *event);
函數(shù)功能:用于控制某個(gè)epoll文件描述符事件,可以注冊(cè)、修改、刪除。
函數(shù)參數(shù):
epfd:epoll_create()函數(shù)生成的專用文件描述符。
op:
EPOLL_CTL_ADD ? ? ? —— ?注冊(cè)
EPOLL_CTL_MOD ? ? ?—— ?修改
EPOLL_CTL_DEL ? ? ? ?—— ?刪除
fd:關(guān)聯(lián)的文件描述符
event:告訴內(nèi)核要監(jiān)聽什么事件
EPOLLIN ? ? —— 讀
EPOLLOUT —— 寫
EPOLLERR ?—— 異常
structepoll_event {/* 該結(jié)構(gòu)體主要存放和fd有關(guān)的信息 */
uint32_t? ? ?events; ? ? ? ? ? ? ? ? ? ? ? ? ?
epoll_data_t?data;?
? ? ? };
typedefunion epoll_data {
void? ? ? ? ?*ptr;
int? ? ? ? ? fd;
uint32_t? ? ?u32;
uint64_t? ? ?u64;
? ? ? }?epoll_data_t;
epoll_data_t是一個(gè)聯(lián)合體union,四個(gè)成員共用同一塊內(nèi)存,也就是說四個(gè)成員我們只能用一個(gè),一般情況下我們用fd,這個(gè)fd實(shí)際上就是epoll_ctl()函數(shù)的第三個(gè)參數(shù)fd。
如果我們想在epoll樹上掛載更多信息,而不僅僅是fd文件描述符的話,我們可以把更多信息封裝在結(jié)構(gòu)體中,并把該結(jié)構(gòu)體傳給epoll_data_t結(jié)構(gòu)體的ptr指針,這樣就可以在epoll樹上掛載和fd有關(guān)的更多信息。
structsockInfo
? ? ? ? {
int? ? ? ? ?fd;
structsockaddr_inaddr;
? ? ? ? };
比如說,要獲取發(fā)生變化的fd對(duì)應(yīng)的client的IP和port,就可以利用指針ptr,這樣的話聯(lián)合epoll_data_t中的fd就不能用了,我們把文件描述符傳給sockInfo的fd即可完成fd信息的掛載。
intepoll_wait(
int?epfd,
struct?epoll_event* events, ?/* 結(jié)構(gòu)體數(shù)組 */
int?maxevents,
int?timeout
);
函數(shù)功能:等待IO事件發(fā)生(可以設(shè)置阻塞),epoll_wait()函數(shù)相當(dāng)于前面講的select()或poll()函數(shù),表示委托內(nèi)核去進(jìn)行檢測。epoll_event通過返回值和傳出參數(shù)events來實(shí)現(xiàn)把哪幾個(gè)fd發(fā)生變化告訴server進(jìn)程的目的。首先,每當(dāng)有fd變化,就把這個(gè)fd對(duì)應(yīng)的樹節(jié)點(diǎn)拷貝到events數(shù)組中,最后,有幾個(gè)fd變化,就返回幾。這樣只要根據(jù)返回值和參數(shù)events就可以遍歷出所有變化的fd以及相關(guān)信息。
函數(shù)參數(shù):
epfd:要檢測的句柄
events:用于回傳待處理事件的數(shù)組。它是一個(gè)傳出參數(shù),需要提前分配內(nèi)存,哪個(gè)fd發(fā)生變化了,就把哪個(gè)fd的樹節(jié)點(diǎn)(struct epoll_event)拷貝一份放到這個(gè)數(shù)組中。這樣epoll就能返回是哪個(gè)fd發(fā)生了變化。
maxevents:告訴內(nèi)核events的大小,因?yàn)閮?nèi)核要把發(fā)生變化的fd對(duì)應(yīng)的樹節(jié)點(diǎn)拷貝到數(shù)組中,所以要知道數(shù)組大小。
timeout:為超時(shí)時(shí)間
1:永久阻塞
0:立即返回
>0
函數(shù)返回值:有多少個(gè)fd發(fā)生了變化就返回幾(變化的fd信息存在events數(shù)組中)。
(2)epoll樹
(3)epoll模型
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<ctype.h>
#include<sys/epoll.h>
intmain(int?argc, constchar* argv[])
{
if(argc <?2)
? ? {
printf("eg: ./a.out portn");
exit(1);
? ? }
structsockaddr_inserv_addr;
socklen_t?serv_len =?sizeof(serv_addr);
int?port =?atoi(argv[1]);?//字符串轉(zhuǎn)整形值
// 創(chuàng)建套接字
int?lfd =?socket(AF_INET, SOCK_STREAM,?0);
// 初始化服務(wù)器 sockaddr_in?
memset(&serv_addr,?0, serv_len);
? ? serv_addr.sin_family = AF_INET;?// 地址族?
? ? serv_addr.sin_addr.s_addr =?htonl(INADDR_ANY);?// 監(jiān)聽本機(jī)所有的IP
? ? serv_addr.sin_port =?htons(port);?// 設(shè)置端口?
// 綁定IP和端口
? ??bind(lfd, (struct?sockaddr*)&serv_addr, serv_len);
// 設(shè)置同時(shí)監(jiān)聽的最大個(gè)數(shù)
? ??listen(lfd,?36);
printf("Start accept ......n");
structsockaddr_inclient_addr;
socklen_t?cli_len =?sizeof(client_addr);
// 創(chuàng)建epoll樹根節(jié)點(diǎn)
int?epfd =?epoll_create(2000);
// 初始化epoll樹
structepoll_eventev;
? ? ev.events = EPOLLIN;
? ? ev.data.fd = lfd;
? ??epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
//存放發(fā)生變化的fd對(duì)應(yīng)的樹節(jié)點(diǎn)
structepoll_eventall[2000];
while(1)
? ? {
// 使用epoll通知內(nèi)核fd 文件IO檢測
int?ret =?epoll_wait(epfd, all,?sizeof(all)/sizeof(all[0]),?-1);
// 遍歷all數(shù)組中的前ret個(gè)元素 //ret表示有幾個(gè)變化的fd,變化的fd都存在all數(shù)組中
for(int?i=0; i<ret; ++i)
? ? ? ? {
int?fd = all[i].data.fd;
// 判斷是否有新連接
if(fd == lfd)
? ? ? ? ? ? {
// 接受連接請(qǐng)求 // accept不阻塞,因?yàn)橐呀?jīng)有連接
int?cfd =?accept(lfd, (struct?sockaddr*)&client_addr, &cli_len);
if(cfd ==?-1)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ??perror("accept error");
exit(1);
? ? ? ? ? ? ? ? }
// 將新得到的cfd掛到樹上
structepoll_eventtemp;
? ? ? ? ? ? ? ? temp.events = EPOLLIN;?//檢測cfd對(duì)應(yīng)的讀緩沖區(qū),是否有數(shù)據(jù)傳入
? ? ? ? ? ? ? ? temp.data.fd = cfd;
? ? ? ? ? ? ? ??epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &temp);
// 打印客戶端信息
char?ip[64] = {0};
printf("New Client IP: %s, Port: %dn",
? ? ? ? ? ? ? ??inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip,?sizeof(ip)),
? ? ? ? ? ? ? ? ? ? ? ??ntohs(client_addr.sin_port));
? ? ? ? ? ? }
else
? ? ? ? ? ? {
// 處理已經(jīng)連接的客戶端發(fā)送過來的數(shù)據(jù)
if(!all[i].events & EPOLLIN)?//只處理讀事件
? ? ? ? ? ? ? ? {
continue;
? ? ? ? ? ? ? ? }
/*
? ? ? ? ? ? ? ? 假如說client發(fā)送過了100個(gè)數(shù)據(jù),也就是serve的read緩沖區(qū)有100個(gè)數(shù)據(jù),
? ? ? ? ? ? ? ? 但是調(diào)用recv函數(shù)的時(shí)候只能讀50個(gè)數(shù)據(jù),而本次循環(huán)只調(diào)用了一次recv,
? ? ? ? ? ? ? ? 那么只能下次循環(huán)再讀剩余的50個(gè)數(shù)據(jù),所以下次循環(huán)檢測的時(shí)候,
? ? ? ? ? ? ? ? epoll_wait還是會(huì)返回,因?yàn)榫彌_區(qū)還是剩余數(shù)據(jù)。這就是水平觸發(fā)模式。
? ? ? ? ? ? ? ? 這樣的話雖然client只發(fā)了1次,但是epoll_wait會(huì)通知兩次server去讀數(shù)據(jù)。
? ? ? ? */
// 讀數(shù)據(jù)
char?buf[1024] = {0};
int?len =?recv(fd, buf,?sizeof(buf),?0);
if(len ==?-1)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ??perror("recv error");
exit(1);
? ? ? ? ? ? ? ? }
elseif(len ==?0)
? ? ? ? ? ? ? ? {
printf("client disconnected ....n");
//close(fd);
// fd從epoll樹上刪除
? ? ? ? ? ? ? ? ? ? ret =?epoll_ctl(epfd, EPOLL_CTL_DEL, fd,?NULL);
// 掛樹的時(shí)候需要ev,把ev掛在樹上刪除寫NULL就行了
if(ret ==?-1)
? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ??perror("epoll_ctl del error");
exit(1);
? ? ? ? ? ? ? }
? ? ? ? ? ? ? ?close(fd);
? ? ? ? ? ? ? ? }
else
? ? ? ? ? ? ? ? {
printf(" recv buf: %sn", buf);
? ? ? ? ? ? ? ? ? ??write(fd, buf, len);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ??close(lfd);
return0;
}
epoll維護(hù)的紅黑樹是存在一個(gè)共享內(nèi)存中,內(nèi)核和用戶都可以通過操作這個(gè)共享內(nèi)存來操作樹,不需要內(nèi)核態(tài)和用戶態(tài)的切換,也不需要兩種狀態(tài)之間的數(shù)據(jù)拷貝,所以效率更高。
(4)epoll的三種工作模式
水平觸發(fā)模式 ? - (根據(jù)讀來解釋)
只要fd對(duì)應(yīng)的緩沖區(qū)有數(shù)據(jù),epoll_wait就會(huì)返回
返回的次數(shù)與發(fā)送數(shù)據(jù)的次數(shù)沒有關(guān)系
epoll默認(rèn)的工作模式
邊沿觸發(fā)模式 - ET
fd - 默認(rèn)阻塞屬性
客戶端給server發(fā)數(shù)據(jù):
發(fā)一次數(shù)據(jù)server 的 epoll_wait就返回一次
不在乎數(shù)據(jù)是否讀完
如果讀不完,如何把數(shù)據(jù)全部讀出來?
while(recv());
數(shù)據(jù)讀完之后recv會(huì)阻塞
解決阻塞問題 —— 設(shè)置非阻塞fd
對(duì)于epoll_wait()來說,epoll_wait 調(diào)用次數(shù)越多, 系統(tǒng)的開銷越大。
水平觸發(fā)模式會(huì)多次返回,只要server的read緩沖區(qū)有數(shù)據(jù),epoll_wait就返回,也就會(huì)通知server去讀數(shù)據(jù),那么在循環(huán)檢測的時(shí)候,只要server的read緩沖區(qū)有數(shù)據(jù),epoll_wait就會(huì)多次調(diào)用,多次返回,并通知server去讀數(shù)據(jù);假如說client發(fā)送過了100個(gè)數(shù)據(jù),也就是serve的read緩沖區(qū)有100個(gè)數(shù)據(jù),但是調(diào)用recv函數(shù)的時(shí)候只能讀50個(gè)數(shù)據(jù),而本次循環(huán)只調(diào)用了一次recv,那么只能下次循環(huán)再讀剩余的50個(gè)數(shù)據(jù),所以下次循環(huán)檢測的時(shí)候,epoll_wait還是會(huì)返回,因?yàn)榫彌_區(qū)還是剩余數(shù)據(jù)。這就是水平觸發(fā)模式。這樣的話雖然client只發(fā)了1次,但是epoll_wait會(huì)通知兩次server去讀數(shù)據(jù)。
—— (printf函數(shù)是標(biāo)準(zhǔn)C庫函數(shù),C庫函數(shù)都有一個(gè)默認(rèn)緩沖區(qū),printf的大小是8K。printf函數(shù)是行緩沖,使用printf函數(shù)的時(shí)候,如果不加 n 會(huì)默認(rèn)等到寫滿的時(shí)候才打印內(nèi)容,加 n 會(huì)強(qiáng)制把緩沖區(qū)的內(nèi)容打印出來。另外 表示結(jié)束,不加 就會(huì)一直輸出直到遇到 ,用write(STDOUT_FILENO)替代printf函數(shù)就可以解決這些問題。)
邊沿觸發(fā)模式,client發(fā)一次數(shù)據(jù)epoll_wait只返回一次,也就只讀一次,這樣的話server的read緩沖區(qū)可能會(huì)有很多數(shù)據(jù)堆積,server讀數(shù)據(jù)的時(shí)候可能讀到的是上一次剩余的數(shù)據(jù),并且只有client發(fā)的時(shí)候,epoll_wait才會(huì)通知server去讀數(shù)據(jù),邊沿觸發(fā)模式盡可能減少了epoll_wait的調(diào)用次數(shù),缺點(diǎn)是數(shù)據(jù)有可能讀不完導(dǎo)致堆積;
邊沿非阻塞觸發(fā)
效率最高
如何設(shè)置非阻塞
open()
設(shè)置flags
必須 O_WDRW | O_NONBLOCK
終端文件: /dev/tty
fcntl
int flag = fcntl(fd, F_GETFL);
flag |= O_NONBLOCK;
fcntl(fd, F_SETFL, flag);
如何將緩沖區(qū)的全部數(shù)據(jù)都讀出?
while(recv() >?0)
? ? ?{
? ? ??printf();
? ? ?}
當(dāng)緩沖區(qū)數(shù)據(jù)讀完之后, 返回值是否為0?
阻塞狀態(tài)
數(shù)據(jù)讀完之后,recv阻塞
非阻塞狀態(tài)
強(qiáng)行讀了一個(gè)沒有數(shù)據(jù)的緩沖區(qū)(fd),數(shù)據(jù)已經(jīng)被讀完了,因?yàn)槭欠亲枞?,所以在while循環(huán)中recv還要繼續(xù)讀,導(dǎo)致返回-1
判斷 errno == EAGAIN
示例
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<ctype.h>
#include<sys/epoll.h>
#include<fcntl.h>
#include<errno.h>
intmain(int?argc, constchar* argv[])
{
if(argc <?2)
? ? {
printf("eg: ./a.out portn");
exit(1);
? ? }
structsockaddr_inserv_addr;
socklen_t?serv_len =?sizeof(serv_addr);
int?port =?atoi(argv[1]);
// 創(chuàng)建套接字
int?lfd =?socket(AF_INET, SOCK_STREAM,?0);
// 初始化服務(wù)器 sockaddr_in?
memset(&serv_addr,?0, serv_len);
? ? serv_addr.sin_family = AF_INET; ? ? ? ? ? ? ? ? ??// 地址族?
? ? serv_addr.sin_addr.s_addr =?htonl(INADDR_ANY); ? ?// 監(jiān)聽本機(jī)所有的IP
? ? serv_addr.sin_port =?htons(port); ? ? ? ? ? ?// 設(shè)置端口?
// 綁定IP和端口
? ??bind(lfd, (struct?sockaddr*)&serv_addr, serv_len);
// 設(shè)置同時(shí)監(jiān)聽的最大個(gè)數(shù)
? ??listen(lfd,?36);
printf("Start accept ......n");
structsockaddr_inclient_addr;
socklen_t?cli_len =?sizeof(client_addr);
// 創(chuàng)建epoll樹根節(jié)點(diǎn)
int?epfd =?epoll_create(2000);
// 初始化epoll樹
structepoll_eventev;
// 設(shè)置邊沿觸發(fā)
? ? ev.events = EPOLLIN;?//監(jiān)聽的文件描述符沒必要邊沿觸發(fā),主要是通信的cfd
? ? ev.data.fd = lfd;
? ??epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
structepoll_eventall[2000];
while(1)
? ? {
// 使用epoll通知內(nèi)核fd 文件IO檢測
int?ret =?epoll_wait(epfd, all,?sizeof(all)/sizeof(all[0]),?-1);
printf("================== epoll_wait =============n");
// 遍歷all數(shù)組中的前ret個(gè)元素
for(int?i=0; i<ret; ++i)
? ? ? ? {
int?fd = all[i].data.fd;
// 判斷是否有新連接
if(fd == lfd)
? ? ? ? ? ? {
// 接受連接請(qǐng)求
int?cfd =?accept(lfd, (struct?sockaddr*)&client_addr, &cli_len);
if(cfd ==?-1)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ??perror("accept error");
exit(1);
? ? ? ? ? ? ? ? }
// 設(shè)置文件cfd為非阻塞模式
int?flag =?fcntl(cfd, F_GETFL);
? ? ? ? ? ? ? ? flag |= O_NONBLOCK;
? ? ? ? ? ? ? ??fcntl(cfd, F_SETFL, flag);
// 將新得到的cfd掛到樹上
structepoll_eventtemp;
// 設(shè)置邊沿觸發(fā)
? ? ? ? ? ? ? ? temp.events = EPOLLIN | EPOLLET;
? ? ? ? ? ? ? ? temp.data.fd = cfd;
? ? ? ? ? ? ? ??epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &temp);
// 打印客戶端信息
char?ip[64] = {0};
printf("New Client IP: %s, Port: %dn",
? ? ? ? ? ? ? ??inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip,?sizeof(ip)),
? ? ? ? ? ? ? ??ntohs(client_addr.sin_port));
? ? ? ? ? ? }
else
? ? ? ? ? ? {
// 處理已經(jīng)連接的客戶端發(fā)送過來的數(shù)據(jù)
if(!all[i].events & EPOLLIN)?
? ? ? ? ? ? ? ? {
continue;
? ? ? ? ? ? ? ? }
// 讀數(shù)據(jù)
char?buf[5] = {0};
int?len;
// 循環(huán)讀數(shù)據(jù)
while( (len =?recv(fd, buf,?sizeof(buf),?0)) >?0?)
? ? ? ? ? ? ? ? {
// 數(shù)據(jù)打印到終端
//不要用printf,因?yàn)閜rintf如果找不到 n 字符會(huì)出現(xiàn)亂碼,打印不出來等問題
? ? ? ? ? ? ? ? ? ??write(STDOUT_FILENO, buf, len);
// 發(fā)送給客戶端
? ? ? ? ? ? ? ? ? ??send(fd, buf, len,?0);
? ? ? ? ? ? ? ? }
if(len ==?0)
? ? ? ? ? ? ? ? {
printf("客戶端斷開了連接n");
? ? ? ? ? ? ? ? ? ? ret =?epoll_ctl(epfd, EPOLL_CTL_DEL, fd,?NULL);
if(ret ==?-1)
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ??perror("epoll_ctl - del error");
exit(1);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ??close(fd);
? ? ? ? ? ? ? ? }
elseif(len ==?-1)
? ? ? ? ? ? ? ? {
//數(shù)據(jù)已經(jīng)被讀完了,因?yàn)槭欠亲枞栽趙hile循環(huán)中recv還要繼續(xù)讀,導(dǎo)致返回-1
if(errno == EAGAIN)
? ? ? ? ? ? ? ? ? ? {
printf("緩沖區(qū)數(shù)據(jù)已經(jīng)讀完n");
? ? ? ? ? ? ? ? ? ? }
else
? ? ? ? ? ? ? ? ? ? {
//這才是真正的recv錯(cuò)誤
printf("recv error----n");
exit(1);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
#if?0
if(len ==?-1)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ??perror("recv error");
exit(1);
? ? ? ? ? ? ? ? }
elseif(len ==?0)
? ? ? ? ? ? ? ? {
printf("client disconnected ....n");
// fd從epoll樹上刪除
? ? ? ? ? ? ? ? ? ? ret =?epoll_ctl(epfd, EPOLL_CTL_DEL, fd,?NULL);
if(ret ==?-1)
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ??perror("epoll_ctl - del error");
exit(1);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ??close(fd);
? ? ? ? ? ? ? ? }
else
? ? ? ? ? ? ? ? {
// printf(" recv buf: %sn", buf);
? ? ? ? ? ? ? ? ? ??write(STDOUT_FILENO, buf, len);
? ? ? ? ? ? ? ? ? ??write(fd, buf, len);
? ? ? ? ? ? ? ? }
#endif
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ??close(lfd);
return0;
}
5)文件描述符1024限制
對(duì)于select來說,無法突破文件描述符1024上限,因?yàn)閟elect是通過數(shù)組實(shí)現(xiàn)的。poll和epoll可以突破1024限制,poll是內(nèi)部鏈表實(shí)現(xiàn),而epoll是紅黑樹實(shí)現(xiàn)。
查看受計(jì)算機(jī)硬件限制的文件描述符上限可以通過下面命令
cat?/proc/sys/fs/file-max
同樣,我們也可以通過修改配置文件來修改這個(gè)上限,但是,我們?cè)诔绦蛑性O(shè)置的時(shí)候不能超過硬件限制的上限
vim /etc/security/limits.conf
- soft ?nofile ? ?8000 ? ? ?—— 也可以通過命令ulimit -n 2000來修改為2000
- hard ?nofile ? 8000 ? ? ?—— 硬件資源限制
修改后重啟系統(tǒng)即可起效。