套接字的多种可选项

下列是针对SOL_SOCKET协议层的

可选项描述
SO_REUSEADDR允许重用本地地址和端口,即使之前的连接处于 TIME_WAIT 状态。
SO_KEEPALIVE启用 TCP 连接的心跳检测功能,保持连接活动状态。
SO_LINGER控制关闭连接时的行为。设置为 0 表示立即关闭连接,非零值则表示等待一段时间再关闭。
SO_RCVBUF控制套接字接收缓冲区的大小。
SO_SNDBUF控制套接字发送缓冲区的大小。
SO_RCVTIMEO设置套接字接收操作的超时时间。
SO_SNDTIMEO设置套接字发送操作的超时时间。
SO_BROADCAST允许广播发送和接收。
SO_DEBUG启用调试模式,在调试期间捕获套接字的调试信息。
SO_ERROR获取套接字错误状态。
SO_REUSEPORT允许多个套接字绑定到相同的地址和端口上。
SO_TYPE获取套接字的类型。
下列是针对IPPROTO_TCP协议层的

可选项描述
TCP_NODELAY禁用 Nagle 算法,允许小数据包立即发送。
TCP_MAXSEG设置 TCP 数据包的最大段长度(Maximum Segment Size,MSS)。
TCP_KEEPALIVE启用 TCP 连接的心跳检测功能,保持连接活动状态。
TCP_KEEPIDLE设置 TCP 连接的空闲时间阈值,超过阈值会发送心跳包。
TCP_KEEPINTVL设置 TCP 心跳包的发送间隔。
TCP_KEEPCNT设置 TCP 心跳包的发送次数,达到次数仍无响应则关闭连接。
TCP_LINGER2控制关闭连接时的行为。设置为 0 表示立即关闭连接,非零值则表示等待一段时间再关闭。
TCP_SYNCNT设置 TCP 发送 SYN 数据包的最大次数。
下列是针对IPPROTO_IP协议层的

可选项描述
IP_TTL设置 IP 头部的生存时间(Time To Live),指定数据包在网络中可以传输的最大跳数。
IP_HDRINCL控制应用程序是否提供自定义 IP 头部。
IP_OPTIONS设置自定义 IP 选项。
IP_RECVOPTS允许应用程序接收来自对方发送的 IP 选项。
IP_RETOPTS允许应用程序发送自定义的返回路径 IP 选项。
IP_PKTINFO允许应用程序获得接收到的数据包的相关信息,如源地址、目标地址和接口索引等。
IP_RECVTOS允许应用程序接收来自对方发送的服务类型(Type of Service)字段。
IP_RECVTTL允许应用程序接收来自对方发送的生存时间(Time To Live)字段。
IP_MULTICAST_IF控制多播套接字的出站接口。
也许有些人看到上述表格会产生畏惧,但现在无需全部背下来或者理解。本章只会介绍少数几种选项的设置。

getsockopt&setsockopt

可选项的设置可以依照下面两个函数完成设置

下面函数是用于读取套接字可选项
#include<sys/socket.h> int getsockopt(int sock,int level,int optname,void
*optval,socklen_t*optlen); //成功时返回0,失败时返回-1 sock //用于查看选项套接字文件描述符 level
//查看可选项的协议层 optname //要查看的可选项名 optval //保存查看结果的缓冲地址值 optlen
//该变量中保存通过第四个参数返回的可选信息的字节数
下面函数是用于设置可选项的函数
#include<sys/socket.h> int setsockopt(int sock,int level,int optname,const
void*optval,socklen_t optlen); //成功时返回0,失败时返回-1 sock //用于更改可选项的套接字文件描述符 level
//用于更改可选项协议层 optname //要更改的可选项名 optval //用于保存更改的可选项信息的缓冲地址值 optlen //传递第四个参数的字节数
具体的演示放在下一小节里面展示

SO_SNDBUF&SO_RCVBUF

SO_RCVBUF是输入缓冲大小相关可选项,SO_SNDBUF是输出缓冲大小相关可选项。用这两个可选项即可读取当前IO缓冲大小,可以进行更改。

下面的演示是获取具体缓冲大小
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/socket.h>
void error_handling(char*message); int main(int argc,char*argv[]){ int sock;
int snd_buf,rcv_buf,state; socklen_t len; sock=socket(PF_INET,SOCK_STREAM,0);
len=sizeof(snd_buf);
state=getsockopt(sock,SOL_SOCKET,SO_SNDBUF,(void*)&snd_buf,&len);
if(state)error_handling("getsockopt() error"); len=sizeof(rcv_buf);
state=getsockopt(sock,SOL_SOCKET,SO_RCVBUF,(void*)&rcv_buf,&len);
if(state)error_handling("getsockopt() error"); printf("Input buffer size: %d
\n",rcv_buf); printf("Output buffer size: %d \n",snd_buf); return 0; } void
error_handling(char*message){ fputs(message,stderr); fputc('\n',stderr);
exit(1); }
下面的演示是修改具体缓冲大小
#include<"声明与上一个示例相同,故省略"> void error_handling(char*message); int main(int
argc,char* argv[]){ int sock; int snd_buf=1024*3,rcv_buf=1024*3; int state;
socklen_t len; sock=socket(PF_INET,SOCK_STREAM,0);
state=setsockopt(sock,SOL_SOCKET,SO_RCVBUF,(void*)&rcv_buf,sizeof(rcv_buf));
if(state)error_handling("setsockopt() error");
state=setsockopt(sock,SOL_SOCKET,SO_SNDBUF,(void*)&snd_buf,sizeof(snd_buf));
if(state)error_handling("setsockopt() error"); len=sizeof(snd_buf);
state=getsockopt(sock,SOL_SOCKET,SO_SNDBUF,(void*)&snd_buf,&len);
if(state)error_handling("getsockopt() error"); len=sizeof(rcv_buf);
state=getsockopt(sock,SOL_SOCKET,SO_RCVBUF,(void*)&rcv_buf,&len);
if(state)error_handling("getsockopt() error"); printf("Input buffer size: %d
\n",rcv_buf); printf("Output buffer size: %d \n",snd_buf); return 0; } void
error_handling(char*message){ fputs(message,stderr); fputc('\n',stderr);
exit(1); }

最后输出的结果可能会和我们设置的大小完全不同。因为再缓冲大小上需要谨慎处理,因此不会100%的按照我梦的请求设置缓冲大小,但也大致反映出了通过setsockopt函数设置缓冲大小。

SO_REUSEADDR

先描述一下前面章节的代码情景,让客户端先通知服务器端终止程序。在客户端控制台输入Q消息时调用close函数(参考第4章),向服务器端发送FIN消息并经过四次握手过程。当然,输入CTRL+C时也会向服务器传递FIN消息。强制终止程序时,由操作系统关闭文件及套接字,此过程相当于调用close函数,也会向服务器端传递FIN消息。“但看不到什么特殊现象啊?”
是的,通常都是由客户端先请求断开连接,所以不会发生特别的事情。重新运行服务器端也
不成问题,但按照如下方式终止程序时则不同。
“服务器端和客户端已建立连接的状态下,向服务器端控制台输入CTRL+C,即强
制关闭服务器端。”
这主要模拟了服务器端向客户端发送FIN消息的情景
。但如果以这种方式终止程序,那服务器端重新运行时将产生问题。如果用同一端口号重新运行服务器端,将输出“bind()
error”消息,并且无法再次运行。但在这种情况下,再过大约3分钟即可重新运行服务器端。
上述2种运行方式唯一的区别就是谁先传输FIN消息,但结果却迥然不同,原因何在呢?

Time_wait状态

相信各位已经对四次握手有了很好的理解,现在再来描述一下上述过程。假设主机A是服务器端,因为是主机A向B发送FIN消息,故可以想象成服务器端在控制台输入CTRL+C。但问题是套接字经过四次握手过程后并非立即消除,而是要经过一段时间的Time-wait状态。当然,只有先断开连接的(先发送FIN消息的)主机才经过Time-wait状态。因此,若服务器端先断开连接,则无法立即重新运行。套接字处在Time-wait过程时,相应端口是下在使用的状态。因此,就像之前验证过的,bind函数调用过程中当然会发生错误。到底为什么会有Time-wait状态呢?

假设主机A向主机B传输ACK消息后立即消除套接字。但最后这条ACK消息在传递途中丢失,未能传给主机B。这时会发生什么?主机B会认为之前自己发送的FIN消息未能抵达主机A,维而试图重传。但此时主机A已是完全终止的状态,因此主机B永远无法收到从主机A最后传来的ACK消息。相反,若主机A的套接字处在Time-wait状态,则会向主机B重传最后的ACK消息,主机B也可以正常终止。基于这些考虑,先传输FIN消息的主句应经过Time-wait过程。

地址再分配

Time-wait看似重要,但并不一定讨人喜欢。考虑一下系统发生故障从而紧急停止的情况。这时需要尽快重启服务器端以提供服务,但因处于Time-wait状态而必须等待几分钟。因此,Time-wait并非只有优点,而且有些情况下可能引发更大问题。

在主机A的四次握手过程中,如果最后的数据丢失,则主机B会认为主机A未能收到自己发送的FIN消息,因此重传。这时,收到FIN消息的主机A将重启Time-wait计时器。因此,如果网络状况不理想,Time-wait状态将持续。

解决方案就是在套接字的可选项中更改SO_REUSEADDR的状态。适当调整该参数,可将Time-wait状态下的套接字端口号重新分配给新的套接字。SO_REUSEADDR的默认值为0(假),这就意味着无法分配Time-wait状态下的套接字端口号。因此需要将这个值改成1(真)。

只需要添加入如下代码即可
optlen=sizeof(option); option=TRUE; setsockopt(serv_sock, SOL_SOCKET,
SO_REUSEADDR,(void*)&option,optlen);

基于Windows的实现

套接字可选项及其相关内容与操作系统无关,特别是本章的可选项,因此在Windows平台和Linux平台下并无区别。接下来介绍读取和设定的两个函数。
#include <winsock2.h> int getsockopt(SOCKET sock, int level, int optname, char
* optval, int * optlen); //成功时返回0,失败时返回SOCKET_ERROR。 sock //要查看可选项的套接字句柄。 level
//要查看的可选项协议层。 optname //要查看的可选项名。 optval //保存查看结果的缓冲地址值。 optlen
//向第四个参数optval传递的缓冲大小。调用结束后,该变量中保存通过第四个参数返回的可 //选项字节数。

可以看到,除了optval类型变成char指针外,与Linux中的getsockopt函数相比并无太大区别(Linux中是void型指针)。将Linux中的示例移植到Windows时,应做适当的类型转换。接下来给出setsockopt函数。
#include <winsock2.h> int setsockopt(SOCKET sock, int level, int optname,
const char* optval, int optlen); //成功时返回0,失败时返回SOCKET_ERROR。 sock
//要更改可选项的套接字句柄。 level //要更改的可选项协议层。 optname //要更改的可选项名。 optval
//保存要更改的可选项信息的缓冲地址值。 optlen //传入第四个参数optval的可选项信息的字节数。
setsockopt函数也与Linux版的毫无二致。

技术
下载桌面版
GitHub
百度网盘(提取码:draw)
Gitee
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:766591547
关注微信