插口層可以說是在使用者程式與tcp/ip協議之間的乙個呈上啟下的層次,它將使用者與某協議相關的請求對映到具體的協議實現。不同型別的套接字在產生時就會關聯到相關協議實現(通過一組函式指標來實現的)。比如在乙個tcp套接字上呼叫write函式,則會轉而呼叫tcp協議相關的函式。
插口也就是我們常說的套接字,它代表了一條通訊鏈路的一端,插口結構中儲存或指向了與鏈路相關的所有資訊。這些資訊包括使用的協議型別,協議的狀態資訊(如是否已連線,源和目的位址等),資料快取,插口選項(如so_keepalive,so_reuseaddr等)。
插口的資料結構如下:
struct socket so_rcv, so_snd;
// 定義了一些常量如預設緩衝最大大小,sb為sockbuf的縮寫
#define sb_max (256*1024) /* default for max chars in sockbuf */
// - 保護套接字資料快取的鎖,因此不存在當使用者呼叫recv函式從快取讀入資料時,又將資料新增到快取中的情況。
// - 且多執行緒從乙個套接字讀取資料也是安全的
#define sb_lock 0x01
#define sb_want 0x02 /* someone is waiting to lock */
#define sb_wait 0x04 /* someone is waiting for data/space */
#define sb_sel 0x08 /* someone is selecting */
#define sb_async 0x10 /* async i/o, need signals */
#define sb_notify (sb_wait|sb_sel|sb_async)
#define sb_nointr 0x40 /* operations not interruptible */
caddr_t so_tpcb; /* wisc. protocol control block *** */
void (*so_upcall) __p((struct socket *so, caddr_t arg, int waitf));
caddr_t so_upcallarg; /* arg for above */
};
我們可以使用訊號,讓核心在描述符就緒時傳送sigio訊號通知程序(程序id記錄於socket結構體中)。稱這種模型為訊號驅動式i/o,如下圖所示:
當將套接字設定為訊號驅動io後,每當套接字狀態發生改變,便會向套接字中記錄的程序傳送sigio訊號。在udp上使用訊號驅動i/o是簡單的,此時sigio訊號會在以下事件發生時產生:1)資料報到達套接字(即已放入套接字快取); 2) 套接字上發生非同步錯誤 。在tcp上使用訊號驅動程式時(訊號驅動io幾乎不會用在tcp連線上),在出現以下幾種狀況時都會產生訊號:1)鏈結建立完成;2)開始斷開鏈結;3)斷開鏈結完成;4)連線的乙個通道已關閉(讀關閉或寫關閉?沒有測試過);5)插口上有資料到達(此時資料已準備就緒);6)資料已被傳送(即可寫,(是否需要剩餘資料低於高水位));7)當乙個udp或tcp插口上有待處理差錯。
posix保證**獲的訊號在其訊號處理函式期間總是阻塞的,關於訊號的排隊機制,我們將在其它博文中做進一步說明。
關於udp使用訊號驅動i/o的例子可參見unp p531。其伺服器端的主要**如下:
void server::start()
sigprocmask(sig_block, &oldmask, null); // 重新設定為非阻塞
// recvfrom(...) // 接收資料
// sendto(...) // 傳送應答
_dataque.pop();
sigprocmask(sig_block, &newmask, &oldmask);}}
socket函式用於建立套接字及與之對應的檔案描述符,檔案物件結構。當使用者許可權不夠時(如建立原始套接字),則會將socket狀態設定為ss_priv,當呼叫其它系統呼叫時,便會檢測改狀態,並返回錯誤資訊。
當在乙個非監聽套接字上呼叫該函式,會返回einval,這個錯誤碼表示引數錯誤。若當前連線就緒佇列無就緒連線,且是非阻塞模式,則會返回eagain,該錯誤碼一般表示請求的資源尚未就緒(如:套接字資料接收佇列無資料等),請稍後再試。
對於乙個非阻塞套接字而言,若呼叫connect時套接字的狀態是正在連線,則會返回ealready,這通常出現在:當前一次connect未呼叫成功,之後又在同一套接字上進行呼叫的情況(有的系統不會又這個問題,當盡量避免這樣使用)。
當在乙個非阻塞套接字上成功呼叫connect時,會立刻返回,若此時仍在進行連線(即返回時連線尚未完成),則會返回錯誤碼einprogress,那麼如何知道連線已完成呢?當連線就緒時,該套接字的狀態會變為可寫,因此在select上關注該描述符的寫事件。那麼第二個問題又來了,當乙個套接字上出現錯誤時,套接字會變得可讀亦可寫,那麼當套接字可寫時,我們如何判斷是連線建立成功還是存在待處理的錯誤呢?這個問題可以通過傳入引數so_error呼叫getsockopt(_sockfd, sol_socket, so_error, &err, &len);函式處理,若套接字上存在錯誤則說明是有錯誤待處理,否則為連線建立成功。muduo網路庫中也是採取的這種方式來進行區別的。
那麼對於阻塞式套接字呢?假設在乙個阻塞式套接字上呼叫connect函式,而該函式被中斷(捕獲到某個訊號),此時會怎樣呢?加入核心不自動重啟,那麼它將返回einter,此時不應該再次呼叫connect,而應該採用與與非阻塞套接字一樣的檢測方法。
參博文《高階i/o》(包括accept驚群也參考這篇博文)
TCP IP實現(九) 插口I O
struct sockbuf so rcv,so snd 注意,程序訪問套接字快取時是加鎖的,因此多個程序訪問套接字快取是安全的。寫系統呼叫有write,writev,send,sendto,sendmsg,所有的這些系統呼叫都會都會直接或間接呼叫sosend函式,該函式會將程序傳來的資料複製到核心...
插口層簡介(一)
1.描述符的概念 通過呼叫socket函式,可以獲得乙個socket描述符,這個描述符具體其他unix描述符的所有特性 可以用這個描述符呼叫read和write,呼叫fork後,父程序和子程序可以共享它 乙個描述符是程序的程序表表項的乙個陣列的下標,這個陣列項指向乙個開啟檔案表的結構 程序可以通過描...
速讀原著 TCP IP 插口排錯選項
檢視乙個t c p連線上發生的事情的另一種方法是使能插口排錯選項,當然是在支援這一特徵的系統中。這個特徵只能工作在 t c p上 其他協議都不行 並且需要應用程式支援 當應用程式啟動時,使能乙個插口排錯選項 大多數伯克利演變的實現都支援這個特徵,包括s u nos 4.4bsd和svr4。程式使能了...