讀寫socket套接字的正確姿勢
掃描二維碼
隨時隨地手機看文章
導學問題:
1. 增加緩沖區(qū),可以提高程序的吞吐量嗎?
2. 網(wǎng)絡數(shù)據(jù)從發(fā)送到接受的過程,總共拷貝了多少次?
發(fā)送數(shù)據(jù)
ssize_t write (int socketfd, const void *buffer, size_t size)
ssize_t send (int socketfd, const void *buffer, size_t size, int flags)
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags)
這三個都是發(fā)送程序時常用的接口,大部分人可以都很熟悉write函數(shù)了,經(jīng)典的寫文件接口。我們這里重點給大家介紹后兩種。
如果我們想發(fā)送緊急數(shù)據(jù),那么我們就可以使用send接口,給發(fā)送的數(shù)據(jù)加上緊急標記,那么我們的這一幀數(shù)據(jù)就會快速被發(fā)送出去。
如果想指定多重緩沖區(qū)進行數(shù)據(jù)傳輸,就需要使用第三個函數(shù),以結構體msghdr的方式發(fā)送數(shù)據(jù)。
發(fā)送緩沖區(qū)
當tcp連接成功后,在linux內核里面會維護一個發(fā)送緩存區(qū)。它的大小可以通過調整套接字選項來進行設置。當我們應用程序調用write函數(shù)時,實際上是把數(shù)據(jù)從應用程序拷貝到操作系統(tǒng)內核的發(fā)送緩存區(qū),它并不一定馬上就會發(fā)送出去。具體情況如下:
- 發(fā)送緩存區(qū)足夠大,那么write的系統(tǒng)調用會馬上退出,返回寫入的字節(jié)數(shù)就是應用程序的大小。
- 發(fā)送緩存區(qū)不足以容納應用程序的數(shù)據(jù),那么write函數(shù)會一直阻塞,直到應用數(shù)據(jù)完全填充到緩存區(qū)后返回,緩存區(qū)的數(shù)據(jù)由操作系統(tǒng)內核負責把它發(fā)送出去。
如下圖:

所以無限增大緩沖區(qū)肯定不行,內核緩沖區(qū)總是充滿數(shù)據(jù)時會產(chǎn)生粘包問題,同時網(wǎng)絡 的傳輸大小MTU也會限制每次發(fā)送的大小,最后由于數(shù)據(jù)堵塞需要消耗大量內存資源,資 源使用效率不高。相當于讓倉庫變大,可以存儲了更多的貨物,如果出貨的速度有限,會有更多的貨物爛在倉庫里。
讀取數(shù)據(jù)
最簡單的數(shù)據(jù)讀取方式就是read函數(shù)了,它的函數(shù)原型如下:
ssize_t read (int socketfd, void *buffer, size_t size)
read函數(shù)要求操作系統(tǒng)內核從套接字描述子socketfd讀取固定數(shù)量的字節(jié),并將讀取的結果存儲到buffer中。返回值告訴我們讀取的實際字節(jié)數(shù)量。特殊情況下,如果它的返回值為0,那么代表EOF,表示網(wǎng)絡中對端發(fā)送了FIN,我們也要相應地進行斷開連接處理。而如果返回-1,則代表出錯。
阻塞式套接字最終發(fā)送返回的實際寫入字節(jié)數(shù)和請求字節(jié)數(shù)是相等的。發(fā)送成功僅僅表示的是數(shù)據(jù)被拷貝到了發(fā)送緩沖區(qū)中,并不意味著連接對端已經(jīng)收到所有的數(shù)據(jù)。至于什么時候發(fā)送到對端的接收緩沖區(qū),或者更進一步說,什么時候被對方應用程序緩沖所接收,對我們而言完全都是透明的。
總結
對于send發(fā)送數(shù)據(jù)來說,返回成功僅代表數(shù)據(jù)寫到發(fā)送緩存區(qū)成功,并不代表對端已經(jīng)接收成功。而對于read來說,需要循壞讀取數(shù)據(jù),并且要考慮EOF等異常條件。