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

news/2024/10/23 7:20:14/

套接字的多种可选项

下列是针对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,失败时返回-1sock    //用于查看选项套接字文件描述符level   //查看可选项的协议层optname //要查看的可选项名optval  //保存查看结果的缓冲地址值optlen  //该变量中保存通过第四个参数返回的可选信息的字节数

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

#include<sys/socket.h>
int setsockopt(int sock,int level,int optname,const void*optval,socklen_t optlen);
//成功时返回0,失败时返回-1sock    //用于更改可选项的套接字文件描述符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版的毫无二致。


http://www.ppmy.cn/news/889236.html

相关文章

读书笔记--数据治理之器

继延续上一篇文章&#xff0c;对数据治理之器进行学习思考。所谓的工欲善其事&#xff0c;必先利其器&#xff0c;数据治理工具是企业数据治理体系落地的重要保证。本部分内容主要讲述了数据治理的7把利剑&#xff0c;包括数据模型管理工具、元数据管理工具、数据标准管理工具、…

Java杂谈——求所有的4位吸血鬼数字

Java杂谈篇(一) 【求所有的4位吸血鬼数字】1、什么是吸血鬼数字?2、方法思考2.1、各种方法2.2、关于上述第二个解决方法的解析2.2.1、方法详情2.2.2、方法详解3、运行结果【求所有的4位吸血鬼数字】 1、什么是吸血鬼数字? 吸血鬼数字是指位数为偶数的数字,可以由一对数字…

Java求吸血鬼数

在看《Thinking In Java》,有一题是求4位数的吸血鬼数&#xff0c;这里做个笔记。 首先解释一下吸血鬼数字&#xff1a;吸血鬼数字是指位数为偶数的数字&#xff0c;可由一对数字相乘而得到&#xff0c;这对数字各包含乘积的一半位数的数字&#xff0c;以两个0结尾的数字是不允…

java--吸血鬼数的判断

首先解释一下吸血鬼数字&#xff1a;吸血鬼数字是指位数为偶数的数字&#xff0c;可由一对数字相乘而得到&#xff0c;这对数字各包含乘积的一半位数的数字&#xff0c;以两个0结尾的数字是不允许的。 四位数吸血鬼数字示例&#xff1a;126021*60&#xff0c;182721*87…

ThinkingInJava_吸血鬼数

package java_001;import java.util.Arrays;/*** Thinking in Java*吸血鬼数字是指位数为偶数的数字&#xff0c;可由一对数字相乘而得到&#xff0c;*这对数字各包含乘积的一半位数的数字&#xff0c;*其中从最初数字选取的数字可以任意排序*以两个0结尾的数字是不允许的。*四…

java编程找出吸血鬼数字,Java 找到四位数的所有吸血鬼数字 基础代码实例

Java 找出四位数的所有吸血鬼数字 基础代码实例 /** * 找出四位数的所有吸血鬼数字 * 吸血鬼数字是指位数为偶数的数字&#xff0c;可以由一对数字相乘而得到&#xff0c;而这对数字各包含乘积的一半位数的数字&#xff0c;其中从最初的数字中选取的数字可以任意排序. * 以两个…

Thinking in Java之吸血鬼数字

今天学习了Java的流程控制的内容&#xff0c;基本与C语言一样&#xff0c;只不过是Java多了foreach语句的一种用法&#xff0c;相对于C语言来说方便了很多。看了最后一道习题&#xff0c;是关于吸血鬼数字的&#xff0c;比较感兴趣于是就写了代码。但是我这个属于暴力求解&…

java吸血鬼数字_[求助]吸血鬼数字

[求助]吸血鬼数字 public class xixuegui { public static void main (String[] args) { int i,j,k,a,b,c,d,n,m; for(i1001;i<9999;i){ ai/1000; b(i-a*1000)/100; c(i-a*1000-b*100)/10; di-a*1000-b*100-c*10; for(j1;j<10;j){ for(k0;k<10;k){ if(i(j*1000)(k*100…