socket

我是小白别骂我發表於2024-08-12

socket可以用於TCP/UDP連線
對於TCP的socket:
1、服務端呼叫socket(AF_INET,SOCKET_STREAM,0)即TCP連線IPV4
AF_UNIX是本機 AF_INET6是IPV6
SOCKET_DGRAM是UDP,SOCKET_DGRAM是原始套接字
2、呼叫bind函式,繫結IP和埠號,由埠號找應用程式,由IP找機器
3、呼叫listen函式監聽(可以透過netstat命令來檢視對應埠號有沒有被監聽)
Linux:netstat -t(TCP)/-u(UDP)/-i(埠)
4、進入監聽狀態後,呼叫accept函式,從核心獲取客戶端連線,如果沒有則阻塞等待連線
客戶端怎麼發起連線?
客戶端建立完socket,呼叫connect函式發起連線

檔案描述符即fd,每個程序都有一個資料結構task_struct,結構體中有一個指向檔案描述符陣列的成員指標,陣列的下標就是檔案描述符,陣列的內容是一個指標,指向核心中所有開啟檔案的列表
每個檔案都有一個inode,socket檔案的inode指向了核心中socket結構,在這個結構體中有兩個佇列分別是傳送和接收佇列,佇列中儲存的為struck sk_buff用連結串列串起來
sk_buff可以表示各個層的資料包,在應用層資料包為data,TCP層位segment,IP層位packet,在資料鏈路層位frame,就一個資料透過指標來給不同層

多程序模型
伺服器的主程序負責監聽客戶連線,一旦與客戶端連線,accept函式就返回一個已連線socket,透過fork函式建立一個子程序,fork會將父程序所有相關東西複製一份,包括檔案描述符、記憶體地址空間、程式計數器、執行的程式碼
但是返回值不同,返回為0是子程序,子程序會複製父程序的檔案描述符,可以直接使用已連線的socket與客戶端通訊

當子程序退出時,核心中還保留該程序的一些資訊,如果沒回收好,就會變成殭屍程序,兩種方式回收子程序:wait()和waitpid()函式

多執行緒模型:
當伺服器與客戶端TCP連線後,透過pthread_create()函式建立執行緒,然後將已連線socket檔案描述符傳遞給執行緒函式
用執行緒池來避免執行緒頻繁建立和銷燬

IO多路複用:
程序可以透過一個系統呼叫函式從核心中獲取多個事件
select:
將已連線的socket放到一個檔案描述符集合,呼叫select函式將檔案描述集合複製到核心裡,讓核心檢查是否有網路事件產生,透過遍歷檔案描述符集合,當有事件標記socket為可讀/可寫,然後再把整個檔案描述符複製回使用者態,使用者態還需要再透過遍歷方法找到可讀/科協的socket
這種select需要進行2次遍歷檔案描述符集合,一次核心態,一次使用者態,還有2次複製檔案描述符集合,從使用者空間傳入核心空間,由核心修改後再傳出使用者空間
select用固定長度bitsmap來表示檔案描述符集合,所支援的檔案描述符個數是有限的,Linux中由核心FD_SETSIZE限制,預設最大為1024,只能監聽0-1023
poll不再用bitsmap來儲存所關注的檔案描述符,用動態陣列來替換,以連結串列形式組織,突破了select個數限制,但還受到系統檔案描述符限制
select和poll沒太大區別,都是用線性結構儲存程序關注的socket集合,因此需要遍歷檔案描述符集合來找到可讀/可寫的socket,時間複雜度為O(n),也需要在使用者態和核心態之間複製檔案描述符集合
epoll:
有兩方面解決了select/poll問題:
1、epoll在核心使用紅黑樹來跟蹤程序所有待檢測的檔案描述字,將socket透過epoll_ctl()函式加入至核心中紅黑樹,紅黑樹對於線性結構快速增刪改,且直接在核心中維護,只需要傳入一個待檢測的socket減少核心和使用者空間大量資料複製和記憶體分配
2、epoll使用事件驅動核心裡維護了一個連結串列來記錄就緒事件,當某個socket事件發生時,透過回撥函式核心會將其加入到這個就緒事件列表中,當使用者呼叫epoll_wait()函式,只會返回有事件發生的檔案描述符個數,不需要像select/poll那樣輪詢

epoll有兩種事件觸發模式:邊緣觸發(ET)和水平觸發(LT)
邊緣觸發:當被監控的socket描述符上有可讀事件發生,服務端只會從epoll_wait中甦醒一次,即程序沒有呼叫read函式從核心讀取資料,也只有一次,因此需要一次性將核心快取區資料讀完
水平觸發:當被監控的socket上有可讀事件發生,伺服器端不斷從epoll_wait中甦醒,直到核心快取區資料被read函式讀完才結束

相關文章