获取和设置套接字选项——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);
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值