首页 > 代码库 > Linux下getsockopt/setsockopt 函数说明
Linux下getsockopt/setsockopt 函数说明
【 getsockopt/setsockopt系统调用
功能描述:
获取或者设置与某个套接字关联的选 项。选项可能存在于多层协议中,它们总会出现在最上面的套接字层。当操作套接字选项时,选项位于的层和选项的名称必须给出。为了操作套接字层的选项,应该 将层的值指定为SOL_SOCKET。为了操作其它层的选项,控制选项的合适协议号必须给出。例如,为了表示一个选项由TCP协议解析,层应该设定为协议 号TCP。
用法:
#include
#include
int getsockopt(int sock, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen);
参数:
sock:将要被设置或者获取选项的套接字。
level:选项所在的协议层。
optname:需要访问的选项名。
optval:对于getsockopt(),指向返回选项值的缓冲。对于setsockopt(),指向包含新选项值的缓冲。
optlen:对于getsockopt(),作为入口参数时,选项值的最大长度。作为出口参数时,选项值的实际长度。对于setsockopt(),现选项的长度。
返回说明:
成功执行时,返回0。失败返回-1,errno被设为以下的某个值
EBADF:sock不是有效的文件描述词
EFAULT:optval指向的内存并非有效的进程空间
EINVAL:在调用setsockopt()时,optlen无效
ENOPROTOOPT:指定的协议层不能识别选项
ENOTSOCK:sock描述的不是套接字
首先知道setsockopt()函数的KEEPALIVE属性是周期地测试连接是否仍存活,
我上网查了很多资料还是不知道如何使用,
最后硬着头皮自己写了一个服务器端和一个客户端的套接字连接
分别设置了两端的KEEPALIVE属性为打开
服务器端:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVPORT 6000 //设定服务器服务端口为6000
#define MAX_LISTEN_SOCK_NUM 20 //设定可监听套接字的最大个数为20
int main()
{
//sockfd为本地监听套接字标识符,client_fd为客户端套接字标识符
int sockfd,client_fd;
struct sockaddr_in my_addr;
struct sockaddr_in client_addr;
//创建本地监听套接字
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1){
perror("套接字创建失败!/n");
exit(1);
}
//设置套接字的属性使它能够在计算机重启的时候可以再次使用套接字的端口和IP
int err,sock_reuse=1;
err=setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,(char *)&sock_reuse,sizeof(sock_reuse));
if(err!=0){
printf("套接字可重用设置失败!/n");
exit(1);
}
my_addr.sin_family=AF_INET;
my_addr.sin_port=htons(SERVPORT);
my_addr.sin_addr.s_addr=INADDR_ANY;
bzero(&(my_addr.sin_zero),8);
//绑定套接字
if(bind(sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))==-1){
perror("绑定失败!/n");
exit(1);
}
//设置监听
if((listen(sockfd,MAX_LISTEN_SOCK_NUM))==-1){
perror("设置监听失败!/n");
exit(1);
}
printf("套接字进入监听状态,等待请求连接:/n");
//int time_to_quit=1;
//while(time_to_quit){ //可以通过设置time_to_quit来主动关闭服务器端
while(1){
//有连接请求时进行连接
socklen_t sin_size=sizeof(struct sockaddr_in);
if((client_fd=accept(sockfd,(struct sockaddr *)&client_addr,&sin_size))==-1){
perror("接受连接失败!/n");
continue;
}
int opt;
socklen_t len=sizeof(int);
if((getsockopt(sockfd,SOL_SOCKET,SO_KEEPALIVE,(char*)&opt,&len))==0){
printf("SO_KEEPALIVE Value: %d/n", opt);
}
printf("接到一个来自%s的连接/n",inet_ntoa(client_addr.sin_addr));
//创建子进程来处理已连接的客户端套接字
if(send(client_fd,"您好,您已经连接成功!/n",50,0)==-1){
perror("发送通知信息失败!/n");
exit(0);
}
}
close(client_fd);
return 0;
}
客户端:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAXDATASIZE 100
int main()
{
int sockfd,nbytes,serv_port;
char buf_serv_ip[16],buf[26];
struct sockaddr_in serv_addr;
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1){
perror("创建套接字失败!/n");
exit(1);
}
//创建套接字成功后设置其可重用的属性
int KeepAlive=1;
socklen_t KPlen=sizeof(int);
if(setsockopt(sockfd,SOL_SOCKET,SO_KEEPALIVE,(char *)&KeepAlive,KPlen)!=0){
perror("设置周期测试连接是否仍存活失败!/n");
exit(1);
}
printf("请输入要连接主机的IP地址:/n");
scanf("%s",buf_serv_ip);
printf("请输入要连接主机的端口号:/n");
scanf("%d",&serv_port);
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=inet_addr(buf_serv_ip);
serv_addr.sin_port=htons(serv_port);
bzero(&(serv_addr.sin_zero),8);
if(connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(struct sockaddr))==-1){
perror("连接服务器失败!/n");
exit(1);
}
printf("连接服务器成功!/n");
//在此处可以先接受判断将要接受数据的长度再创建数组
if((nbytes=recv(sockfd,buf,26,0))==-1){
perror("接受数据失败!/n");
exit(1);
}
buf[nbytes]=‘/0‘;
printf("接受的数据为:%s/n",buf);
close(sockfd);
return 0;
}
分别在两个终端运(在同一个主机上)行:先启动服务器端;再启动客户端,向服务器端请求连接,连接成功后,客户端套接字关闭,服务器端套接字始终打开,等待两个小时多也没有反应,
肯请高手指点:
(1)这样做能达到预期报告连接是否仍存在的效果吗,
(2)不能的话应该如何做,
(3)还有getsockopt()的相关功能又是什么哪?在上面的程序中没有用到该函数
(4)看了unix网络编程上的一点关于该问题的解释还是不明白,因为她提到要两个小时才回应,但等了撒小时也没有反应,她还说可以缩短时间,我却不知道如何缩短
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)
level指定控制套接字的层次.可以取三种值:
1)SOL_SOCKET:通用套接字选项.
2)IPPROTO_IP:IP选项.
3)IPPROTO_TCP:TCP选项.
optname指定控制的方式(选项的名称),我们下面详细解释
optval获得或者是设置套接字选项.根据选项名称的数据类型进行转换
选项名称 说明 数据类型
========================================================================
SOL_SOCKET
------------------------------------------------------------------------
SO_BROADCAST 允许发送广播数据 int
SO_DEBUG 允许调试 int
SO_DONTROUTE 不查找路由 int
SO_ERROR 获得套接字错误 int
SO_KEEPALIVE 保持连接 int
SO_LINGER 延迟关闭连接 struct linger
SO_OOBINLINE 带外数据放入正常数据流 int
SO_RCVBUF 接收缓冲区大小 int
SO_SNDBUF 发送缓冲区大小 int
SO_RCVLOWAT 接收缓冲区下限 int
SO_SNDLOWAT 发送缓冲区下限 int
SO_RCVTIMEO 接收超时 struct timeval
SO_SNDTIMEO 发送超时 struct timeval
SO_REUSERADDR 允许重用本地地址和端口 int
SO_TYPE 获得套接字类型 int
SO_BSDCOMPAT 与BSD系统兼容 int
========================================================================
IPPROTO_IP
------------------------------------------------------------------------
IP_HDRINCL 在数据包中包含IP首部 int
IP_OPTINOS IP首部选项 int
IP_TOS 服务类型
IP_TTL 生存时间 int
========================================================================
IPPRO_TCP
------------------------------------------------------------------------
TCP_MAXSEG TCP最大数据段的大小 int
TCP_NODELAY 不使用Nagle算法 int
========================================================================
返回说明:
成功执行时,返回0。失败返回-1,errno被设为以下的某个值
EBADF:sock不是有效的文件描述词
EFAULT:optval指向的内存并非有效的进程空间
EINVAL:在调用setsockopt()时,optlen无效
ENOPROTOOPT:指定的协议层不能识别选项
ENOTSOCK:sock描述的不是套接字
SO_RCVBUF和SO_SNDBUF每个套接口都有一个发送缓冲区和一个接收缓冲区,使用这两个套接口选项可以改变缺省缓冲区大小。
// 接收缓冲区
int nRecvBuf=32*1024; //设置为32K
setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
//发送缓冲区
int nSendBuf=32*1024;//设置为32K
setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));
注意:
当设置TCP套接口接收缓冲区的大小时,函数调用顺序是很重要的,因为TCP的窗口规模选项是在建立连接时用SYN与对方互换得到的。对于客户,SO_RCVBUF选项必须在connect之前设置;对于服务器,SO_RCVBUF选项必须在listen前设置。
如下内容转自http://linux.chinaunix.net/techdoc/net/2007/11/01/971094.shtml
有时候我们要控制套接字的行为(如修改缓冲区的大小),这个时候我们就要控制套接字的选项了.
以下资料均从网上收集得到
getsockopt 和 setsockopt
获得套接口选项:
代码:
int getsockopt ( int sockfd, int level, int optname, void * optval, socklen_t *opteln ) 设置套接口选项:
int setsockopt ( int sockfd, int level, int optname, const void * optval, socklen_t *opteln )
sockfd(套接字): 指向一个打开的套接口描述字
level:(级别): 指定选项代码的类型。
SOL_SOCKET: 基本套接口
IPPROTO_IP: IPv4套接口
IPPROTO_IPV6: IPv6套接口
IPPROTO_TCP: TCP套接口
optname(选项名): 选项名称
optval(选项值): 是一个指向变量的指针 类型:整形,套接口结构, 其他结构类型:linger{}, timeval{ }
optlen(选项长度) :optval 的大小
返回值:标志打开或关闭某个特征的二进制选项
以下说明方法为
选项名称 简要说明 数据类型
补充
========================================================================
SOL_SOCKET
------------------------------------------------------------------------
SO_BROADCAST 允许发送广播数据 int
适用於 UDP socket。其意义是允许 UDP socket 「广播」(broadcast)讯息到网路上。
SO_DEBUG 允许调试 int
SO_DONTROUTE 不查找路由 int
SO_ERROR 获得套接字错误 int
SO_KEEPALIVE 保持连接 int
检测对方主机是否崩溃,避免(服务器)永远阻塞于TCP连接的输入。
设置该选项后,如果2小时内在此套接口的任一方向都没有数据交换,TCP就自动给对方 发一个保持存活探测分节(keepalive
probe)。这是一个对方必须响应的TCP分节.它会导致以下三种情况:
对方接收一切正常:以期望的ACK响应。2小时后,TCP将发出另一个探测分节。
对方已崩溃且已重新启动:以RST响应。套接口的待处理错误被置为ECONNRESET,套接 口本身则被关闭。
对方无任何响应:源自berkeley的TCP发送另外8个探测分节,相隔75秒一个,试图得到
一个响应。在发出第一个探测分节11分钟15秒后若仍无响应就放弃。套接口的待处理错
误被置为ETIMEOUT,套接口本身则被关闭。如ICMP错误是“host unreachable(主机不
可达)”,说明对方主机并没有崩溃,但是不可达,这种情况下待处理错误被置为 EHOSTUNREACH。
SO_DONTLINGER 若为真,则SO_LINGER选项被禁止。
SO_LINGER 延迟关闭连接 struct linger
上面这两个选项影响close行为
选项 间隔 关闭方式 等待关闭与否
SO_DONTLINGER 不关心 优雅 否
SO_LINGER 零 强制 否
SO_LINGER 非零 优雅 是
若设置了SO_LINGER(亦即linger结构中的l_onoff域设为非零,参见2.4,4.1.7和4.1.21各节),并设置了零超时间隔,则
closesocket()不被阻塞立即执行,不论是否有排队数据未发送或未被确认。这种关闭方式称为“强制”或“失效”关闭,因为套接口的虚电路立即被
复位,且丢失了未发送的数据。在远端的recv()调用将以WSAECONNRESET出错。
若设置了SO_LINGER并确定了非零的超时间隔,则closesocket()调用阻塞进程,直到所剩数据发送完毕或超时。这种关闭称为“优雅的”关
闭。请注意如果套接口置为非阻塞且SO_LINGER设为非零超时,则closesocket()调用将以WSAEWOULDBLOCK错误返回。
若在一个流类套接口上设置了SO_DONTLINGER(也就是说将linger结构的l_onoff域设为零;参见2.4,4.1.7,4.1.21
节),则closesocket()调用立即返回。但是,如果可能,排队的数据将在套接口关闭前发送。请注意,在这种情况下WINDOWS套接口实现将在
一段不确定的时间内保留套接口以及其他资源,这对于想用所以套接口的应用程序来说有一定影响。
SO_OOBINLINE 带外数据放入正常数据流,在普通数据流中接收带外数据 int
SO_RCVBUF 接收缓冲区大小 int
设置接收缓冲区的保留大小
与 SO_MAX_MSG_SIZE 或TCP滑动窗口无关,如果一般发送的包很大很频繁,那么使用这个选项
SO_SNDBUF 发送缓冲区大小 int
设置发送缓冲区的保留大小
与 SO_MAX_MSG_SIZE 或TCP滑动窗口无关,如果一般发送的包很大很频繁,那么使用这个选项
每个套接口都有一个发送缓冲区和一个接收缓冲区。 接收缓冲区被TCP和UDP用来将接收到的数据一直保存到由应用进程来读。
TCP:TCP通告另一端的窗口大小。 TCP套接口接收缓冲区不可能溢出,因为对方不允许发出超过所通告窗口大小的数据。
这就是TCP的流量控制,如果对方无视窗口大小而发出了超过宙口大小的数据,则接 收方TCP将丢弃它。
UDP:当接收到的数据报装不进套接口接收缓冲区时,此数据报就被丢弃。UDP是没有
流量控制的;快的发送者可以很容易地就淹没慢的接收者,导致接收方的UDP丢弃数据报。
SO_RCVLOWAT 接收缓冲区下限 int
SO_SNDLOWAT 发送缓冲区下限 int
每个套接口都有一个接收低潮限度和一个发送低潮限度。它们是函数selectt使用的,
接收低潮限度是让select返回“可读”而在套接口接收缓冲区中必须有的数据总量。
——对于一个TCP或UDP套接口,此值缺省为1。发送低潮限度是让select返回“可写”
而在套接口发送缓冲区中必须有的可用空间。对于TCP套接口,此值常缺省为2048。 对于UDP使用低潮限度,
由于其发送缓冲区中可用空间的字节数是从不变化的,只要 UDP套接口发送缓冲区大小大于套接口的低潮限度,这样的UDP套接口就总是可写的。
UDP没有发送缓冲区,只有发送缓冲区的大小。
SO_RCVTIMEO 接收超时 struct timeval
SO_SNDTIMEO 发送超时 struct timeval
SO_REUSERADDR 允许重用本地地址和端口 int
充许绑定已被使用的地址(或端口号),可以参考bind的man
SO_EXCLUSIVEADDRUSE
独占模式使用端口,就是不充许和其它程序使用SO_REUSEADDR共享的使用某一端口。
在确定多重绑定使用谁的时候,根据一条原则是谁的指定最明确则将包递交给谁,而且没有权限之分,也就是说低级权限的用户是可以重绑定在高级权限如服务启动的端口上的,这是非常重大的一个安全隐患,
如果不想让自己程序被监听,那么使用这个选项
SO_TYPE 获得套接字类型 int
SO_BSDCOMPAT 与BSD系统兼容 int
==========================================================================
IPPROTO_IP
--------------------------------------------------------------------------
IP_HDRINCL 在数据包中包含IP首部 int
这个选项常用于黑客技术中,隐藏自己的IP地址
IP_OPTINOS IP首部选项 int
IP_TOS 服务类型
IP_TTL 生存时间 int
以下IPV4选项用于组播
IPv4 选项 数据类型 描 述
IP_ADD_MEMBERSHIP struct ip_mreq 加入到组播组中
IP_ROP_MEMBERSHIP struct ip_mreq 从组播组中退出
IP_MULTICAST_IF struct ip_mreq 指定提交组播报文的接口
IP_MULTICAST_TTL u_char 指定提交组播报文的TTL
IP_MULTICAST_LOOP u_char 使组播报文环路有效或无效
在头文件中定义了ip_mreq结构:
代码:
struct ip_mreq {
struct in_addr imr_multiaddr;
struct in_addr imr_interface;
};
若进程要加入到一个组播组中,用soket的setsockopt()函数发送该选项。该选项类型是ip_mreq结构,它的第一个字段imr_multiaddr指定了组播组的地址,第二个字段imr_interface指定了接口的IPv4地址。
IP_DROP_MEMBERSHIP
该选项用来从某个组播组中退出。数据结构ip_mreq的使用方法与上面相同。
IP_MULTICAST_IF
该选项可以修改网络接口,在结构ip_mreq中定义新的接口。
IP_MULTICAST_TTL
设置组播报文的数据包的TTL(生存时间)。默认值是1,表示数据包只能在本地的子网中传送。
IP_MULTICAST_LOOP
组播组中的成员自己也会收到它向本组发送的报文。这个选项用于选择是否激活这种状态。
==========================================================================
IPPRO_TCP
--------------------------------------------------------------------------
TCP_MAXSEG TCP最大数据段的大小 int
获取或设置TCP连接的最大分节大小(MSS)。返回值是我们的TCP发送给另一端的最大
数据量,它常常就是由另一端用SYN分节通告的MSS,除非我们的TCP选择使用一个比
对方通告的MSS小些的值。如果此值在套接口连接之前取得,则返回值为未从另·—端
收到Mss选项的情况下所用的缺省值。小于此返回值的信可能真正用在连接上,因为譬
如说使用时间戳选项的话,它在每个分节上占用12字节的TCP选项容量。我们的TcP将
发送的每个分节的最大数据量也可在连接存活期内改变,但前提是TCP要支持路径MTU 发现功能。如果到对方的路径改变了,此值可上下调整。
TCP_NODELAY 不使用Nagle算法 int
指定TCP开始发送保持存活探测分节前以秒为单位的连接空闲时间。缺省值至少必须为7200秒,即2小时。此选项仅在SO_KEPALIVEE套接口选项打开时才有效。
TCP_NODELAY 和 TCP_CORK,
这两个选项都对网络连接的行为具有重要的作用。许多UNIX系统都实现了TCP_NODELAY选项,但是,TCP_CORK则是Linux系统所独有的
而且相对较新;它首先在内核版本2.4上得以实现。此外,其他UNIX系统版本也有功能类似的选项,值得注意的是,在某种由BSD派生的系统上的
TCP_NOPUSH选项其实就是TCP_CORK的一部分具体实现。
TCP_NODELAY和TCP_CORK基本上控制了包的“Nagle化”,Nagle化在这里的含义是采用Nagle算法把较小的包组装为更大的帧。
John
Nagle是Nagle算法的发明人,后者就是用他的名字来命名的,他在1984年首次用这种方法来尝试解决福特汽车公司的网络拥塞问题(欲了解详情请参
看IETF RFC 896)。他解决的问题就是所谓的silly window syndrome
,中文称“愚蠢窗口症候群”,具体含义是,因为普遍终端应用程序每产生一次击键操作就会发送一个包,而典型情况下一个包会拥有一个字节的数据载荷以及40
个字节长的包头,于是产生4000%的过载,很轻易地就能令网络发生拥塞,。
Nagle化后来成了一种标准并且立即在因特网上得以实现。它现在已经成为缺省配置了,但在我们看来,有些场合下把这一选项关掉也是合乎需要的。
现在让我们假设某个应用程序发出了一个请求,希望发送小块数据。我们可以选择立即发送数据或者等待产生更多的数据然后再一次发送两种策略。如果我们马上发
送数据,那么交互性的以及客户/服务器型的应用程序将极大地受益。例如,当我们正在发送一个较短的请求并且等候较大的响应时,相关过载与传输的数据总量相
比就会比较低,而且,如果请求立即发出那么响应时间也会快一些。以上操作可以通过设置套接字的TCP_NODELAY选项来完成,这样就禁用了Nagle
算法。
另外一种情况则需要我们等到数据量达到最大时才通过网络一次发送全部数据,这种数据传输方式有益于大量数据的通信性能,典型的应用就是文件服务器。应用
Nagle算法在这种情况下就会产生问题。但是,如果你正在发送大量数据,你可以设置TCP_CORK选项禁用Nagle化,其方式正好同
TCP_NODELAY相反(TCP_CORK 和 TCP_NODELAY 是互相排斥的)。下面就让我们仔细分析下其工作原理。
假设应用程序使用sendfile()函数来转移大量数据。应用协议通常要求发送某些信息来预先解释数据,这些信息其实就是报头内容。典型情况下报头很
小,而且套接字上设置了TCP_NODELAY。有报头的包将被立即传输,在某些情况下(取决于内部的包计数器),因为这个包成功地被对方收到后需要请求
对方确认。这样,大量数据的传输就会被推迟而且产生了不必要的网络流量交换。
但是,如果我们在套接字上设置了TCP_CORK(可以比喻为在管道上插入“塞子”)选项,具有报头的包就会填补大量的数据,所有的数据都根据大小自动地
通过包传输出去。当数据传输完成时,最好取消TCP_CORK
选项设置给连接“拔去塞子”以便任一部分的帧都能发送出去。这同“塞住”网络连接同等重要。
总而言之,如果你肯定能一起发送多个数据集合(例如HTTP响应的头和正文),那么我们建议你设置TCP_CORK选项,这样在这些数据之间不存在延迟。能极大地有益于WWW、FTP以及文件服务器的性能,同时也简化了你的工作。示例代码如下:
intfd, on = 1;
…
…
setsockopt (fd, SOL_TCP, TCP_CORK, &on, sizeof (on));
write (fd, …);
fprintf (fd, …);
sendfile (fd, …);
write (fd, …);
sendfile (fd, …);
…
on = 0;
setsockopt (fd, SOL_TCP, TCP_CORK, &on, sizeof (on));
不幸的是,许多常用的程序并没有考虑到以上问题。例如,Eric Allman编写的sendmail就没有对其套接字设置任何选项。
Apache
HTTPD是因特网上最流行的Web服务器,它的所有套接字就都设置了TCP_NODELAY选项,而且其性能也深受大多数用户的满意。这是为什么呢?答
案就在于实现的差别之上。由BSD衍生的TCP/IP协议栈(值得注意的是FreeBSD)在这种状况下的操作就不同。当在TCP_NODELAY
模式下提交大量小数据块传输时,大量信息将按照一次write()函数调用发送一块数据的方式发送出去。然而,因为负责请求交付确认的记数器是面向字节而
非面向包(在Linux上)的,所以引入延迟的概率就降低了很多。结果仅仅和全部数据的大小有关系。而 Linux
在第一包到达之后就要求确认,FreeBSD则在进行如此操作之前会等待好几百个包。
在Linux系统上,TCP_NODELAY的效果同习惯于BSD TCP/IP协议栈的开发者所期望的效果有很大不同,而且在Linux上的Apache性能表现也会更差些。其他在Linux上频繁采用TCP_NODELAY的应用程序也有同样的问题。
TCP_DEFER_ACCEPT
我们首先考虑的第1个选项是TCP_DEFER_ACCEPT(这是Linux系统上的叫法,其他一些操作系统上也有同样的选项但使用不同的名字)。为了
理解TCP_DEFER_ACCEPT选项的具体思想,我们有必要大致阐述一下典型的HTTP客户/服务器交互过程。请回想下TCP是如何与传输数据的目
标建立连接的。在网络上,在分离的单元之间传输的信息称为IP包(或IP
数据报)。一个包总有一个携带服务信息的包头,包头用于内部协议的处理,并且它也可以携带数据负载。服务信息的典型例子就是一套所谓的标志,它把包标记代
表TCP/IP协议栈内的特殊含义,例如收到包的成功确认等等。通常,在经过“标记”的包里携带负载是完全可能的,但有时,内部逻辑迫使TCP/IP协议
栈发出只有包头的IP包。这些包经常会引发讨厌的网络延迟而且还增加了系统的负载,结果导致网络性能在整体上降低。
现在服务器创建了一个套接字同时等待连接。TCP/IP式的连接过程就是所谓“3次握手”。首先,客户程序发送一个设置SYN标志而且不带数据负载的
TCP包(一个SYN包)。服务器则以发出带SYN/ACK标志的数据包(一个SYN/ACK包)作为刚才收到包的确认响应。客户随后发送一个ACK包确
认收到了第2个包从而结束连接过程。在收到客户发来的这个SYN/ACK包之后,服务器会唤醒一个接收进程等待数据到达。当3次握手完成后,客户程序即开
始把“有用的”的数据发送给服务器。通常,一个HTTP请求的量是很小的而且完全可以装到一个包里。但是,在以上的情况下,至少有4个包将用来进行双向传
输,这样就增加了可观的延迟时间。此外,你还得注意到,在“有用的”数据被发送之前,接收方已经开始在等待信息了。
为了减轻这些问题所带来的影响,Linux(以及其他的一些操作系统)在其TCP实现中包括了TCP_DEFER_ACCEPT选项。它们设置在侦听套接
字的服务器方,该选项命令内核不等待最后的ACK包而且在第1个真正有数据的包到达才初始化侦听进程。在发送SYN/ACK包之后,服务器就会等待客户程
序发送含数据的IP包。现在,只需要在网络上传送3个包了,而且还显著降低了连接建立的延迟,对HTTP通信而言尤其如此。
这一选项在好些操作系统上都有相应的对等物。例如,在FreeBSD上,同样的行为可以用以下代码实现:
struct accept_filter_arg af = { "dataready", "" };
setsockopt(s, SOL_SOCKET, SO_ACCEPTFILTER, &af, sizeof(af));
这个特征在FreeBSD上叫做“接受过滤器”,而且具有多种用法。不过,在几乎所有的情况下其效果与TCP_DEFER_ACCEPT是一样的:服务器
不等待最后的ACK包而仅仅等待携带数据负载的包。要了解该选项及其对高性能Web服务器的重要意义的更多信息请参考Apache文档上的有关内容。
就HTTP客户/服务器交互而言,有可能需要改变客户程序的行为。客户程序为什么要发送这种“无用的”ACK包呢?这是因为,TCP协议栈无法知道ACK
包的状态。如果采用FTP而非HTTP,那么客户程序直到接收了FTP服务器提示的数据包之后才发送数据。在这种情况下,延迟的ACK将导致客户/服务器
交互出现延迟。为了确定ACK是否必要,客户程序必须知道应用程序协议及其当前状态。这样,修改客户行为就成为必要了。
对Linux客户程序来说,我们还可以采用另一个选项,它也被叫做TCP_DEFER_ACCEPT。我们知道,套接字分成两种类型,侦听套接字和连接套
接字,所以它们也各自具有相应的TCP选项集合。因此,经常同时采用的这两类选项却具有同样的名字也是完全可能的。在连接套接字上设置该选项以后,客户在
收到一个SYN/ACK包之后就不再发送ACK包,而是等待用户程序的下一个发送数据请求;因此,服务器发送的包也就相应减少了。
TCP_QUICKACK
阻止因发送无用包而引发延迟的另一个方法是使用TCP_QUICKACK选项。这一选项与
TCP_DEFER_ACCEPT不同,它不但能用作管理连接建立过程而且在正常数据传输过程期间也可以使用。另外,它能在客户/服务器连接的任何一方设
置。如果知道数据不久即将发送,那么推迟ACK包的发送就会派上用场,而且最好在那个携带数据的数据包上设置ACK
标志以便把网络负载减到最小。当发送方肯定数据将被立即发送(多个包)时,TCP_QUICKACK选项可以设置为0。对处于“连接”状态下的套接字该选
项的缺省值是1,首次使用以后内核将把该选项立即复位为1(这是个一次性的选项)。
在某些情形下,发出ACK包则非常有用。ACK包将确认数据块的接收,而且,当下一块被处理时不至于引入延迟。这种数据传输模式对交互过程是相当典型的,因为此类情况下用户的输入时刻无法预测。在Linux系统上这就是缺省的套接字行为。
在上述情况下,客户程序在向服务器发送HTTP请求,而预先就知道请求包很短所以在连接建立之后就应该立即发送,这可谓HTTP的典型工作方式。既然没有
必要发送一个纯粹的ACK包,所以设置TCP_QUICKACK为0以提高性能是完全可能的。在服务器方,这两种选项都只能在侦听套接字上设置一次。所有
的套接字,也就是被接受呼叫间接创建的套接字则会继承原有套接字的所有选项。
通过TCP_CORK、TCP_DEFER_ACCEPT和TCP_QUICKACK选项的组合,参与每一HTTP交互的数据包数量将被降低到最小的可接
受水平(根据TCP协议的要求和安全方面的考虑)。结果不仅是获得更快的数据传输和请求处理速度而且还使客户/服务器双向延迟实现了最小化。
关于这些选项的详细情况请查看 Linux Programmer‘s Manual
如下内容转自http://hi.baidu.com/taozpwater/item/55983ec132eb304fa8ba9423
今天发现setsockopt的用法太强大了,现在记录下该函数的使用方法:
简述:
setsockopt函数用于设置套接口的选项。
函数定义如下:
int setsockopt(
__in SOCKET s,
__in int level,
__in int optname,
__in const char *optval,
__in int optlen
);
s:标识一个套接口的描述字。
level:选项定义的层次;目前仅支持SOL_SOCKET、IPPROTO_TCP、NSPROTO_IPX层次。
optname:需设置的选项。
optval:指针,指向存放选项值的缓冲区。
optlen:optval缓冲区长度。
详解:
level = SOL_SOCKET
SO_BROADCAST BOOL 允许套接口传送广播信息
SO_CONDITIONAL_ACCEPT BOOL Enables incoming connections are to be accepted or rejected by the application, not by the protocol stack.
SO_DEBUG BOOL 记录调试信息
SO_DONTLINGER BOOL 不要因为数据未发送就阻塞关闭操作。设置本选项相当于将SO_LINGER的l_onoff元素置为零。
SO_DONTROUTE BOOL 禁止选径;直接传送。This option is not supported on ATM sockets (results in an error).
SO_GROUP_PRIORITY int Reserved.
SO_KEEPALIVE BOOL 发送“保持活动”包。 Not supported on ATM sockets (results in an error).
SO_LINGER LINGER Lingers on close if unsent data is present.
SO_OOBINLINE BOOL Indicates that out-of-bound data should be returned in-line with regular data. This option is only valid for connection-oriented protocols that support out-of-band data. For a discussion of this topic, see Protocol Independent Out-Of-band Data.
SO_RCVBUF int 为接收确定缓冲区大小
SO_REUSEADDR BOOL Allows the socket to be bound to an address that is already in use. For more information, see bind. Not applicable on ATM sockets.
SO_EXCLUSIVEADDRUSE BOOL Enables a socket to be bound for exclusive access. Does not require administrative privilege.
SO_RCVTIMEO DWORD Sets the timeout, in milliseconds, for blocking receive calls.
SO_SNDBUF int 指定发送缓冲区大小。
SO_SNDTIMEO DWORD The timeout, in milliseconds, for blocking send calls.
SO_UPDATE_ACCEPT_CONTEXT int Updates the accepting socket with the context of the listening socket.
PVD_CONFIG Service Provider Dependent This object stores the configuration information for the service provider associated with socket s. The exact format of this data structure is service provider specific.
level = IPPROTO_TCP
TCP_NODELAY BOOL 禁止发送合并的Nagle算法。This socket option is included for backward compatibility with Windows Sockets1.1
level = IPPROTO_UDP
UDP_NOCHECKSUM BOOL When TRUE, UDP datagrams are sent with the checksum of zero. Required for service providers. If a service provider does not have a mechanism to disable UDP checksum calculation, it may simply store this option without taking any action.
level = NSPROTO_IPX
IPX_PTYPE int Sets the IPX packet type.
IPX_FILTERPTYPE int Sets the receive filter packet type
IPX_STOPFILTERPTYPE int Stops filtering the filter type set with IPX_FILTERTYPE
IPX_DSTYPE int Sets the value of the data stream field in the SPX header on every packet sent.
IPX_EXTENDED_ADDRESS BOOL Sets whether extended addressing is enabled.
IPX_RECVHDRBOOLSets whether the protocol header is sent up on all receive headers.
IPX_RECEIVE_BROADCAST BOOL Indicates broadcast packets are likely on the socket. Set to TRUE by default. Applications that do not use broadcasts should set this to FALSE for better system performance.
IPX_IMMEDIATESPXACK BOOL Directs SPX connections not to delay before sending an ACK. Applications without
back-and-forth traffic should set this to TRUE to increase performance.
level = IPPROTO_IP
IP_ADD_MEMBERSHIP struct ip_mreq Joins the socket to multicast group on the specified interface.
IP_DROP_MEMBERSHIP struct ip_mreq Leaves the specified multicast group from the specified interface. Service providers must support this option when multicast is supported. Support is indicated in the WSAPROTOCOL_INFO structure returned by a WSAEnumProtocols function call with the following: XPI_SUPORTS_MULTIPOINT=1, XP1_MULTIPOINT_CONTROL_PLANE=0, XP1_MULTIPOINT_DATA_PLANE=0.
IP_MULTICAST_IF DWORD Sets the outgoing (IPv4) interface for multicast traffic. Important for multihomed machines. The input value is the 4-byte network byte order IPv4 address. If optval is null, the default interface is used.
IP_MULTICAST_TTL DWORD Sets/gets the TTL value associated with IP multicast traffic on the socket.
level = SOL_IRLMP
IRLMP_ENUMDEVICES DEVICELIST Returns a list of IrDA device IDs for IR capable devices within range.
IRLMP_IAS_QUERY IAS_QUERY Queries IAS on a given service and class name for its attributes.
IRLMP_IAS_SET IAS_SET Enables an application to set a single class in the local IAS. The application specifies the class to set, the attribute, and attribute type.
IRMP_IRLPT_MODE int Disables TinyTP mode and sends data directly over IrLMP frames.
IRLMP_EXCLUSIVE_MODE int Sets socket to bypass TinyTP layer to directly communicate with IrLMP
IRLMP_9WIRE_MODE int Puts the IrDA socket into IrCOMM mode
IRLMP_SHARP_MODE int Enables the Sharp mode.
IRLMP_SEND_PDU_LEN int Retrieves the maximum PDU length required to use IRLMP_9WIRE_MODE
IPV6_ADD_MEMBERSHIP struct ipv6_mreq Join the supplied multicast group on the given interface index
IPV6_DROP_MEMBERSHIP struct ipv6_mreq Leave the supplied multicast group from the given interface
IPV6_HDRINCL BOOL Indicates IPv6 header will be supplied on all outgoing data
IPV6_HOPLIMIT BOOL Indicates that hop (TTL) information should be returned in the WSARecvMsg function
IPV6_JOIN_GROUP struct ipv6_mreq Same as IPV6_ADD_MEMBERSHIP
IPV6_LEAVE_GROUP struct ipv6_m Same as IPV6_DROP_MEMBERSHIP
IPV6_MULTICAST_HOPS DWORD Sets/gets the TTL value associated with IPv6 multicast traffic on the socket
IPV6_MULTICAST_IF DWORD Sets the outgoing (IPv6) interface for multicast traffic. This is important for multihomed
machines. The input value is the 4-byte interface index of the desired outgoing interface (use GetAdaptersAddresses to obtain index information).
IPV6_MULTICAST_LOOP BOOL Indicates multicast data sent on socket will be echoed to the sockets receive buffer if it is also joined on the destination multicast group
IPV6_PKTINFO BOOL Indicates that packet information should be returned in the WSARecvMsg function
IPV6_PROTECTION_LEVEL INT Enables restriction of a socket to a specified scope, such as addresses with the same link local or site local prefix. Provides various restriction levels and default settings. See Using IPV6_PROTECTION_LEVEL for more information.
IPV6_UNICAST_HOPS DWORD Sets/gets the current TTL value associated with IPv6 socket
返回值:
若无错误发生,setsockopt()返回0。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。
错误代码:
WSANOTINITIALISED:在使用此API之前应首先成功地调用WSAStartup()。
WSAENETDOWN:WINDOWS套接口实现检测到网络子系统失效。
WSAEFAULT:optval不是进程地址空间中的一个有效部分。
WSAEINPROGRESS:一个阻塞的WINDOWS套接口调用正在运行中。
WSAEINVAL:level值非法,或optval中的信息非法。
WSAENETRESET:当SO_KEEPALIVE设置后连接超时。
WSAENOPROTOOPT:未知或不支持选项。其中,SOCK_STREAM类型的套接口不支持SO_BROADCAST选项,SOCK_DGRAM类型的套接口不支持SO_DONTLINGER 、SO_KEEPALIVE、SO_LINGER和SO_OOBINLINE选项。
WSAENOTCONN:当设置SO_KEEPALIVE后连接被复位。
WSAENOTSOCK:描述字不是一个套接口。
实例:
1.设置调用closesocket()后,仍可继续重用该socket。调用closesocket()一般不会立即关闭socket,而经历TIME_WAIT的过程。
BOOL bReuseaddr = TRUE; setsockopt( s, SOL_SOCKET, SO_REUSEADDR, ( const char* )&bReuseaddr, sizeof( BOOL ) ); 2. 如果要已经处于连接状态的soket在调用closesocket()后强制关闭,不经历TIME_WAIT的过程: BOOL bDontLinger = FALSE;
setsockopt( s, SOL_SOCKET, SO_DONTLINGER, ( const char* )&bDontLinger, sizeof( BOOL ) );
3.在send(),recv()过程中有时由于网络状况等原因,收发不能预期进行,可以设置收发时限: int nNetTimeout = 1000; //1秒
//发送时限
setsockopt( socket, SOL_SOCKET, SO_SNDTIMEO, ( char * )&nNetTimeout, sizeof( int ) );
//接收时限
setsockopt( socket, SOL_SOCKET, SO_RCVTIMEO, ( char * )&nNetTimeout, sizeof( int ) );
4.在send()的时候,返回的是实际发送出去的字节(同步)或发送到socket缓冲区的字节(异步);系统默认的状态发送和接收一次为8688字节(约为8.5K);在实际的过程中如果发送或是接收的数据量比较大,可以设置socket缓冲区,避免send(),recv()不断的循环收发: // 接收缓冲区
int nRecvBuf = 32 * 1024; //设置为32K
setsockopt( s, SOL_SOCKET, SO_RCVBUF, ( const char* )&nRecvBuf, sizeof( int ) );
//发送缓冲区
int nSendBuf = 32*1024; //设置为32K
setsockopt( s, SOL_SOCKET, SO_SNDBUF, ( const char* )&nSendBuf, sizeof( int ) ); 5.在发送数据的时,不执行由系统缓冲区到socket缓冲区的拷贝,以提高程序的性能: int nZero = 0;
setsockopt( socket, SOL_S0CKET, SO_SNDBUF, ( char * )&nZero, sizeof( nZero ) ); 6.在接收数据时,不执行将socket缓冲区的内容拷贝到系统缓冲区: int nZero = 0;
setsockopt( s, SOL_S0CKET, SO_RCVBUF, ( char * )&nZero, sizeof( int ) ); 7.一般在发送UDP数据报的时候,希望该socket发送的数据具有广播特性: BOOL bBroadcast = TRUE;
setsockopt( s, SOL_SOCKET, SO_BROADCAST, ( const char* )&bBroadcast, sizeof( BOOL ) ); 8.在client连接服务器过程中,如果处于非阻塞模式下的socket在connect()的过程中可以设置connect()延时,直到accpet()被调用(此设置只有在非阻塞的过程中有显著的作用,在阻塞的函数调用中作用不大) BOOL bConditionalAccept = TRUE;
setsockopt( s, SOL_SOCKET, SO_CONDITIONAL_ACCEPT, ( const char* )&bConditionalAccept, sizeof( BOOL ) );
Linux下getsockopt/setsockopt 函数说明