TCP/IP网络编程——套接字的多种可选项

news2024/11/19 1:27:40

完整版文章请参考:
TCP/IP网络编程完整版文章

文章目录

    • 第 9 章 套接字的多种可选项
      • 9.1 套接字可选项和 I/O 缓冲大小
        • 9.1.1 套接字多种可选项
        • 9.1.2 `getsockopt` & `setsockopt`
        • 9.1.3 `SO_SNDBUF` & `SO_RCVBUF`
      • 9.2 `SO_REUSEADDR`
        • 9.2.1 发生地址分配错误(Binding Error)
        • 9.2.2 `Time-wait` 状态
        • 9.2.3 地址再分配
      • 9.3 `TCP_NODELAY`
        • 9.3.1 `Nagle` 算法
        • 9.3.2 禁用 `Nagle` 算法

第 9 章 套接字的多种可选项

9.1 套接字可选项和 I/O 缓冲大小

我们进行套接字编程时往往只关注数据通信,而忽略了套接字具有的不同特性。但是,理解这些特性并根据实际需要进行更改也很重要

9.1.1 套接字多种可选项

我们之前写得程序都是创建好套接字之后直接使用的,此时通过默认的套接字特性进行数据通信,这里列出了一些套接字可选项。

协议层选项名读取设置
SOL_SOCKETSO_SNDBUFOO
SOL_SOCKETSO_RCVBUFOO
SOL_SOCKETSO_REUSEADDROO
SOL_SOCKETSO_KEEPALIVEOO
SOL_SOCKETSO_BROADCASTOO
SOL_SOCKETSO_DONTROUTEOO
SOL_SOCKETSO_OOBINLINEOO
SOL_SOCKETSO_ERROROX
SOL_SOCKETSO_TYPEOX
IPPROTO_IPIP_TOSOO
IPPROTO_IPIP_TTLOO
IPPROTO_IPIP_MULTICAST_TTLOO
IPPROTO_IPIP_MULTICAST_LOOPOO
IPPROTO_IPIP_MULTICAST_IFOO
IPPROTO_TCPTCP_KEEPALIVEOO
IPPROTO_TCPTCP_NODELAYOO
IPPROTO_TCPTCP_MAXSEGOO

从表中可以看出,套接字可选项是分层的。

  • IPPROTO_IP 可选项是IP协议相关事项

  • IPPROTO_TCP 层可选项是 TCP 协议的相关事项

  • SOL_SOCKET 层是套接字的通用可选项。

9.1.2 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: 向第四个参数传递的缓冲大小。调用函数后,该变量中保存通过第四个参数返回的可选项信息的字节数。
*/

下面的代码可以看出 getsockopt 的使用方法。下面示例用协议层为 SOL_SOCKET 、名为 SO_TYPE 的可选项查看套接字类型(TCP 和 UDP)

#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 tcp_sock, udp_sock;
    int sock_type;
    socklen_t optlen;
    int state;

    optlen = sizeof(sock_type);
    tcp_sock = socket(PF_INET, SOCK_STREAM, 0);
    udp_sock = socket(PF_INET, SOCK_DGRAM, 0);
    printf("SOCK_STREAM: %d \n", SOCK_STREAM);
    printf("SOCK_DGRAM: %d \n", SOCK_DGRAM);

    state = getsockopt(tcp_sock, SOL_SOCKET, SO_TYPE, (void*)&sock_type, &optlen);
    if (state)
        error_handling("getsockopt() error!");
    printf("Socket type one: %d \n", sock_type);

    state = getsockopt(udp_sock, SOL_SOCKET, SO_TYPE, (void*)&sock_type, &optlen);
    if (state)
        error_handling("getsockopt() error!");
    printf("Socket type two: %d \n", sock_type);
    return 0;
}

void error_handling(char* message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

编译运行:

在这里插入图片描述

首先创建了一个 TCP 套接字和一个 UDP 套接字。然后通过调用 getsockopt 函数来获得当前套接字的状态。

用于验证套接类型的 SO_TYPE 是只读可选项,因为套接字类型只能在创建时决定,以后不能再更改

9.1.3 SO_SNDBUF & SO_RCVBUF

创建套接字的同时会生成 I/O 缓冲。关于 I/O 缓冲,可以去看第五章。

SO_RCVBUF 是输入缓冲大小相关可选项,SO_SNDBUF 是输出缓冲大小相关可选项。用这 2 个可选项既可以读取当前 I/O 大小,也可以进行更改。通过下列示例读取创建套接字时默认的 I/O 缓冲大小。

#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);
}

编译运行:

在这里插入图片描述

输出结果和我们预想的不是很相同,缓冲大小的设置需谨慎处理,因此不会完全按照我们的要求进行

9.2 SO_REUSEADDR

9.2.1 发生地址分配错误(Binding Error)

在客户端控制台输入 Q 可以结束程序,向服务器发送 FIN 消息并经过四次握手过程。当然,输入 CTRL+C 也会向服务器传递 FIN 信息。强制终止程序时,由操作系统关闭文件套接字,此过程相当于调用 close 函数,也会向服务器发送 FIN 消息。

这样看不到是什么特殊现象,考虑以下情况:

服务器端和客户端都已经建立连接的状态下,向服务器控制台输入 CTRL+C ,强制关闭服务端

如果用这种方式终止程序,如果用同一端口号再次运行服务端,就会输出「bind() error」消息,并且无法再次运行。但是在这种情况下,再过大约 3 分钟就可以重新运行服务端。

9.2.2 Time-wait 状态

观察以下过程:

假设图中主机 A 是服务器,因为是主机 A 向 B 发送 FIN 消息,故可想象成服务器端在控制台中输入 CTRL+C 。但是问题是,套接字经过四次握手后并没有立即消除,而是要经过一段时间的 Time-wait 状态。当然,只有先断开连接的(先发送 FIN 消息的)主机才经过 Time-wait 状态。因此,若服务器端先断开连接,则无法立即重新运行。套接字处在 Time-wait 过程时,相应端口是正在使用的状态。因此,就像之前验证过的,bind 函数调用过程中会发生错误。

那到底为什么会有 Time-wait 状态呢?

在图中假设,主机 A 向主机 B 传输 ACK 消息(SEQ 5001 , ACK 7502 )后立刻消除套接字。但是最后这条 ACK 消息在传递过程中丢失,没有传递主机 B ,这时主机 B 就会试图重传。但是此时主机 A 已经是完全终止状态,因此主机 B 永远无法收到从主机 A 最后传来的 ACK 消息。基于这些问题的考虑,所以要设计 Time-wait 状态。

9.2.3 地址再分配

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);

此时,已经解决了上述问题。

9.3 TCP_NODELAY

9.3.1 Nagle 算法

为了防止因数据包过多而发生网络过载,Nagle 算法诞生了。它应用于 TCP 层。它是否使用会导致如图所示的差异:

为了防止因数据包过多而发生网络过载,Nagle 算法诞生了。它应用于 TCP 层。它是否使用会导致如图所示的差异:

图中展示了通过 Nagle 算法发送字符串 Nagle 和未使用 Nagle 算法的差别。可以得到一个结论。

只有接收到前一数据的 ACK 消息, Nagle 算法才发送下一数据。

TCP 套接字默认使用 Nagle 算法交换数据,因此最大限度的进行缓冲,直到收到 ACK 。左图也就是说一共传递 4 个数据包以传输一个字符串。从右图可以看出,发送数据包一共使用了 10 个数据包。由此可知,不使用 Nagle 算法将对网络流量产生负面影响。即使只传输一个字节的数据,其头信息都可能是几十个字节。因此,为了提高网络传输效率,必须使用 Nagle 算法。

Nagle 算法并不是什么情况下都适用,网络流量未受太大影响时,不使用 Nagle 算法要比使用它时传输速度快。最典型的就是「传输大文数据」。将文件数据传入输出缓冲不会花太多时间,因此,不使用 Nagle 算法,也会在装满输出缓冲时传输数据包。这不仅不会增加数据包的数量,反而在无需等待 ACK 的前提下连续传输,因此可以大大提高传输速度。

所以,未准确判断数据性质时不应禁用 Nagle 算法。

9.3.2 禁用 Nagle 算法

禁用 Nagle 算法应该使用:

int opt_val = 1;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void *)&opt_val, sizeof(opt_val));

通过 TCP_NODELAY 的值来查看Nagle 算法的设置状态。

opt_len = sizeof(opt_val);
getsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void *)&opt_val, &opt_len);

如果正在使用Nagle 算法,那么 opt_val 值为 0,如果禁用则为 1.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/195013.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

高效学 C++|编程实例之计算器

本节将实现一个能进行实数间加、减、乘、除运算的简易计算器。首先创建一个基于QWidget带界面的Qt项目&#xff0c;然后按照如下步骤进行操作&#xff1a; 01、计算器界面设计 在界面中拖入两个单行文本框和十七个按钮&#xff0c;按钮上显示的文字、按钮对象和单行文本框对象…

百分百拿捏offer的自动化测试面试题全套教程

最近很多咨询我&#xff0c;有没有软件测试方面的面试题&#xff0c;尤其是Python自动化测试相关的最新面试题&#xff0c;所以今天给大家整理了一份&#xff0c;希望能帮助到你们。 接口测试基础 1、公司接口测试流程是什么&#xff1f; 从开发那边获取接口设计文档、分析接口…

VUE3 指令 插槽

指令 指令是 Vue 模板语法里的特殊标记&#xff0c;在使用上和 HTML 的 data-* 属性十分相似&#xff0c;统一以 v- 开头&#xff08; e.g. v-html &#xff09;。 它以简单的方式实现了常用的 JavaScript 表达式功能&#xff0c;当表达式的值改变的时候&#xff0c;响应式地…

1x1卷积、Inception网络

目录1.1x1卷积(1x1 convolution)又称网络中的网络(network in network)池化层只能压缩图像的宽和高&#xff0c;1x1卷积能压缩通道数量&#xff0c;减少计算成本。如上图&#xff0c;输入维度的通道数为192&#xff0c;用32个1x1x192的filters&#xff0c;就能将输出的通道数压…

java基础—面试题一

文章目录1.和equals区别是什么&#xff1f;2.Java中的 <<、>>、>>> 是什么3.if-else-if-else与switch的区别4.while和do-while的区别5.switch 是否能作用在 byte 上&#xff0c;是否能作用在 long 上&#xff0c;是否能作用在String上6.&和&&…

大数据技术架构(组件)16——Hive:内置UDTF函数

1.4.11、内置UDTF函数1.4.11.1、explodeselect explode(array(100,200,300));Array<int> myCol[100,200,300][400,500,600]得到的结果如下&#xff1a;(int) myNewCol1002003004005006001.4.11.2、posexplodeselect posexplode(array(A,B,C));1.4.11.3、parse_url_tuples…

2023云原生安全值得关注的3个方向

如果说过去几年教会了我们什么的话&#xff0c;那就是云原生和开源环境中安全的重要性。 Log4j 等漏洞产生的重大影响&#xff0c;在无数的行业中浮现&#xff0c;对于云原生环境中的其他安全问题也越来越受到重视。 组织不再质疑是否要迁移到云端&#xff0c;而是在寻找最快、…

centos下安装docker 并通过docker安装gitlab

一:安装docker1、若之前安过docker&#xff0c;可以先卸载yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-selinux \docker-engine-selinux \docker-engine \docker-ce2、更新yum…

软件测试基础(四) 之 软件测试的覆盖率

一、什么是软件测试的覆盖率&#xff1f;软件测试覆盖率是软件测试技术有效性的一个度量手段&#xff0c;用来度量测试完整性。意思概括的说&#xff0c;软件测试的工作中会有非常非常多的item&#xff08;任务&#xff09;&#xff0c;执行过的任务和总任务数的一个比值&#…

尚医通 (二)项目搭建

目录一、工程结构介绍1、工程结构2、模块说明二、创建父工程1、创建sprigboot工程yygh_parent2、删除 src 目录3、配置 pom.xml4、在pom.xml中添加依赖的版本三、搭建model模块1、在父工程yygh_parent下面创建模块model2、添加项目需要的依赖3、复制项目实体类和VO类四、搭建se…

require和important区别

1.require是赋值过程&#xff0c;就是把一个值赋值给另一个&#xff0c;important是对这个值的引用 2 . require 是赋值过程并且是运行时才执行&#xff0c;也就是同步加载&#xff0c;import 是解构过程并且是编译时执行&#xff0c;理解为异步加载 3.require 的性能相对于 im…

Linux部署达梦数据库超详细教程

陈老老老板&#x1f9b8;&#x1f468;‍&#x1f4bb;本文专栏&#xff1a;国产数据库-达梦数据库&#xff08;主要讲一些达梦数据库相关的内容&#xff09;&#x1f468;‍&#x1f4bb;本文简述&#xff1a;本文讲一下达梦数据库的下载与安装教程&#xff08;Linux版&#x…

百度网盘秒传链接生成及提取方法

百度网盘秒传链接生成及提取方法 1.认识秒传链接 首先&#xff0c;我们认识一下秒传链接的格式&#xff1a; 秒传链接是由标准提取码文件名组成。例如下面的格式&#xff1a; fd00338387f50ee5919eb3df4cfce6e3#5048587008#/影视/电影/救火奶爸.mp4 百度网盘秒传链接的提取主…

FISSURE:一款功能强大的RF和逆向工程框架

关于FISSURE FISSURE是一款功能强大的RF和逆向工程框架&#xff0c;该工具适用于不同技能水平的安全研究人员&#xff0c;并提供了信号检测、信号分类、协议发现、渗透测试、IQ操作、漏洞分析、自动化和AI/机器学习等功能。该框架旨在促进软件模块、无线电、协议、信号数据、脚…

2023年怎么开通一个抖音小店?店铺开通后做什么?开店指南!

大家好&#xff0c;我是王路飞。 2023年都已经过去一个月了&#xff0c;你开通抖音小店了吗&#xff1f; 作为目前最受欢迎的创业和副业项目&#xff0c;开通抖音小店的商家数量与日俱增&#xff0c;都是为了蹭一下抖音流量的红利&#xff0c;毕竟直播带货如今正处在风口。 …

50条必背JAVA知识点(三)

31.面向对象中两个重要的概念&#xff1a;类&#xff1a;对一类事物的描述&#xff0c;是抽象的、概念上的定义对象&#xff1a;是实际存在的该类事物的每个个体&#xff0c;因而也称为实例(instance) 32.虚拟机栈&#xff0c;即为平时提到的栈结构。局部变量存储在栈结构中&a…

专利的申请和驳回

说明书和权利要求的区别 说明书里面会写这个新方案的具体内容&#xff0c;实施方案&#xff0c;解释说明等&#xff0c;权利要求书的话&#xff0c;就是对这些具体的内容进行概括 说明书应当补充说明该发明相比有技术的优势 就投屏举例: 到底怎么写 三篇 阿里 楼x投屏方法及装…

人工智能服务哪家强?IDC评估报告看过来

如果您还不知道如何选择人工智能AI服务供应商&#xff0c;那么IDC的这份评估报告也许可以派上用场。如何选择AI软件工具和平台随着科技的不断发展&#xff0c;人工智能&#xff08;Artificial Intelligence&#xff0c;简称AI&#xff09;技术在近年来取得了长足的进步。从语音…

简介JWT

简介JWT http协议无状态的&#xff0c;所以需要sessionId或token的鉴权机制&#xff0c;jwt的token认证机制不需要在服务端再保留用户的认证信息或会话信息。这就意味着基于jwt认证机制的应用程序不需要去考虑用户在哪一台服务器登录了&#xff0c;这就为应用的扩展提供了便利&…

Python函数和 lambda表达式

Python提供了许多内置函数&#xff0c;比如&#xff1a;print()&#xff0c;len()等。它还支持用户自定义函数。 一、Python函数 1、函数定义使用 &#xff08;1&#xff09;函数定义 Python 允许我们将常用的代码以固定的格式封装&#xff08;包装&#xff09;成一个独立的…