SOCKET选项

在了解SOCKET选项之前请先了解TCP/IP协议的基本知识,如三次握手,四次挥手,11种状态之间是如何迁移,调用socket各个API会让TCP层进行什么操作等。
API头文件: sys/socket.h>
获取指定socket连接的选项
int getsockopt(int sock, int level, int optname, void optval, socklen_t optlen);
设置指定socket连接的选项
int setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen);

example:

int sockfd = socket(AF_INET,SOCK_STREAM,0);
int recvbuf = 0;
int len = sizeof( recvbuf );
getsockopt( sock, SOL_SOCKET, SO_RCVBUF, &recvbuf, ( socklen_t* )&len);
printf("old recvbuf = %d",recvbuf);
  
recvbuf = 1024*30;
if(0 != setsockopt( sock, SOL_SOCKET, SO_RCVBUF, &recvbuf, sizeof( recvbuf) ))
{
  printf("setsockopt SO_RCVBUF error“);
}

使用setsockopt()的时间:服务器要求在listen()之前,客户端要求在connect()之前,因为有些选项诸如SO_RCVBUF的信息,是包含在三次握手中,因此必须在三次握手前完成设置,服务器端accept产生的socket连接,会自动继承listen前设置的大部分选项,如SO_LINGER、SO_SNFBUF、SO_RCVBUF。

参数:
sock:将要被设置或者获取选项的套接字。
level:选项所在的协议层。
optname:需要访问的选项名。
optval:对于getsockopt(),指向返回选项值的缓冲。对于setsockopt(),指向包含新选项值的缓冲。
optlen:对于getsockopt(),作为入口参数时,选项值的最大长度。作为出口参数时,选项值的实际长度。对于setsockopt(),现选项的长度。

返回说明:
成功执行时,返回0。失败返回-1,errno被设为以下的某个值
EBADF:sock不是有效的文件描述词
EFAULT:optval指向的内存并非有效的进程空间
EINVAL:在调用setsockopt()时,optlen无效
ENOPROTOOPT:指定的协议层不能识别选项
ENOTSOCK:sock描述的不是套接字

参数详细说明:

level指定控制套接字的层次.可以取三种值:
1)SOL_SOCKET:通用套接字选项.
2)IPPROTO_IP:IP选项.
3)IPPROTO_TCP:TCP选项. 
optname指定控制的方式(选项的名称)
optval获得或者是设置套接字选项的值。

常用的Socket中SOL_SOCKET有以下几个重要的optname选项。

SO_RESUSEADDR:表示是否允许重用Socket所绑定的本地地址
SO_LINGER:表示当执行Socket的close()方法时,是否立即关闭底层的Socket
SO_SNFBUF:表示发送数据的缓冲区大小
SO_RCVBUF:表示接受数据的缓冲区大小

必知必会的选项:
1.SO_REUSEADDR
服务器可以设置该选项来强制使用处于TIME_WAIT状态的socket地址。如果有两个进程,第一个进程的socket处于TIME_WAIT状态,第二个进程要使用同样的地址和端口,只需要第二个进程的socket有设置该选项即可。

应用场景:如果服务器主动关闭了应用,没有设置该选项,重启服务器的该应用,会出现失败,无法绑定端口,因为端口还没有释放。需要等待2MSL时间后才能重新绑定,一般情况下是2-4分钟。

SO_REUSEADDR滥用容易引发bug,UNP1经典书籍列举了只有以下四种情况适用该选项:
1.当有一个有相同本地地址和端口的socket1处于TIME_WAIT状态时,而你启动的程序的socket2要占用该地址和端口,你的程序就要用到该选项。另外作为服务daemon一般都会使用SO_REUSEADDR,避免服务意外崩溃而原有socket还未被kernel释放时,重启的daemon仍然可以bind成功。
2.SO_REUSEADDR允许同一port上启动同一服务器的多个实例。但每个实例绑定的IP地址是不能相同的。在有多块网卡或用IP Alias技术的机器可以测试这种情况。
3.SO_REUSEADDR允许单个进程绑定相同的端口到多个socket上,但每个socket绑定的ip地址不同。这和2很相似,区别请看UNPv1。
4.SO_REUSEADDR允许完全相同的地址和端口的重复绑定。但这只用于UDP的多播,不用于TCP。

有一个滥用该选项导致的异常例子,http://blog.chinaunix.net/uid-23629988-id-217123.html

2.SO_LINGER:
此选项指定函数close对面向连接的协议如何操作(如TCP),内核缺省close操作是将立即返回,如果有数据残留在套接口缓冲区中则系统将试着将这些数据发送给对方。close函数是将当前的fd引用数减1,如果引用数已经为0,则在发送队列的末尾插入FIN标志,给对端发送FIN报文段。

选项中需要用到的结构体如下

struct linger
{ 
int l_onoff //0=off, nonzero=on(开关) 
int l_linger //linger time(延迟时间) 
} 

example:

int sockfd = socket(AF_INET,SOCK_STREAM,0);
struct linger so_linger;
so_linger.l_onoff = TRUE;
so_linger.l_linger = 30;
if(0 != setsockopt(sockfd,SOL_SOCKET,SO_LINGER,&so_linger,sizeof (so_linger)))
{
  printf("setsockopt SO_LINGER error“);
}

1:默认设置
默认状态下l_onoff==0,此时忽略l_linger,调用closesocket()会将FIN包放到队列末尾,然后函数返回
效果是TCP会将队列残留的数据发完,然后发送FIN,执行四次挥手。

2:l_onoff!=0&&l_linger==0
调用closesocket(),会清空未发队列的数据,然后投递一个RST,函数返回,这避免了TIME_WAIT状态。

3:l_onoff!=0&&l_linger!=0
调用closesocket(),向队列投递一个FIN,设置一个计时器,大小为l_linger,单位为秒。

1:收到所有发送出去的数据和FIN的ACK时未超时:
函数返回,并执行四次挥手的剩余步骤。(当应用层调用close时候,实际上服务器已经处于close_wait状态,已经完成了四次挥手的前两个步骤

2:超时:
函数返回SOCKET_ERROR,WINDOW系统下,WSAGetLastError()返回EWOULDBLOCK,清空队列,在对方发送FIN后,发送RET

3:备注:
对于1所述情况,我们只能确定对方TCP已经收到数据,但无法确定对方进程是否已经收到数据。
因为对端接受到FIN后,TCP层会回发一个应答报文,对端从FIN_WAIT1状态迁移到TIME_WAIT状态,在2MSL后,从TIME_WAIT状态迁移到CLOSED状态。这一部分工作都是TCP在自动执行,不需要上层进程操作,所以我们无法确定对方进程是否已经收到数据。

上一篇:ORACLE基础应用学习-- 各种故障的恢复方法总结


下一篇:PolarDB-X 1.0-SQL 手册-函数-信息函数