epoll和ractor的粗淺理解
我們繼續上篇的文章繼續更新我們的代碼。
首先就是介紹一下epoll的三個函數。
- epoll_create
- epoll_ctl
- epoll_wait
如何去理解這3個函數,我是這樣去理解這個函數,
就像我們去取快遞一樣,之前的Select模型,是通過輪詢的方式一直去循環遍歷客戶端FD的列表,而EPOLL就相當于專門了一個快遞柜,會將有讀寫事件的FD放到快遞柜里面,而快遞員只需要去快遞柜進行取件和放件就可以了。
epoll_create函數就相當于我們添加了一個快遞柜在樓下,
epoll_ctl就相當于我們添加快遞或者取快遞在快遞柜中,
epoll_wait就相當于快遞員什么時候進行取件,什么時候取送件。
這就會有一些優點什么優點那?
- 不需要循環遍歷所有fd
- 每一次取就緒集合,在固定位置;
- 異步解耦
那么我們在下面看一下EPOLL實現的服務器的代碼,我會把對應的注釋標記到代碼上
#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<fcntl.h>
#include <unistd.h>
#include<sys/epoll.h>
#include <string.h>
#define BUFFER_LENGTH 128
#define EVENTS_LENGTH 128
char rbuffer[BUFFER_LENGTH] = {0};
char wbuffer[BUFFER_LENGTH] = {0};
int main()
{
unsigned char buffer[BUFFER_LENGTH]={0};
int ret=0;
//socket有兩個參數,第一個參數指定我們要使用IPV4,還是IPV6,第二個參數表明我們要使用套接字類型,這里我們使用的是流格式的套接字,第三個參數就是我們需要使用傳輸協議
//這里使用0,表示讓系統自動推導我們需要使用的傳輸協議。
int listenfd= socket(AF_INET,SOCK_STREAM,0);
//如果返回值為-1,說明我們創建SOCKET失敗,直接返回。
if (listenfd==-1)
{
return -1;
}
//我們需要綁定的信息
struct sockaddr_in serveraddr;
//使用IPV4
serveraddr.sin_family=AF_INET;
//我們需要綁定的IP地址,INADDR_ANY 就是0.0.0.0 ,就是所有網卡的所有IP段都可以連接到我們的創建的TCP服務器上。
serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);
//我們需要綁定的端口,這里我們綁定的端口為9999
serveraddr.sin_port=htons(9999);
//第一個參數我們創建的套接字,第二個是我們填寫的綁定信息,最后是我們的綁定信息結構體的大小。
if (-1==bind(listenfd,(const sockaddr*)&serveraddr,sizeof(serveraddr)))
{
return -2;
}
//監聽我們創建的套接字,請求的隊列數量,這里我們填寫為10個
listen(listenfd,10);
//定義客戶端的socket
//定義可讀序列和可寫序列
fd_set rfds,wfds,rset,wset;
//清空序列
FD_ZERO(&rfds);
//設置讀的序列
FD_SET(listenfd,&rfds);
//清空可寫的序列
FD_ZERO(&wfds);
int maxfd=listenfd;
//開始進行EPOLL的創建
int epfd = epoll_create(1);
struct epoll_event ev,events[EVENTS_LENGTH];
ev.events=EPOLLIN;
ev.data.fd=listenfd;
//添加我們的服務器通信的listenfd到EPFD中,
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
//接下來開始接受 我們的客戶端的連接請求
while (1)
{
//我們需要詳細講解一下這個函數的里面的各個參數的意義 ,以及它什么時候是阻塞的,什么時候是非阻塞的,
//第一個參數我們的EPFD的文件描述符,第二個我們的接收事件的緩沖器,第三個是我們事件數量的多少,最后一個參數就是我們等待的時長了。
//當是-1的時候就是一直等待連接的意思,沒有連接就會 一直被阻塞住,
//當是0的時候就是一直有連接直接返回的意思,
//當是大于0的數的時候,就是在輪詢查看是否有事件的時長,單位是MS。
int nready = epoll_wait(epfd,events,EVENTS_LENGTH,-1);
printf("----------%d\n",nready);
//開始遍歷我們的事件
int i =0;
for (int i = 0; i < nready; i++)
{
int clientfd=events[i].data.fd;
if (listenfd==clientfd)
{
//如果是我們的監聽的FD,說明是有客戶端連入的事件
struct sockaddr_in client;
socklen_t len=sizeof(client);
//接受客戶端的請求,
int connfd=accept(listenfd,(struct sockaddr*)&client,&len);
if (connfd==-1)
{
break;
}
printf("accept:%d\n",connfd);
//增加到我們的快遞柜中
ev.events=EPOLLIN;
ev.data.fd=connfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
//如果是讀的請求
}
else if (events[i].events & EPOLLIN)
{
//如果客戶端在線可以接受到消息
int n=recv(clientfd,rbuffer,BUFFER_LENGTH,0);
if (n>0)
{
rbuffer[n]='\0';
printf("clientfd :%d recv: %s ,n:%d\n",clientfd,rbuffer,n);
memcpy(wbuffer,rbuffer,BUFFER_LENGTH);
ev.events=EPOLLOUT;
ev.data.fd=clientfd;
epoll_ctl(epfd,EPOLL_CTL_MOD,clientfd,&ev);
//客戶端退出的時候會觸發
}
else
{
ev.data.fd=clientfd;
epoll_ctl(epfd,EPOLL_CTL_DEL,clientfd,&ev);
}
}
else if(events[i].events & EPOLLOUT)
{
int sent = send(clientfd, wbuffer, BUFFER_LENGTH, 0); //
printf("sent: %d\n", sent);
ev.events = EPOLLIN;
ev.data.fd = clientfd;
epoll_ctl(epfd, EPOLL_CTL_MOD, clientfd, &ev);
}
}
}
return 0;
}
上面我已經把對應的注釋以及注意的點已經寫在了代碼的上面,
這里我們還要說一個問題,就是EPOLLLT和EPOLLET的問題
LT模式
對于讀事件 EPOLLIN,只要socket上有未讀完的數據,EPOLLIN 就會一直觸發,直到我們的數據接收完畢后才會停止;對于寫事件 EPOLLOUT,只要socket可寫,EPOLLOUT 就會一直觸發。
在這種模式下,大家會認為讀數據會簡單一些,因為即使數據沒有讀完,那么下次調用epoll_wait()時,它還會通知你在上沒讀完的文件描述符上繼續讀,也就是人們常說的這種模式不用擔心會丟失數據。
而寫數據時,因為使用 LT 模式會一直觸發 EPOLLOUT 事件,那么如果代碼實現依賴于可寫事件觸發去發送數據,一定要在數據發送完之后移除檢測可寫事件,避免沒有數據發送時無意義的觸發。
ET模式
對于讀事件 EPOLLIN,只有socket上的數據從無到有,EPOLLIN 才會觸發;對于寫事件 EPOLLOUT,只有在socket寫緩沖區從不可寫變為可寫,EPOLLOUT 才會觸發(剛剛添加事件完成調用epoll_wait時或者緩沖區從滿到不滿)
這種模式聽起來清爽了很多,只有狀態變化時才會通知,通知的次數少了自然也會引發一些問題,比如觸發讀事件后必須把數據收取干凈,因為你不一定有下一次機會再收取數據了,即使不采用一次讀取干凈的方式,也要把這個激活狀態記下來,后續接著處理,否則如果數據殘留到下一次消息來到時就會造成延遲現象。
這種模式下寫事件觸發后,后續就不會再觸發了,如果還需要下一次的寫事件觸發來驅動發送數據,就需要再次注冊一次檢測可寫事件。
其次我們代碼還有一個問題,就是公用同一個緩沖區的問題,這個問題,我們后面的文章再去解決。
推薦一個零聲學院免費教程,個人覺得老師講得不錯,
分享給大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,
fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,
TCP/IP,協程,DPDK等技術內容,點擊立即學習:
服務器
音視頻
dpdk
Linux內核

浙公網安備 33010602011771號