在建立socket的時候,socket函式需要指定是ipv4
還是ipv6
,分別對應af_inet
和af_inet6
,這是網路層的。其次,還需指定是tcp
還是udp
,這是傳輸層的。tcp是基於資料流的,所以設定為sock_stream
,udp是基於資料報的,所以設定為sock_dgram
。
基於tcp協議的socket程式函式呼叫過程:
服務端要先監聽乙個埠,一般是先呼叫bind
函式,給這個socket賦予乙個ip位址和埠。當伺服器有了ip和埠,就可以呼叫listen
函式進行監聽。此時伺服器進入監聽狀態,客戶端可以發起連線。
在linux核心中,為每個socket維護兩個佇列。乙個是已經建立了連線的佇列,這時候連線三次握手已經完畢,處於established
狀態;乙個是還沒有完全建立連線的佇列,這個時候三次握手還沒完成,處於syn_rcvd
的狀態。
接下來,服務端呼叫accept
函式,拿出乙個已經完成的連線進行處理。如果還沒有完成,就要等待。
在服務端等待的時候,客戶端可以通過connect
函式發起連線。先在引數中指明要連線的ip位址和埠號,然後開始發起三次握手。核心會給客戶端分配乙個臨時的埠。一旦握手成功,服務端的accept就會返回另乙個socket。監聽的socket和真正用來傳輸資料的socket是兩個,乙個叫作監聽socket,乙個叫作已連線socket。
連線建立成功之後,雙方開始通過read
和write
函式來讀寫資料。說tcp的socket就是乙個檔案流,是非常準確的。因為socket在linux中就是以檔案的形式存在的。除此之外,還存在檔案描述符,寫入和讀出,就是通過檔案描述符。
在核心中,socket是乙個檔案,那對應就有檔案描述符。每乙個程序都有乙個資料結構task_struct
,裡面指向乙個檔案描述符陣列,來列出這個程序開啟的所有檔案的檔案描述符。檔案描述符是乙個整數,是這個陣列的下標。
這個陣列中的內容是乙個指標,指向核心中所有開啟的檔案的列表。既然是乙個檔案,就會有乙個inode
,只不過socket對應的inode
不像真正的檔案系統一樣,儲存在硬碟上,而是在記憶體中。在這個inode
中,指向了socket在核心中的socket結構。在這個結構裡面,主要的是兩個佇列,乙個是傳送佇列,乙個是接收佇列。在這兩個佇列裡面儲存的是乙個快取sk_buff
。這個快取裡面能夠看到完整的包的結構。
基於udp協議的scoket程式函式呼叫過程:
對於udp來說,不需要三次握手,也就不需要呼叫listen
和connect
。但是,udp的互動仍然需要ip和埠號,因此也需要bind
。udp是沒有維護連線狀態的,因此不需要每對建立一組socket,而是只要有乙個socket,就能夠和多個客戶端通訊。也正是因為沒有連線狀態,每次通訊的時候,都呼叫sendto
和recvfrom
,都可以傳入ip位址和埠。
系統會用乙個四元組來標識乙個tcp連線。
伺服器的ip和埠是本能隨意改變的,所以能改變的只有客戶端的ip和對端埠。因此理論上最大tcp連線數 = 客戶端ip數 * 客戶端埠數。對ipv4,客戶端的ip數最多為2^32,埠數為2^16,也就是服務端單機理論上最大的tcp連線數是2^48。
顯然,這只是理論上來說,事實上遠遠達不到。首先主要是檔案描述符限制,socket都是檔案,所以首先要通過ulimit
配置檔案描述符的數目;另乙個限制是記憶體,按上面的資料結構,每個tcp連線都要占用一定的記憶體,作業系統是有限的。
所以,我們要在有限的資源下接更多的專案,就必須降低每個專案消耗的資源數量。
方式一:多程序
一旦建立乙個連線,就會有乙個已連線socket,這個時候建立乙個子程序,然後將基於已連線socket的互動交給這個新的子程序來做。
在linux中,建立子程序使用fork
函式。這是在父程序的基礎上完全拷貝乙個子程序。在linux核心中,會複製檔案描述符的列表,也會複製記憶體空間,還會複製一條記錄當前執行到了哪一行程式的程序。顯然,複製的時候在呼叫fork
,複製完畢之後,父程序和子程序都會記錄當前剛剛執行完fork
。這兩個程序剛複製完的時候,幾乎一模一樣,只是根據fork
的返回值來區分到底是父程序還是子程序。如果返回0,則是子程序;如果返回其他整數,就是父程序。
方式二:多執行緒
相比於程序,執行緒要輕量級的多。在linux下,通過pthread_create
建立乙個程序,也是呼叫do_fork
。不同的是,雖然新的執行緒在task
列表會新建立一項,但是很多資源,例如檔案描述符、程序空間、還是共享的,只不過多了乙個引用而已。
新的程序也可以通過已連線socket處理請求,從而達到併發處理的目的。
上面基於程序或者執行緒模型的,其實還是有問題的。新到來乙個tcp連線,就需要分配乙個程序或執行緒,一台機器無法建立很多程序和執行緒。有個c10k,它的意思是一台機器要維護10萬個連線,就要建立1萬個程序或者執行緒,那麼作業系統是無法承受的。
方式三:io多路復用,乙個執行緒維護多個socket
由於socket是檔案描述符,因而某個執行緒盯的所有的socket,都放在乙個檔案描述符集合fd_set
中,然後呼叫select
函式來監聽檔案描述符集合是否有變化。一旦有變化,就會依次檢視每個檔案描述符。那些發生變化的檔案描述符在fd_set
對應的位都設為1,表示socket可讀或者可寫,從而可以進行讀寫操作,然後再呼叫select
,接著盯著下一輪的變化。
方式四:io多路復用,從select
到epoll
上面的select
函式還是有問題的,因為每次socket所在的檔案描述符集合中有socket發生變化的時候,都需要通過輪詢的方式,這會大大影響連線數。因此使用select
,需要設定能夠同時盯著的檔案描述符的數量fd_setsize
。
如果改成事件通知的方式,情況會好很多,就不需要輪詢檔案描述符了。而是當集合發生變化時,主動通知執行緒,然後執行緒再做出相應的操作。
能完成這件事情的函式叫epoll
,它在核心中的實現不是通過輪詢的方式,而是通過註冊callback
函式的方式,當某個檔案描述符發生變化的時候,就會主動通知。
如圖所示,假如程序開啟了socket m, n, x等多個檔案描述符,現在需要通過epoll
來監聽是否這些socket都有事件發生。其中epoll_create
建立乙個epoll
物件,也是乙個檔案,也對應乙個檔案描述符,同樣也對應著開啟檔案列表中的一項。在這項裡面有乙個紅黑樹,在紅黑樹裡,要儲存這個epoll
要監聽的所有socket。
當epoll_ctl
新增乙個socket的時候,其實是加入這個紅黑樹,同時紅黑樹裡面的節點指向乙個結構,將這個結構掛在被監聽的socket的事件列表中。當乙個socket來了乙個事件的時候,可以從這個列表中得到epoll
物件,並呼叫callback
通知它。
這種通知方式使得監聽的socket資料增加的時候,效率不會大幅度降低,能夠同時監聽的socket的數目也非常多了。上限就為系統定義的、程序開啟的最大檔案描述符個數。因此,epoll
被稱為解決c10k問題的利器。
基於Socket的UDP和TCP協議
一 概述 tcp 傳輸控制協議 和udp 使用者資料報協議 是網路體系結構tcp ip模型中傳輸層一層中的兩個不同的通訊協議。tcp 傳輸控制協議,一種面向連線的協議,給使用者程序提供可靠的全雙工的位元組流,tcp套介面是位元組流套介面 stream socket 的一種。udp 使用者資料報協議。...
分析udp資料報 網路協議之TCP和UDP
首先強調一點,tcp ip協議是乙個協議簇。裡面包括很多協議的,udp只是其中的乙個,之所以命名為tcp ip協議,因為tcp ip協議是兩個很重要的協議,就用他兩命名了。兩個協議的區別實際使用時,只需要記住 tcp正常連線傳送資料時一般不會產生丟包 排除上下層其他因素 而udp產生丟包是很常見的事...
基於TCP和UDP的Socket網路應用程式
基於tcp和udp的socket網路應用程式 計算機網路課設 一 需求分析 利用socket編寫乙個簡單的網路應用程式,獲取伺服器當前的時間和日期。說明與要求 1 對客戶與伺服器之間使用的協議進行設計。2 分別採用流式套接字和資料報套接字進行實現。二 程式設計 2.1 基於tcp協議的流程 編寫用t...