UNP总结 Chapter 7 套接字选项
1.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); //均返回:若成功为0,出错为-1
其中sockfd必须指向一个打开套接字描述符,level(级别)指定系统中解释选项的代码或为通用套接字代码,或为某个特定于协议的代码,optval是一个指向某个变量(*optval)的指针,setsockopt从*optval中取得选项待设置的新值,getsockopt则把已获取的选项当前值存放到*optval,*optval的大小由最后一个参数optlen指定,它对于setsockopt是一个值参数,对于getsockopt是一个值-结果参数
套接字选项大致分为两大基本类型:一是启用或禁止某个特性的二院选项(成为标志选项),二是取得并返回我们可以设置或检查的特定值的选项(称为值选项)
调用getsockopt函数时,*optval是一个整数,*optval中返回的值为0,表示相应选项被禁止,不为0表示相应项被启用,类似地,setsockopt函数需要一个不为0的*optval值里启用选项,一个为0的*optval值来禁止选项。
简而言之,上述两个函数就是通过控制套接字的选项来控制套接字的行为(如修改缓冲区的大小)。这里给它们的功能描述:
获取或者设置与某个套接字关联的选 项。选项可能存在于多层协议中,它们总会出现在最上面的套接字层。当操作套接字选项时,选项位于的层和选项的名称必须给出。为了操作套接字层的选项,应该将层的值指定为SOL_SOCKET。为了操作其它层的选项,控制选项的合适协议号必须给出。
以上内容为UNP原文 这里对参数做一下总结
参数:
sock:将要被设置或者获取选项的套接字。
level:选项所在的协议层。
optname:需要访问的选项名。
optval:对于getsockopt(),指向返回选项值的缓冲。对于setsockopt(),指向包含新选项值的缓冲。
optlen:对于getsockopt(),作为入口参数时,选项值的最大长度。作为出口参数时,选项值的实际长度。对于setsockopt(),现选项的长度。
返回说明:
成功执行时,返回0。失败返回-1,errno被设为以下的某个值
EBADF:sock不是有效的文件描述词
EFAULT:optval指向的内存并非有效的进程空间
EINVAL:在调用setsockopt()时,optlen无效
ENOPROTOOPT:指定的协议层不能识别选项
ENOTSOCK:sock描述的不是套接字
2.通用、IPv4和TCP套接字选项
level指定控制套接字的层次.常用的有三种值:
1)SOL_SOCKET:通用套接字选项.
2)IPPROTO_IP:IP选项.
3)IPPROTO_TCP:TCP选项.
来自网上整理出的常用套接字选项总结详见UNP3)
选项名称 说明 数据类型
========================================================================
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
========================================================================
部分详解:
1).SO_LINGER
此选项指定函数close对面向连接的协议如何操作(如TCP)。内核缺省close操作是立即返回,如果有数据残留在套接口缓冲区中则系统将试着将这些数据发送给对方。
SO_LINGER选项用来改变此缺省设置。使用如下结构:
struct linger { int l_onoff; /* 0 = off, nozero = on */ int l_linger; /* linger time */ };
有下列三种情况:
a.设置 l_onoff为0,则该选项关闭,l_linger的值被忽略,等于内核缺省情况,close调用会立即返回给调用者,如果可能将会传输任何未发送的数据;
b.设置 l_onoff为非0,l_linger为0,则套接口关闭时TCP夭折连接,TCP将丢弃保留在套接口发送缓冲区中的任何数据并发送一个RST给对方,而不是通常的四分组终止序列,这避免了TIME_WAIT状态;
c.设置 l_onoff 为非0,l_linger为非0,当套接口关闭时内核将拖延一段时间(由l_linger决定)。如果套接口缓冲区中仍残留数据,进程将处于睡眠状态,直
- 所有数据发送完且被对方确认,之后进行正常的终止序列(描述字访问计数为0)
- 延迟时间到。此种情况下,应用程序检查close的返回值是非常重要的,如果在数据发送完并被确认前时间到,close将返回EWOULDBLOCK错误且套接口发送缓冲区中的任何数据都丢失。
close的成功返回仅告诉我们发送的数据(和FIN)已由对方TCP确认,它并不能告诉我们对方应用进程是否已读了数据。如果套接口设为非阻塞的,它将不等待close完成。(详见UNP)
2).SO_RCVBUF和SO_SNDBUF
在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));
3).SO_REUSEADDR
closesocket(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket:
BOOL bReuseaddr=TRUE; setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(BOOL));
4).SO_BROADCAST
closesocket(一般不会立即关闭而经历TIME_WAIT的过程)后想继续重用该socket:
BOOL bReuseaddr=TRUE; setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(BOOL));
5).SO_RCVBUF和SO_SNDBUF
在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));
6).SO_RCVTIMEO和SO_SNDTIMEO
在send(),recv()过程中有时由于网络状况等原因,发收不能预期进行,而设置收发时限:
int nNetTimeout=1000; //1秒 //发送时限 setsockopt(socket,SOL_S0CKET,SO_SNDTIMEO,(char *)&nNetTimeout,sizeof(int)); //接收时限 setsockopt(socket,SOL_S0CKET,SO_RCVTIMEO,(char *)&nNetTimeout,sizeof(int));
7).SO_DONTLINGER
如果要已经处于连接状态的soket在调用closesocket后强制关闭,不经历TIME_WAIT的过程:
BOOL bDontLinger = FALSE; setsockopt(s,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));
8).IP_RECVDSTADDR
设置本套接字选项导致所收到的UDP数据报的目的IP地址由recvmsg函数作为辅助数据返回
9).TCP_MAXSEG
获取或设置TCP连接的最大分节大小(MSS)。返回值是我们的TCP发送给另一端的最大数据量,它常常就是由另一端用SYN分节通告的MSS,除非我们的TCP选择使用一个比 对方通告的MSS小些的值。如果此值在套接口连接之前取得,则返回值为未从另·—端 收到Mss选项的情况下所用的缺省值。小于此返回值的信可能真正用在连接上,因为譬 如说使用时间戳选项的话,它在每个分节上占用12字节的TCP选项容量。
10).TCP_NODELAY
开启本选项将禁止TCP的Nagle算法。默认情况下该算法是启动的
Nagle算法,该算法指出如果某个给定连接上有待确认数据,那么原本应该作为用户写操作之响应的在该连接上立即发送响应小分组的行为就不会发生,直到现有数据被确认为止
开启Nagle算法时TCP通信情况
禁止Nagle算法时TCP通信情况
PS:
与TCP_NODELAY相对应的套接字选项---TCP_CORK:它是一种加强的nagle算法,过程和nagle算法类似,都是累计数据然后发送。但它没有 nagle中1的限制,所以,在设置cork后,即使所有ack都已经收到,但我还是不想发送数据,我还想继续等待应用层更多的数据,所以它的效果比nagle更好。
3.fcntl函数
这个函数在APUE专题有介绍,这里只列出与fcntl函相关的网络编程特性
- 非阻塞I/O。通过使用F_SETFL命令设置O_NONBLOCK文件状态标志,我们可以把一个套接字设置为非阻塞型
- 信号驱动I/O。通过使用F_SETFL命令设置O_ASYNC文件状态标志,我们可以把一个套接字设置成一旦其状态发生变化,内核就产生一个SIGIO信号
- F_SETOWN命令允许我们指定用于接收SIGIO和SIGURG信号的进程ID或进程组ID,。其中SIGIO信号是套接字被设置为信号驱动式I/O型后产生的,SIGURG信号是在新的外带数据到达套接字时产生的。
#include <fcntl.h> int fcntl(intfd, int cmd, ... /* int arg */ ); //返回:若成功取决于cmd,出错-1
每种描述符(包括套接字描述符)都有一组由F_GETFL命令获取或F_SETFL命令设置的文件标志。其中影响套接字描述符的两个标志是:
- O_NONBLOCK--------非阻塞I/O
- O_ASYNC-------------信号驱动式I/O
使用fcntl开启非阻塞I/O的典型代码
int flags; /* Set a socket as nonblocking */ if ( (flags = fcntl (fd, F_GETFL, 0)) < 0) err_sys("F_GETFL error"); flags |= O_NONBLOCK; if (fcntl(fd, F_SETFL, flags) < 0) err_sys("F_SETFL error");
可能会遇到的、简单的设置所期望标识的代码,但是是错误的方法,所以正确方法:先取得当前标志,与新标志逻辑或后再设置标志
/* Wrong way to set a socket as nonblocking */ if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) err_sys("F_SETFL error");
以下代码关闭非阻塞标志,其中假设flags是由上面所示的fcntl调用来设置。
int flags;
/* Set a socket as nonblocking */
if ( (flags = fcntl (fd, F_GETFL, 0)) < 0)
err_sys("F_GETFL error");
flags &= ~O_NONBLOCK; if (fcntl(fd, F_SETFL, flags) < 0) err_sys("F_SETFL error");