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