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使用場景:客戶端連接量大,但是每個客戶端發送的數據量少,並且向服務器發送消息的次數很少
多線程使用場景:客戶端連接量小,每個客戶端發送的數據量大,且長時間連接發送消息

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM