I O復用之select伺服器

2021-08-07 09:59:05 字數 4268 閱讀 4000

學習了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...