學習了select之後,也有好一段時間了,但是一直沒有提起寫一篇關於select的部落格,大概也是因為自己那會還沒搞懂吧,這段時間在看《linux高效能伺服器程式設計》時,又看到i/o復用對於select,poll,epoll的用法例項和比較,又從頭看了一次之前寫的**,雖然是在老師的指導下寫的,但是印象還是很深刻的,現在看來,當時一頭霧水的寫,頂多記得乙個寫**的步驟,而並沒有明白為什麼和原理,甚至當初還因為檔案描述符集繞了半天。
如果你只是想看看**,github鏈結在這:
如果一味的講解原理的話,我自己也會睡著,所以我選擇從**入手,原理其實都隱藏在**裡,一行行的看**,看到疑惑或者不懂的地方再去看看書,搜搜資料,才會發現自己的想法究竟是貼合了大牛的思想還是根本很愚蠢,一味的看原理就像是「站在岸上學不會游泳」,而一味的敲**那不就是乙個碼農了嗎,有何意義。
下面是我的**,我會從頭到尾,詳細解釋**,和對應的原理:
#include
#include
#include
#include
#include
#include
#include
int fds[1024];
為什麼要定義乙個全域性變數的fds陣列?這是因為需要在select裡面的檔案描述符集中與之對應,將外界有效的檔案描述符(套接字)set進select的檔案描述符集中,相當於select的乙份儲存。這個解釋也不是很好,往下看或許你就明白了
static
void usage(const
char* str)
int startup(const
char* _ip,const
char* _port)
af_inet代表協議族,sock_stream表示以位元組流傳遞資料,即代表使用tcp協議,最後乙個引數代表協議型別,但因為前兩個引數已經確定了乙個協議,所以預設為0。
struct sockaddr_in local;
local.sin_family=af_inet;
local.sin_port=htons(atoi(_port));
local.sin_addr.s_addr=inet_addr(_ip);
if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
if(listen(sock,5)<0)
listen的第二個引數代表最大完全連線的數量引數的值,但是最大的半連線的數量則由核心決定。
我自己的理解是一次只能同時處理backlog個連線,再多的話你先等著。
ps:最大完全連線數為:backlog+1,因為計算機的下標從0開始。backlog表示listen的第二個引數
return sock;
}int main(int argc,char *argv)
int listen_sock = startup(argv[1],argv[2]);
int i=1;
int maxfd=-1;
maxfd表示當前最大的有效socket,在遍歷整個socket陣列時,只需要遍歷到這個位置就ok了。
後續的socket因為是無效的socket,所以不用進行判斷
int nums=sizeof(fds)/sizeof(fds[0]);
for(i=1;i
i++)
fds[0]=listen_sock;
將全域性陣列的所有元素全部初始化為-1,便於在後續判斷中不出錯,並且把listen_sock(監聽套接字)放入陣列的第乙個位置中,因為監聽套接字已經處理號,所以後續並不需要對監聽套接字再做什麼別的操作。
while(1)
; fd_set readfds;//讀 檔案描述符集
fd_zero(&readfds);//將"讀"檔案描述符集中的所有檔案描述符設為0
maxfd=-1;
更新檔案描述符集的最大值,防止多出來的被無效訪問。
例如:第一次while迴圈,maxfd=1000,此時需要遍歷到1000,
第二次時,maxfd=10,表示10後面的socket全部無效,沒有讀寫事件,若不重新設定的話,就浪費時間。
for(i=1;i0)//如果有效,則設定入"讀"檔案描述符集中
小於0的,表示無效,繼續遍歷下乙個,直到遍歷到有效的檔案描述符,才進行操作
if(i==0&&fd_isset(listen_sock,&readfds))
listen_sock就緒,tcp三次握手完成,此時可以開始進行連線
fd_isset表示判斷第乙個引數是否在第二個引數的集合中
使用accept函式將得到乙個連線的socket,再fds陣列中找到乙個可用的位置,因為有效的socket必大於0,所以原先初始化的陣列中為-1的則表示可用的位置,插入此socket,等待下次迴圈時,進入對連線請求處理的模組
//執行到此處,說明已經有乙個client連線
printf("client[%s]--[%d]# ",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
fflush(stdout);
int j=1;
for(j=1;jif(fds[j]==-1)//找到乙個可用的檔案描述符
}if(j==nums)
檔案描述符集已滿,沒有可用的檔案描述符,關閉new_fd,即關閉此client的鏈結
到此處,有兩種情況:
1.從for迴圈中跳出,表示找到乙個可用的位置,也表示當前的伺服器還有資源再處理乙個連線的請求
2.或因為j==nums,表示資源全部分配,沒有「餘力」處理其他連線,拒絕接收此連線,不過這種已經返回,不會進入else邏輯。
else
}else
if(i>0&&fd_isset(fds[i],&readfds))
除listen_sock就緒外的其他檔案描述符就緒
else
}else
if(s==0)
s==0表示客戶端斷開連線
else
}else}}
break;}}
}
至此,關於select的**已經寫完,建議讀者在看這篇部落格時自己了解過select的架構,因為我並沒有很詳細的分析select伺服器,只是從**層分析,希望在看完之後,自己動手敲一遍**,然後對不熟悉的地方或查手冊,或從網上找點針對性的部落格看看。
我是分割線
上次忘記說在select中何種情況下算作可讀,何種情況下算作可寫:
1,socket核心接收緩衝區中的位元組數大於或等於其低水位標記so_rcvlowat,此時可讀;
2,socket通訊的對方關閉,會返回0;
3,監聽socket上有新的連線到來;
4,socket上有未處理的錯誤,此時可以用getsockopt來讀取和清除該錯誤;
1,socket核心傳送緩衝區中的位元組數大於或等於其低水位標記so_sndlowat,此時可寫;
2,socket的寫操作被關閉,對寫操作被關閉的socket執行寫操作將會返回sigpipe;
3,socket使用非阻塞connect連線成功或失敗之後;
4,socket上有未處理的錯誤,此時可以用getsockopt來讀取和清除該錯誤。
在select中還可以接收帶外資料(即緊急資料),這個帶外資料是tcp標誌位中urg有效時的資料,而不是psh有效時的資料,如果還是分不清,可以看看這篇部落格:
並且,urg的帶外資料有點奇怪,並不是想象中的一串資料,而是偏移量的最後乙個資料,假如tcp報頭中的urg標誌位有效,則必然緊急指標的位置會有乙個有效的位址偏移量,然後接收方根據緊急指標解讀出帶外資料,如下圖的,往tcp的緩衝區中寫入n個普通資料後,又緊接著寫了三個帶外資料「a,b,c」,系統將緊急指標指向帶外資料的最後乙個位元組的下一位置,此時在接收端只有c被解讀為帶外資料,其他資料都被解釋為普通資料,所以,不難發現,帶外資料一次只能發乙個。
I O多路復用之select
阻塞i o模型 應用程式呼叫乙個i o函式,應用程式會一直等待資料準備好。如果資料沒有準備好,就會一直等待。只有當資料準備好,從核心拷貝到使用者空間io函式才成功返回。非阻塞i o模型 把乙個套介面設定成非阻塞告訴核心,當所有的i o操作無法完成時,不要將程序睡眠,而返回乙個錯誤資訊。此時i o操作...
IO多路復用之select
1 背景知識 我們首先來看看伺服器程式設計的模型,客戶端發來的請求服務端會產生乙個程序來對其進行服務,每當來乙個客戶請求就產生乙個程序來服務,然而程序不可能無限制的產生,因此為了解決大量客戶端訪問的問題,引入了io復用技術。即 乙個程序可以同時對多個客戶請求進行服務。也就是說io復用的 介質 是程序...
redis中io復用之select
前面講過evport epoll kqueue的實現,下來我們繼續看看我們select在redis中的使用 1 首先通過fd zero來清空用於監控讀和寫fd的rfds和wfds的鍊錶 typedef struct aeapistate aeapistate static int aeapicrea...