TCP/IP网络编程 第九章:套接字的多种可选项

套接字的多种可选项

下列是针对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

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

下面函数是用于读取套接字可选项

cpp 复制代码
#include<sys/socket.h>
int getsockopt(int sock,int level,int optname,void *optval,socklen_t*optlen);
//成功时返回0,失败时返回-1
     sock    //用于查看选项套接字文件描述符
     level   //查看可选项的协议层
     optname //要查看的可选项名
     optval  //保存查看结果的缓冲地址值
     optlen  //该变量中保存通过第四个参数返回的可选信息的字节数

下面函数是用于设置可选项的函数

cpp 复制代码
#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缓冲大小,可以进行更改。

下面的演示是获取具体缓冲大小

cpp 复制代码
#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);
}

下面的演示是修改具体缓冲大小

cpp 复制代码
#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(真)。

只需要添加入如下代码即可

cpp 复制代码
optlen=sizeof(option);
option=TRUE;
setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR,(void*)&option,optlen);

基于Windows的实现

套接字可选项及其相关内容与操作系统无关,特别是本章的可选项,因此在Windows平台和Linux平台下并无区别。接下来介绍读取和设定的两个函数。

cpp 复制代码
#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函数。

cpp 复制代码
#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版的毫无二致。

相关推荐
_.Switch42 分钟前
高级Python自动化运维:容器安全与网络策略的深度解析
运维·网络·python·安全·自动化·devops
2401_8504108342 分钟前
文件系统和日志管理
linux·运维·服务器
qq_2546744144 分钟前
工作流初始错误 泛微提交流程提示_泛微协同办公平台E-cology8.0版本后台维护手册(11)–系统参数设置
网络
JokerSZ.1 小时前
【基于LSM的ELF文件安全模块设计】参考
运维·网络·安全
芯盾时代1 小时前
数字身份发展趋势前瞻:身份韧性与安全
运维·安全·网络安全·密码学·信息与通信
心灵彼岸-诗和远方2 小时前
DevOps业务价值流:架构设计最佳实践
运维·产品经理·devops
一只哒布刘2 小时前
NFS服务器
运维·服务器
苹果醋33 小时前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx
小松学前端3 小时前
第六章 7.0 LinkList
java·开发语言·网络