1. 程式人生 > >獲取和設定套接字選項——getsockopt/setsockopt

獲取和設定套接字選項——getsockopt/setsockopt

函式原型:

#include <sys/socket.h>

int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

引數說明:   

sockfd:將要被設定或者獲取選項的套接字描述符。
level:指定系統中解釋選項的程式碼或為通用套接字程式碼,或為某個特定於協議的程式碼(例如IPv4、IPv6、TCP)。

1)SOL_SOCKET:通用套接字選項. 
2)IPPROTO_IP:IP選項. 
3)IPPROTO_TCP:TCP選項. 

optname:需要訪問的選項名。

optval:對於getsockopt(),則把已獲取的選項值存放到*optval指向的緩衝。對於setsockopt(),指向包含新選項值的緩衝。
optlen:對於getsockopt(),作為入口引數時,選項值的最大長度。作為出口引數時,選項值的實際長度。對於setsockopt(),現選項的長度。

返回說明:   
成功執行時,返回0。失敗返回-1,errno被設為以下的某個值   
EBADF:sockfd不是有效的檔案描述符。


EFAULT:optval指向的記憶體並非有效的程序空間。
EINVAL:在呼叫setsockopt()時,optlen無效。
ENOPROTOOPT:指定的協議層不能識別選項。
ENOTSOCK:sockfd描述的不是套接字

詳細說明: 

通用套接字選項(SOL_SOCKET 協議無關的):
SO_BROADCAST 
使能或禁止程序傳送廣播訊息的能力。只有資料報套介面支援廣播,並且還必須在支援廣播訊息的網路上(如乙太網、令牌環網等)。 
如果目的地址是廣播地址但此選項未設,則返回EACCES錯誤。 

SO_DEBUG 
僅僅TCP支援。當開啟此選項時,核心對TCP在此套介面所傳送和接收的所有分組跟蹤詳細資訊。這些資訊儲存在核心的環形緩衝區內,可由程式trpt進行檢查。 



SO_DONTROUTE 
此選項規定發出的分組將繞過底層協議的正常路由機制。 
該選項經常由路由守護程序(routed和gated)用來繞過路由表(路由表不正確的情況下),強制一個分組從某個特定介面發出。 

SO_ERROR (可以獲取但不能設定的套接字選項)
當套介面上發生錯誤時,源自Berkeley的核心中的協議模組將此套介面的名為so_error的變數設為標準的UNIX Exxx值中的一個,它稱為此套介面的待處理錯誤(pending error)。核心可立即以以下兩種方式通知程序: 
   1. 如果程序阻塞於次套介面的select呼叫,則無論是檢查可讀條件還是可寫條件,select都返回並設定其中一個或所有兩個條件。 
   2. 如果程序使用訊號驅動I/O模型,則給程序或程序組生成訊號SIGIO。 
程序然後可以通過獲取SO_ERROR套介面選項來得到so_error的值。由getsockopt返回的整數值就是此套介面的待處理錯誤。so_error隨後由核心復位為0。 
當程序呼叫read且沒有資料返回時,如果so_error為非0值,則read返回-1且errno設為so_error的值,接著so_error的值被複位為0。如果此套介面上有資料在排隊,則read返回那些資料而不是返回錯誤條件。 
如果程序呼叫write時so_error為非0值,則write返回-1且errno設為so_error的值,隨後so_error也被複位。 

SO_KEEPALIVE 
開啟此選項後,如果2小時內在此套介面上沒有任何資料交換,TCP就會自動給對方發一個保持存活探測分節,這是一個對端必須響應的TCP分節,結果如下: 

   1. 對方以期望的ACK響應,則一切正常,應用程式得不到通知; 
   2. 對方以RST響應,它告知本端TCP:對端已奔潰且已重新啟動。套介面的待處理錯誤被置為ECONNRESET,套介面本身則被關閉; 
   3. 對方對探測分節無任何響應,經過重試都沒有任何響應,套介面的待處理錯誤被置為ETIMEOUT,套介面本身被關閉;若接收到一個ICMP錯誤作為某個探測分節的響應,則返回相應錯誤。 
此選項一般由伺服器使用。伺服器使用它是為了檢測出半開連線並終止他們。(即客戶端已經奔潰掉,而伺服器端不知道) 

SO_LINGER 
此選項指定函式close對面向連線的協議如何操作(如TCP)。預設close操作是立即返回,如果有資料殘留在套接字傳送緩衝區中則系統將試著將這些資料傳送給對方。 

SO_LINGER選項用來改變此預設設定。本選項要求在使用者程序與核心間傳遞如下結構: 
struct linger { 
    int l_onoff; /* 0 = off, nozero = on */ 
    int l_linger; /* linger time */ 
}; 

有下列三種情況: 

   1. l_onoff為0,則該選項關閉,l_linger的值被忽略,等於預設情況,close立即返回; 
   2. l_onoff為非0,l_linger為0,則套介面關閉時TCP夭折連線,TCP將丟棄保留在套介面傳送緩衝區中的任何資料併發送一個RST給對方,而不是通常的四分組終止序列,這避免了TIME_WAIT狀態; 
   3. l_onoff 為非0,l_linger為非0,當套介面關閉時核心將拖延一段時間(由l_linger決定)。如果套介面緩衝區中仍殘留資料,程序將處於睡眠狀態,直到所有資料傳送完且被對方確認,之後進行正常的終止序列(描述字訪問計數為0)或延遲時間到。此種情況下,應用程式檢查close的返回值是非常重要的,如果在資料傳送完並被確認前時間到,close將返回EWOULDBLOCK錯誤且套介面傳送緩衝區中的任何資料都丟失。close的成功返回僅告訴我們傳送的資料(和FIN)已由對方TCP確認,它並不能告訴我們對方應用程序是否已讀了資料。如果套接字設為非阻塞的,它將不等待close完成。 

讓客戶知道伺服器已經讀其資料的一個方法時:呼叫shutdown(SHUT_WR)而不是呼叫close,並等待對方close連線的本地(伺服器)端。 

SO_OOBINLINE 
此選項開啟時,帶外資料將被保留在正常的輸入佇列中(即線上存放)。當發生這種情況時,接收函式的MSG_OOB標誌不能用來讀帶外資料。 

SO_RCVBUF和SO_SNDBUF 
每個套介面都有一個傳送緩衝區和一個接收緩衝區,使用這兩個套接字選項可以改變預設緩衝區大小。 
當設定TCP套介面接收緩衝區的大小時,函式呼叫順序是很重要的,因為TCP的視窗規模選項是在建立連線時用SYN分節與對方互換得到的。對於客戶,SO_RCVBUF選項必須在connect之前設定;對於伺服器,SO_RCVBUF選項必須在listen前設定。 
TCP套接字緩衝區的大小至少是相應連線的MSS值的四倍。(典型的緩衝區大小預設值是8192位元組,典型的MSS值為512或1460)。

SO_RCVLOWAT和SO_SNDLOWAT 
每個套接字有一個接收低潮限度和一個傳送低潮限度,他們由函式select使用。這兩個選項可以修改他們。 
接收低潮限度是讓select返回“可讀”時在套接字接收緩衝區中必須有的資料量,對於一個TCP或UDP套介面,此值預設為1。傳送低潮限度是讓select返回“可寫”時在套接字傳送緩衝區中必須有的可用空間,對於TCP套介面,此值常為2048。 

SO_RCVTIMEO和SO_SNDTIMEO 
使用這兩個選項可以給套接字設定一個接收和傳送超時。通過設定引數的值為0秒和0微秒來禁止超時。預設時兩個超時都是禁止的。 
接收超時影響5個輸入函式:read、readv、recv、recvfrom和recvmsg;傳送超時影響5個輸出函式:write、writev、send、sendto和sendmsg。 

SO_REUSEADDR和SO_REUSEPORT 
SO_REUSEADDR提供如下四個功能: 

   1. SO_REUSEADDR允許啟動一個監聽伺服器並捆綁其眾所周知埠,即使以前建立的將此埠用做他們的本地埠的連線仍存在。這通常是重啟監聽伺服器時出現,若不設定此選項,則bind時將出錯。 
   2. SO_REUSEADDR允許在同一埠上啟動同一伺服器的多個例項,只要每個例項捆綁一個不同的本地IP地址即可。這對於使用IP別名技術託管多個HTTP伺服器的網點來說是很常見的。對於TCP,我們根本不可能啟動捆綁相同IP地址和相同埠號的多個伺服器。 
   3. SO_REUSEADDR允許單個程序捆綁同一埠到多個套介面上,只要每個捆綁指定不同的本地IP地址即可。這一般不用於TCP伺服器。 
   4. SO_REUSEADDR允許完全重複的捆綁:當一個IP地址和埠繫結到某個套介面上時,還允許此IP地址和埠捆綁到另一個套介面上。一般來說,這個特性僅在支援多播的系統上才有,而且只對UDP套介面而言(TCP不支援多播)。 

SO_REUSEPORT選項有如下語義: 

   1. 此選項允許完全重複捆綁,但僅在想捆綁相同IP地址和埠的套介面都指定了此套介面選項才性。 
   2. 如果被捆綁的IP地址是一個多播地址,則SO_REUSEADDR和SO_REUSEPORT等效。 

使用這兩個套介面選項的建議: 

   1. 在所有TCP伺服器中,在呼叫bind之前設定SO_REUSEADDR套介面選項; 
   2. 當編寫一個同一時刻在同一主機上可執行多次的多播應用程式時,設定SO_REUSEADDR選項,並將本組的多播地址作為本地IP地址捆綁。 

SO_TYPE 
該選項返回套接字的型別,返回的整數值是一個諸如SOCK_STREAM或SOCK_DGRAM這樣的值。 

SO_USELOOPBACK 
該選項僅用於路由域(AF_ROUTE)的套介面,它對這些套接字的預設設定為開啟(這是唯一一個預設為開啟而不是關閉的SO_xxx套接字選項)。當此套接字選項開啟時,套接字接收在其上傳送的任何資料的一個拷貝。 
禁止這些回饋拷貝的另一個方法是shutdown,第二個引數應設為SHUT_RD。 


IPv4套接字選項(IPPROTO_IP)

IP_HDRINCL 
如果本選項是給一個原始IP套接字設定的,則我們必須為所有在該原始套接字上傳送的資料報構造自己的IP頭部。 

IP_OPTIONS 
設定此選項允許我們在IPv4頭部中設定IP選項。這要求掌握IP頭部中IP選項的格式資訊。 

IP_RECVDSTADDR 
該選項導致所接收到的UDP資料報的目的IP地址由函式recvmsg作為輔助資料返回。 

IP_RECVIF 
該選項導致所接收到的UDP資料報的接收介面索引由函式recvmsg作為輔助資料返回。 

IP_TOS 
該選項使我們可以給TCP或UDP套接字在IP頭部中設定服務型別欄位。如果我們給此選項呼叫getsockopt,則放到外出IP資料報頭部的TOS欄位中的當前值將返回(預設為0)。還沒有辦法從接收到的IP資料報中取此值。 

可以將TOS設定為如下的值: 
    * IPTOS_LOWDELAY:最小化延遲 
    * IPTOS_THROUGHPUT:最大化吞吐量 
    * IPTOS_RELIABILITY:最大化可靠性 
    * IPTOS_LOWCOST:最小化成本 

IP_TTL 
用次選項,可以設定和獲取系統用於某個給定套接字的預設TTL值(存活時間欄位)。與TOS一樣,沒有辦法從接收到的資料報中得到此值。 

ICMPv6套接字選項(IPPROTO_ICMPV6)

ICMP6_FILTER 
可獲取和設定一個icmp6_filter結構,他指明256個可能的ICMPv6訊息型別中哪一個傳遞給在原始套接字上的程序。 


IPv6套接字選項(IPPROTO_IPV6)
IPV6_ADDRFORM 
允許套介面從IPv4轉換到IPv6,反之亦可。 

IPV6_CHECKSUM 
指定使用者資料中校驗和所處位置的位元組偏移。如果此值為非負,則核心將(1)給所有外出分組計算並存儲校驗和;(2)輸入時檢查所收到的分組的校驗和,丟棄帶有無效校驗和的分組。此選項影響出ICMPv6原始套介面外的所有IPv6套介面。如果指定的值為-1(預設值),核心在此原始套介面上將不給外出的分組計算並存儲校驗和,也不檢查所收到的分組的校驗和。 

IPV6_DSTOPTS 
設定此選項指明:任何接收到的IPv6目標選項都將由recvmsg作為輔助資料返回。此選項預設為關閉。 

IPV6_HOPLIMIT 
設定此選項指明:接收到的跳限欄位將由recvmsg作為輔助資料返回。 

IPV6_HOPOPTS 
設定此選項指明:任何接收到的步跳選項都將由recvmsg作為輔助資料返回。 

IPV6_NEXTHOP 
這不是一個套介面選項,而是一個可指定個sendmsg的輔助資料物件的型別。此物件以一個套介面地址結構指定某個資料報的下一跳地址。 

IPV6_PKTINFO 
設定此選項指明:下面關於接收到的IPv6資料報的兩條資訊將由recvmsg作為輔助資料返回:目的IPv6地址和到達介面索引。 

IPV6_PKTOPTIONS 
大多數IPv6套介面選項假設UDP套介面使用recvmsg和sendmsg所用的輔助資料在核心與應用程序間傳遞資訊。TCP套介面使用IPV6_PKTOPTIONS來獲取和儲存這些值。 

IPV6_RTHDR 
設定此選項指明:接收到的IPv6路由頭部將由recvmsg作為輔助資料返回。 

IPV6_UNICAST_HOPS 
類似於IPv4的IP_TTL,它的設定指定傳送到套介面上的外出資料報的預設跳限,而它的獲取則返回核心將用於套介面的跳限值。為了從接收到的IPv6資料報中得到真實的跳限欄位,要求使用IPV6_HOPLIMIT套介面選項。 



TCP套接字選項(IPPROTO_TCP)

TCP_KEEPALIVE 
它指定TCP開始傳送保持存活探測分節前以秒為單位的連線空閒時間。預設值至少為7200秒,即2小時。該選項僅在SO_KEEPALIVE套介面選項開啟時才有效。 

TCP_MAXRT 
它指定一旦TCP開始重傳資料,在連線斷開之前需經歷的以秒為單位的時間總量。值0意味著使用系統預設值,值-1意味著永遠重傳資料。 

TCP_MAXSEG 
允許獲取或設定TCP連線的最大分節大小(MSS)。返回值是我們的TCP傳送給另一端的最大資料量,他常常就是由另一端用SYN分節通告的MSS,除非我們的TCP選擇使用一個比對方通告的MSS小的值。如果此選項在套介面連線之前取得,則返回值為未從另一端收到的MSS選項的情況下所用的預設值。 

TCP_NODELAY 
如果設定,此選項禁止TCP的Nagle演算法。預設時,該演算法是使能的。 
Nagle演算法的目的是減少WAN上小分組的數目。 
Nagle演算法常常與另一個TCP演算法聯合使用:延遲ACK(delayed ACK)演算法。 
解決多次寫導致Nagle演算法和延遲ACK演算法負面影響的方法: 

   1. 使用writev而不是多次write; 
   2. 合併緩衝區,對此緩衝區使用一次write; 
   3. 設定TCP_NODELAY選項,繼續呼叫write多次,這是最不可取的解決方法。 

TCP_STDURG 
它影響對TCP緊急指標的解釋。 

獲支援套接字選項的預設值的程式碼:

#include	<sys/socket.h>
#include	<sys/types.h>
#include	<stdlib.h>
#include	<stdio.h>
#include	<netinet/tcp.h>		
#include	<netinet/in.h>

/* 對於getsockopt的每個返回值,我們的union型別中都有一個成員  */
union val {
  int				i_val;
  struct linger		linger_val;
  struct timeval	timeval_val;
} val;

/* 用於將給定套接字選項的值轉換為字串輸出,第一個函式用來輸出標誌選項(0:表示禁用,1:表示開啟),其它三個函式用來輸出特定型別的值選項  */
static char	*sock_str_flag(union val *, int);
static char	*sock_str_int(union val *, int);
static char	*sock_str_linger(union val *, int);
static char	*sock_str_timeval(union val *, int);

/* 定義結構並初始化陣列,每個元素代表一個套接字選項  */
struct sock_opts {
  const char	   *opt_str;
  int		opt_level;
  int		opt_name;
  char   *(*opt_val_str)(union val *, int);
} sock_opts[] = {
	{ "SO_BROADCAST",		SOL_SOCKET,	SO_BROADCAST,	sock_str_flag },
	{ "SO_DEBUG",			SOL_SOCKET,	SO_DEBUG,		sock_str_flag },
	{ "SO_DONTROUTE",		SOL_SOCKET,	SO_DONTROUTE,	sock_str_flag },
	{ "SO_ERROR",			SOL_SOCKET,	SO_ERROR,		sock_str_int },
	{ "SO_KEEPALIVE",		SOL_SOCKET,	SO_KEEPALIVE,	sock_str_flag },
	{ "SO_LINGER",			SOL_SOCKET,	SO_LINGER,		sock_str_linger },
	{ "SO_OOBINLINE",		SOL_SOCKET,	SO_OOBINLINE,	sock_str_flag },
	{ "SO_RCVBUF",			SOL_SOCKET,	SO_RCVBUF,		sock_str_int },
	{ "SO_SNDBUF",			SOL_SOCKET,	SO_SNDBUF,		sock_str_int },
	{ "SO_RCVLOWAT",		SOL_SOCKET,	SO_RCVLOWAT,	sock_str_int },
	{ "SO_SNDLOWAT",		SOL_SOCKET,	SO_SNDLOWAT,	sock_str_int },
	{ "SO_RCVTIMEO",		SOL_SOCKET,	SO_RCVTIMEO,	sock_str_timeval },
	{ "SO_SNDTIMEO",		SOL_SOCKET,	SO_SNDTIMEO,	sock_str_timeval },
	{ "SO_REUSEADDR",		SOL_SOCKET,	SO_REUSEADDR,	sock_str_flag },
#ifdef	SO_REUSEPORT
	{ "SO_REUSEPORT",		SOL_SOCKET,	SO_REUSEPORT,	sock_str_flag },
#else
	{ "SO_REUSEPORT",		0,			0,				NULL },
#endif
	{ "SO_TYPE",			SOL_SOCKET,	SO_TYPE,		sock_str_int },
	{ "IP_HDRINCL",			IPPROTO_IP,	IP_HDRINCL,		sock_str_flag },
	{ "IP_TOS",			IPPROTO_IP,	IP_TOS,			sock_str_int },
	{ "IP_TTL",			IPPROTO_IP,	IP_TTL,			sock_str_int },
	{ "TCP_MAXSEG",			IPPROTO_TCP,TCP_MAXSEG,		sock_str_int },
	{ "TCP_NODELAY",		IPPROTO_TCP,TCP_NODELAY,	sock_str_flag },
	{ NULL,					0,			0,				NULL }
};

int main(int argc, char **argv)
{
	int		fd;
	socklen_t	len;
	struct sock_opts *ptr;

	for (ptr = sock_opts; ptr->opt_str != NULL; ptr++) {
		printf("%s: ", ptr->opt_str);
		if (ptr->opt_val_str == NULL)
			printf("(undefined)\n");
		else {
			if((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0 ){
				printf("Can't create fd for level %d\n", ptr->opt_level);
				exit(1);
			}

			len = sizeof(val);
			if (getsockopt(fd, ptr->opt_level, ptr->opt_name,&val, &len) == -1) {
				printf("getsockopt error");
				continue;
			} else {
				printf("default = %s\n", (*ptr->opt_val_str)(&val, len));
			}
			close(fd);
		}
	}
	exit(0);
}

static char	strres[128];

static char	*sock_str_flag(union val *ptr, int len)
{

	if (len != sizeof(int))
		snprintf(strres, sizeof(strres), "size (%d) not sizeof(int)", len);
	else
		snprintf(strres, sizeof(strres), "%s", (ptr->i_val == 0) ? "off" : "on");
	return(strres);

}


static char	*sock_str_int(union val *ptr, int len)
{
	if (len != sizeof(int))
		snprintf(strres, sizeof(strres), "size (%d) not sizeof(int)", len);
	else
		snprintf(strres, sizeof(strres), "%d", ptr->i_val);
	return(strres);
}

static char	*sock_str_linger(union val *ptr, int len)
{
	struct linger	*lptr = &ptr->linger_val;

	if (len != sizeof(struct linger))
		snprintf(strres, sizeof(strres),"size (%d) not sizeof(struct linger)", len);
	else
		snprintf(strres, sizeof(strres), "l_onoff = %d, l_linger = %d",
				 lptr->l_onoff, lptr->l_linger);
	return(strres);
}

static char	*sock_str_timeval(union val *ptr, int len)
{
	struct timeval	*tvptr = &ptr->timeval_val;

	if (len != sizeof(struct timeval))
		snprintf(strres, sizeof(strres), "size (%d) not sizeof(struct timeval)", len);
	else
		snprintf(strres, sizeof(strres), "%d sec, %d usec",tvptr->tv_sec, tvptr->tv_usec);
	return(strres);
}