Linux 網絡編程七(非阻塞socket:epoll--select)

阻塞socket
--阻塞調用是指調用結果返回以前,當前線程會被掛起。函數只有在獲得結果以後纔會返回。
--對於文件操做 read,fread函數調用會將線程阻塞(日常使用read感受不出來阻塞,
由於之前的程序read都是從本機上讀取數據,因此速度很快,沒法感受出來,可是從網絡上讀取就會有阻塞現象)。
--對於socket來說,accept與recv、recvfrom函數調用會將線程阻塞。 --爲了不整個進程被阻塞後掛起,因此在阻塞模式下,每每須要採用多線程技術。 --一個進程中可併發的線程總數是有限的,在處理大量客戶端socket鏈接(好比上萬個client socket),經過線程併發處理socket處理socket並不方便,效率也不高。
非阻塞socket
--非阻塞調用是指調用馬上返回。
--在非阻塞模式下,accept與recv、recvfrom函數調用會馬上返回。
--在nonblocking狀態下調用accept函數,若是沒有客戶端socket鏈接請求,那麼accept函數返回-1,同時errno值爲EAGAIN或者EWOULDBLOCK,這兩個宏定義都爲整數11.
--在nonblocking狀態下調用recv、recvfrom函數,若是沒有數據,函數返回-1,同時errno值爲11(EINPROGRESS)。若是socket已經關閉,函數返回0.
--在nonblocking狀態下對一個已經關閉的socket調用send函數,將引起一個SIGPIPE信號,進程必須捕捉這個信號,由於SIGPIPE系統默認的處理方式是關閉進程。
fcntl函數調用
fcntl函數能夠將文件或者socket描述符設置爲阻塞或者非阻塞狀態
int fcntl(int fd,int cmd,.../*arg*/);
參數fd爲要設置的文件描述符或者socket。
參數cmd,F_GETFL爲獲得目前狀態,F_SETFL爲設置狀態。
宏定義O_NONBLOCK表明非阻塞,0表明阻塞。
成功返回值爲描述符當前狀態,失敗返回-1,而且設置errno。
//fcntl函數調用設置非阻塞socket
int opts=fcntl(st,F_GETFL);
if(opts<0)
{
    printf("fcntl failed ! error message :%s\n",strerror(errno));
    return -1;
}
opts=opts | O_NONBLOCK;
if(fcntl(st,F_SETFL,opts)<0)
{
    printf("fcntl failed ! error message :%s\n",strerror(errno));
        return -1;
}
//fcntl函數調用設置阻塞socket
if(fcntl(st,F_SETFL,0)<0)
{
    printf("fcntl failed ! error message :%s\n",strerror(errno));
    return -1;
}
場景解釋:如今將服務器端的st和客戶端傳到服務器端的client_socket都設置爲非阻塞,
可是這種設置針對的是服務器,因此在服務器端設置客戶端client_socket非阻塞,並不會影響客戶端的socket,客戶端的socket仍是阻塞的。

 

epoll的系統調用函數
--epoll_create    epoll_create用來建立一個epoll文件描述符。
--epoll_ctl       epoll_clt用來添加|修改|刪除須要偵聽的文件描述符及其事件
--epoll_wait      epoll_wait接收發生在被偵聽的描述符上的,用戶感興趣的IO事件
--epoll文件描述符用完後,須要用close關閉
--每次添加|修改|刪除文件描述符都須要調用epoll_ctl,因此要儘可能少的調用epoll_ctl
--epoll只適用與Linux 內核 2.6版本或者其以上的版本,並不適用於Unix和window
epoll_create
--int epoll_create(int size);
--epool_create建立一個epoll句柄
--參數size指定epoll所支持的最大句柄數
--函數成功會返回一個新的epoll句柄,以後的全部操做將經過這個句柄來進行操做,函數失敗返回-1,而且設置errno。
--在用完句柄以後,須要用close()來關閉着建立出來的epoll句柄。
epoll_ctl:
--int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);
--參數epfd是epoll_create()的返回值
--參數op表示動做,用三個宏來表示
    EPOLL_CTL_ADD:註冊新的fd到epfd中
    EPOLL_CTL_MOD:修改已經註冊的fd的監聽事件
    EPOLL_CTL_DEL:從epfd中刪除一個fd
--參數fd是須要監聽的socket描述符
--參數event通知內核須要監聽什麼事件
epoll_ctl()函數的第四個參數能夠是一個臨時變量,epoll彷佛會拷貝這個參數而不是直接使用

 

//union epoll_data共用體
typedef union epoll_data
{
    void *ptr;
    int fd;
    _uint32_t u32;
    _uint64_t u64;
}epoll_data_t;

//struct epoll_event結構
struct epoll_event
{
    _uint32_t events;/*Epoll events*/
    epoll_data_t data;/*User data variable */
};

 

events定義
--EPOLLIN     表示對應的文件描述符能夠讀(包括對端SOCKET正常關閉)
--EPOLLOUT    表示對應的文件描述符能夠寫
--EPOLLPRI    表示對應的文件描述符有緊急的數據可讀(這裏應該表示有帶外數據到來)
--EPOLLERR    表示對應的文件描述符發生錯誤;
--EPOLLHUP    表示對應的文件描述符被掛斷
--EPOLLET     將EPOLL設爲邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來講的
--EPOLLONESHOT    只監聽一次事件,當監聽完此次事件以後,若是還須要繼續監聽這個socket的話,須要再次把這個socket加入到EPOLL隊列裏
關於ET、LT兩種工做模式
LT(level triggered)是缺省的工做方式,而且同時支持block和no-block socket
--在LT模式中,內核通知一個文件描述符是否就緒了,而後能夠對這個就緒的fd進行IO操做
--若是你不做任何操做,內核仍是會繼續通知你的,因此這種模式編程出錯誤可能性要小一點。

ET(edge-triggered)是高速工做方式,只支持no-block socket
--在ET模式下,當描述符從未就緒變爲就緒時,內核經過epoll告訴你。
--ET模式會假設你知道文件描述符已經就緒,而且不會再爲那個文件描述符發送更多的就緒通知,知道你作了某些操做致使那個文件描述符再也不爲就緒狀態了
--若是一直不對這個fd做IO操做(從而致使它再次變成未就緒),內核不會發送更多的通知。

ET和LT的區別:
--LT事件不會丟棄,而是隻要讀buffer裏面有數據可讓用戶讀,則不斷的通知你
--ET則只在事件發生之時通知。能夠簡單理解爲LT是水平觸發,而ET則爲邊緣觸發。
epoll_wait
--int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);
--參數epfd是epoll_create()的返回值。
--參數events是一個epoll_event*指針,通常是一個事件數組,當epoll_wait這個函數操做成功以後,epoll_events裏面將存儲全部的讀寫事件。
--參數maxevents是當前須要監聽的全部socket句柄數。
--參數timeout是epoll_wait的超時,爲0的時候表示立刻返回,爲-1的時候表示一致等下去,直到有事件返回,正整數表示等這麼長的時間。
--通常若是網絡主循環是單獨的線程的話,能夠用-1來等,這樣能夠保證一些效率,若是是和主邏輯在同一線程的話,則能夠用0來保證主循環的效率。
--函數成功返回值是有消息的socket的數目,失敗返回-1,而且設置errno。

 

epoll_wait返回以後應該是一個循環,遍歷全部的事件。
epoll所能容納的文件描述符無上限,可是poll和select所容納的文件描述符最大隻能是1024

 

select
--int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct tomeval *timeout);
--參數nfds爲最大socket的值加1,(socket都是int類型)
--參數readfds是讀事件的socket集合
--參數writefds是寫事件的socket集合
--參數exceptfds是出錯事件的socket集合
--每一個事件數組都支持1024個文件描述符
--參數timeout若是爲NULL,表示永遠阻塞,若是是一個具體的時間類型,表示等待的事件。
--select也是阻塞的,只要放入readfds池中的socket有事件發生或者放入writefds池中的socket有事件發生
或者放入exceptfds池中的socket有事件發生,select函數都會馬上返回。readfds和epoll_wait中的事件數組很類似
不關心的事件數組能夠設置爲NULL
--當readfds、writefds、exceptfds都設置爲NULL,而且參數timeout設置了時間,那麼select函數的效果就會等同於sleep()函數
sleep()函數有必定延遲,可是select比sleep()更加精確,而且設置的時間範圍更加普遍,能夠精確到微秒。
--select最大支持1024個文件描述符
--void FD_ZERO(fd_set *set);    初始化(清空)一個select事件數組,不可使用memset()
--void FD_SET(int fd,fd_set *set);    在select事件數組裏添加一個文件描述符,添加劇復的socket對事件數組沒有影響
--void FD_CLR(int fd,fd_set *set);    從select事件數組中刪除一個文件描述符
--void FD_ISSET(int fd,fd_set *set);    判斷一個文件描述符是否在select的某個事件數組中    
--函數成功返回有消息的socket的數目,失敗返回-1,而且設置errno

 

epoll和select使用場景:客戶端鏈接量大,可是每一個客戶端發送的數據量少,而且向服務器發送消息的次數不多
多線程使用場景:客戶端鏈接量小,每一個客戶端發送的數據量大,且長時間鏈接發送消息
相關文章
相關標籤/搜索