一、函数原型
#include <sys/types.h >
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
-
sockfd:标识一个套接口的描述字
-
level:选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6
-
optname:层次为SOL_SOCKET时可设置的选项,而有部分选项需在listen/connect调用前设置才有效,选项如下:
选项名称 说明 数据类型 O_BROADCAST 允许发送广播数据 int O_DEBUG 允许调试 int O_DONTROUTE 不查找路由 int O_ERROR 获得套接字错误 int O_KEEPALIVE 保持连接 int O_LINGER 延迟关闭连接 struct linger O_OOBINLINE 带外数据放入正常数据流 int O_RCVBUF 接收缓冲区大小 int O_SNDBUF 发送缓冲区大小 int O_RCVLOWAT 接收缓冲区下限 int O_SNDLOWAT 发送缓冲区下限 int O_RCVTIMEO 接收超时 struct timeval O_SNDTIMEO 发送超时 struct timeval O_REUSERADDR 允许重用本地地址和端口 int O_TYPE 获得套接字类型 int O_BSDCOMPAT 与BSD系统兼容 int -
optval:指针,指向存放选项值的缓冲区
-
optlen:optval缓冲区长度
-
返回值:成功执行时,返回0。失败返回-1,errno被设为以下的某个值:
- EBADF:sock不是有效的文件描述词
- EFAULT:optval指向的内存并非有效的进程空间
- EINVAL:在调用setsockopt()时,optlen无效
- ENOPROTOOPT:指定的协议层不能识别选项
- ENOTSOCK:sock描述的不是套接字
二、使用场景
-
如果在已经处于 ESTABLISHED状态下的socket(一般由端口号和标志符区分)调用close(socket)(一般不会立即关闭,而是需要经历TIME_WAIT的过程)后想继续重用该socket:
int reuseAddrOn = 1; setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuseAddrOn, sizeof(reuseAddrOn));
注意:必须在调用bind函数之前设置SO_REUSEADDR选项。
TIME_WAIT是什么?
UNP书上说,TIME_WAIT状态有两个存在的理由:
(1)可靠地实现TCP全双工连接的终止;
(2)允许老的重复分节在网络中消逝。
(1)如果服务器最后发送的ACK因为某种原因丢失了,那么客户一定会重新发送FIN,这样因为有TIME_WAIT的存在,服务器会重新发送ACK给客户,如果没有TIME_WAIT,那么无论客户有没有收到ACK,服务器都已经关掉连接了,此时客户重新发送FIN,服务器将不会发送ACK,而是RST,从而使客户端报错。也就是说,TIME_WAIT有助于可靠地实现TCP全双工连接的终止。
(2)如果没有TIME_WAIT,我们可以在最后一个ACK还未到达客户的时候,就建立一个新的连接。那么此时,如果客户收到了这个ACK的话,就乱套了,必须保证这个ACK完全死掉之后,才能建立新的连接。也就是说,TIME_WAIT允许老的重复分节在网络中消逝。
回到我们的问题,由于我并不是正常地经过四次断开的方式中断连接,所以并不会存在最后一个ACK的问题。不过,最终的服务器版本,还是不要设置为端口可复用的。 -
如果要已经处于连接状态的soket在调用close(socket)后强制关闭,不经历TIME_WAIT的过程:
int reuseAddrOn = 0; setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuseAddrOn, sizeof(reuseAddrOn));
-
在send(),recv()过程中有时由于网络状况等原因,收发不能预期进行,而设置收发时限:
int nNetTimeout=1000; //1秒 //发送时限 setsockopt(sockfd, SOL_S0CKET, SO_SNDTIMEO, (char *)&nNetTimeout, sizeof(nNetTimeout)); //接收时限 setsockopt(sockfd, SOL_S0CKET, SO_RCVTIMEO, (char *)&nNetTimeout, sizeof(nNetTimeout));
-
在send()的时候,返回的是实际发送出去的字节(同步)或发送到socket缓冲区的字节(异步),系统默认的状态发送和接收一次为8688字节(约为8.5K);在实际的过程中发送数据和接收数据量比较大,可以设置socket缓冲区,而避免了send(),recv()不断的循环收发:
//接收缓冲区 int recvBufLen = 32*1024; //设置为32K setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, (const char*)&recvBufLen, sizeof(recvBufLen)); //发送缓冲区 int sendBufLen = 32*1024; //设置为32K setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (const char*)&sendBufLen, sizeof(sendBufLen));
注意:
(1). 并不是说你设置的多大,系统就会设置多大,系统一般会将我们设置的缓冲区大小加倍,并且不得小于tcp的接收缓冲区和发送缓冲区设置的默认最小值。
(2). TCP有发送缓冲区和接收缓冲区,但是UDP因为是不可靠的,它没有确认重传机制,不保存应用程序数据的副本,所以是没有发送缓冲区的,但是UDP有接收缓冲区。 -
如果在发送数据时,希望不经历由系统缓冲区到socket缓冲区的拷贝而影响程序的性能:
int nZero = 0; setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (char *)&nZero, sizeof(nZero));
-
同上在recv()完成上述功能(默认情况是将socket缓冲区的内容拷贝到系统缓冲区):
int nZero = 0; setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, (char *)&nZero, sizeof(nZero));
-
一般在发送UDP数据报的时候,希望该socket发送的数据具有广播特性:
int bBroadcast = 1; setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, (const char*)&bBroadcast, sizeof(bBroadcast));
-
设置存活检测
int opt = 1; setsockopt (sockfd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt));
用于TCP socket检测/保持网络连接,这个选项被设置后,2小时以内双方没有数据交互,将发送一个探测数据段到对方,此时可能出现以下情况:
a. 得到对方正确响应,继续等待下一次2小时超时;
b. 收到RST数据段,返回错误ECONNRESET;
c. 对方无响应,多次发送探测数据段直到超时返回错误ETIMEOUT;
d. 选项值类型:int,0—不能发送;1—可以发送,默认值为0;对setsockopt和getsockopt有效; -
延迟接收
实际上就是当接收到第一个数据之后,才会创建连接。对于像http这类非交互式的服务器,这个很有意义,可以防御空连接攻击。int val = 5; setsockopt(sockfd, SOL_TCP, TCP_DEFER_ACCEPT, &val, sizeof(val));
打开这个功能后,内核在val时间之类还没有收到数据,不会继续唤醒进程,而是直接丢弃连接。
从三次握手上讲,就是设置这个状态之后,就算完成了三次握手,服务器socket状态也不是ESTABLISHED,而依然是 SYN_RCVD,不会去接收数据。