套接字選項
繼承自監聽套接字的選項
- SO_DEBUG
- SO_NOROUTE
- SO_KEEPALIVE
- SO_LINGER
- SO_OOBINLINE
- SO_RCVBUF
- SO_RCVLOWWAT
- SO_SNDBUF
- SO_SNDLOWAT
- TCP_MAXSEG
- TCP_NODELAY
這幾個屬性是從監聽套接字繼承的,要想設定已連線套接字的這些屬性,需要在監聽套接字上設定。
SO_KEEPALIVE
如果設定了這個選項,當2小時內套接字的任一方向都沒有資料交換時,TCP會自動傳送一個探活資料包(keep-alive probe),接下來會有幾種可能:
- 對端正常響應,這時不會通知應用程式。接下來2小時內如果仍沒有資料,傳送另一個探活資料包。
- 對端響應RST,表示對端已經崩潰並重新啟動,這時將SO_ERROR設定為ECONNRESET,關閉套接字。
- 對端沒有任何響應,這時會繼續傳送探活資料包試圖獲得響應(不同系統的重試次數和間隔不同),如果仍沒有任何響應則放棄。這時會將SO_ERROR設定為ETIMEOUT,關閉套接字。
- 套接字收到一個ICMP錯誤(比如host unreachable),這時會將SO_ERROR設定為對應的錯誤,然後關閉套接字。
SO_LINGER
這個選項表示如何關閉面向連線的協議,預設是呼叫close
時立即返回,但如果傳送緩衝區有殘留的資料,會嘗試將其傳送給對端。
設定時通過以下結構體來控制引數:
struct linger {
int l_onoff; /* option on/off */
int l_linger; /* linger time, 單位為秒*/
};
複製程式碼
- 當
l_onoff
為0時表示選項關閉,l_linger
的值被忽略,呼叫close
時會立刻返回 - 當
l_onoff
為非0而l_linger
為0時,呼叫close
關閉某個連線時TCP會中止該連線,即丟棄傳送緩衝區的所有資料並向對端傳送一個RST,而不是進行正常的四次揮手。這樣能夠避免TCP的TIME_WAIT狀態,但是也可能出現2MSL內建立出該連線的化身的情況,導致來自剛才被終止的連線上的舊的資料被髮送到新的化身上。 - 當
l_onoff
為非0而且l_linger
也為非0時,關閉套接字時核心將會拖延一段時間。如果此時傳送緩衝區中有資料,程式將會進入睡眠,直到:(a) 所有資料都已傳送並被對端確認;(b) 拖延時間到。如果套接字被設定為非阻塞型,close
會立即返回,即使拖延時間為非0的情況也是。在使用SO_LINGER選項時,應該檢查close
的返回值,如果在資料傳送完並被確認前拖延時間到的話,close
會返回EWOULDBLOCK,且傳送緩衝區的資料都會被丟棄。
SO_REUSEADDR
SO_REUSEADDR
允許一個監聽套接字繫結到其眾所周知埠,即使以前建立的將該埠作為本地埠的連線仍存在。比如監聽的程式中途關閉了但其建立的子程式和連線仍存在,這時監聽程式重啟時嘗試重新繫結埠時需要指定了SO_REUSEADDR
才行,否則會繫結失敗。- 允許在同一個埠上啟動同一伺服器的多個例項,只要每個例項繫結不同的本地地址即可
TCP_MAXSEG
獲取或設定TCP最大分節大小(MSS),表示TCP能夠傳送的最大資料量,通常由對端的SYN分節指定,除非我們選擇一個更小的值。如果在連線建立之前查詢,返回的是預設值。
TCP_NODELAY
開啟該選項將禁用Nagle演算法,預設情況其是啟用的。nagle演算法可以減少大量小的資料包在網路中傳輸的情況。
TCP_CORK
linux2.4之後的版本才支援選項,開啟該選項將啟用cork演算法,預設是禁用的。cork選項可以禁止小的資料包在網路中傳輸。
fcntl(file control)
fcntl顧名思義,可以對描述符進行各種控制操作,主要通過cmd和arg兩個引數來控制。
int fcntl(int fd, int cmd, .../* args */)
複製程式碼
可選的cmd有:
F_SETFL
設定flagF_GETFL
獲取flagF_SETOWN
設定套接字屬主F_GETOWN
獲取套接字屬主
使用方式:
int flags;
//先獲取當前flags
if ((flags = fcntl(fd, F_GETFL, 0)) < 0) {
//error
}
//增加O_NONBLOCK選項
flags |= O_NONBLOCK;
//設定flags
if ((flags = fcntl(fd, F_SETFL, flag)) < 0) {
//error
}
//設定flags
if (fcntl(fd, F_SETFL, flags) < 0) {
//error
}
//關閉O_NONBLOCK選項
flags &= ~O_NONBLOCK
//設定flags
if (fcntl(fd, F_SETFL, flags) < 0) {
//error
}
複製程式碼
在使用fcntl
時,必須先獲取當前的標誌,然後與新的標誌或之後再設定標誌,否則會清除描述符的其他標誌。
UDP套接字
sendto和recvfrom
ssize_t sendto(int fd, const void *buff, size_t nbytes,
int flags, const struct sockaddr *to, socklen_t *addrlen);
ssize_t recvfrom(int fd, void *buff, size_t nbytes,
int flags, struct sockaddr *from, socklen_t *addrlen);
複製程式碼
函式返回傳送或者接收的位元組數,最後兩個引數指定了對端地址。如果recvfrom的後兩個引數是空指標,則表示不關心資料傳送者的協議地址。
非同步錯誤
在一個UDP套接字上呼叫sendto
時,如果對端不可用,對端會返回一個ICMP訊息(比如"port unreachable"),但這個錯誤不會返回給應用程式,sendto
仍能夠正常返回。
我們稱這裡的這個ICMP錯誤為非同步錯誤,這個錯誤由sendto
觸發,但是sendto
本身卻成功返回;原因是sendto
的成功僅表示在網路介面輸出佇列中具備足夠的空間存放sendto
形成的IP資料包,而真正的錯誤在隨後實際發出資料包的時候才發生,所以我們稱這個錯誤是非同步的。
對於非同步錯誤,處理的基本規則是:對於一個UDP套接字,由它引發的非同步錯誤不會返回給它,除非它已連線。這裡的已連線指的是成功呼叫了connect
函式。為什麼這麼規定呢?因為一個UDP套接字可能會往多個對端傳送和讀取資料,而sendto
和recvfrom
只能返回單純的errno
,不能返回對端的ip和埠號資訊。所以我們決定:只有在UDP已經只繫結到一個對端時,這些非同步錯誤才返回給程式。
connect
除非udp套接字事先成功呼叫了connect
,否則sendto
和recvfrom
發生的異常不會返回給應用程式。
對一個UDP套接字呼叫connect
函式並不會像TCP套接字那樣進行三次握手,而是先檢查傳入的地址是否合法(是否可達等)然後儲存ip和port到傳入的套接字地址結構體中,接著直接返回。
一旦一個UDP套接字已連線,會發生三個變化:
sendto
時不能再指定最後兩個引數(目標地址和地址長度),必須設定為空指標;或者改用write
或者send
recvfrom
時不能再指定最後兩個引數(目標地址和地址長度),必須設定為空指標;或者改用read
/recv
/recvmsg
- 由已連線UDP套接字引發的非同步錯誤會返回給程式。
多次呼叫connect
和TCP套接字不同,UDP套接字可以多次呼叫connect
函式,通過這樣做可以達到兩個目的:
- 為套接字指定新的對端ip和port
- 斷開套接字(將套接字地址結構體的地址族設定為
AF_UNSPEC
即可)
connect與效能
對於未連線的UDP套接字,每次呼叫sendto
函式時都會隱式地進行套接字連線和斷開連線;所以如果確定UDP套接字要傳送的對端只有一個時,可以通過顯式連線來提高效率。