Linux网络编程杂谈(聊聊网络编程背后的故事)

数据是如何传输到物理网络上的?

以TCP为例,当 TCP 决定发送数据时,这些数据需要经过多个处理阶段才能真正被传输到物理网络。其中一个关键步骤是将数据移动到网络接口卡 (NIC)。以下是这个过程的详细描述:

  1. 数据序列化:

    • TCP 会为要发送的数据添加 TCP 头部,这创建了一个 TCP 数据段。
    • 这个 TCP 数据段再被 IP 层封装,添加 IP 头部,形成一个 IP 数据包。
    • 依据使用的物理媒体和链路层协议,如 Ethernet,此 IP 数据包再被封装,添加例如以太网的 MAC 头部和尾部。
  2. 队列化:

    • 准备好的数据包(例如以太网帧)被放置在一个队列中,等待 NIC 的传输。
    • 操作系统通常为每个 NIC 维护一个传输队列(也称为发送缓冲区或发送环)。
  3. NIC 的 DMA (Direct Memory Access):

    • 网络接口卡使用 DMA 来从系统内存中直接获取数据,而无需 CPU 的干预。
    • DMA 允许 NIC 在准备好时从发送队列中取出数据包,并将它们直接发送到网络。
  4. 物理传输:

    • 一旦 NIC 使用 DMA 获取了数据包,它就开始处理物理层的细节,如信号调制和编码。
    • 数据随后通过物理介质(例如电缆、光纤或无线电波)传输。
  5. 中断和确认:

    • 当 NIC 完成数据包的发送,它可能会产生一个中断通知 CPU 数据已被发送。
    • 这样,操作系统可以从其发送缓冲区中释放空间或进行其他必要的清理工作。
    • 对于 TCP 数据,一旦远端确认已接收到数据,TCP 将从其发送缓冲区中释放相关的数据。

这个过程的各种优化和细节可能会依据操作系统、NIC 设计和网络配置而有所不同,但上述流程为数据在大多数现代系统中的通常路径提供了一个概述。


listen()的工作原理是什么

listen() 是一个系统调用,用于使一个套接字处于监听状态,准备接受来自客户端的连接请求。它不会接受连接,只是将套接字设置为可以通过 accept() 接受连接。

参数:

  • sockfd: 是由 socket() 调用返回的文件描述符。
  • backlog: 定义了系统应该为此套接字维护的尚未由 accept() 接受的传入连接请求的最大数量。

基本工作流:

  1. 当客户端使用 connect() 调用请求连接到服务器时,连接请求被放入服务器的一个队列中。
  2. 此队列的大小由 backlog 参数指定。
  3. 当队列满时,系统开始拒绝额外的连接请求。
  4. 服务器进程可以调用 accept() 来从队列中取出一个连接请求并处理它。

函数实现:

为了说明如何实现这个函数,我们可以考虑以下简化过的伪代码。请注意,这只是一个大概的实现,并没有考虑所有的边界条件和错误处理,也没有实际的系统调用和内核交互。

// 简化的数据结构定义
typedef struct {Queue *connection_requests;  // 保存连接请求的队列int is_listening;            // 标志位,指示套接字是否正在监听
} Socket;// listen函数的简化实现
int listen(int sockfd, int backlog) {// 获取与文件描述符关联的Socket对象Socket *sock = get_socket_object_from_fd(sockfd);// 检查套接字是否已经被绑定到一个地址(通过bind())if (sock == NULL || !is_socket_bound(sock)) {return -1;  // 返回错误}// 初始化连接请求队列sock->connection_requests = create_queue(backlog);// 设置监听标志sock->is_listening = 1;return 0;  // 成功返回
}

请注意,实际的 listen() 实现会涉及到更复杂的逻辑,并且大部分工作是在操作系统内核中完成的。上述伪代码只是为了提供一个高层次的概述。在现实的操作系统中,listen() 的实现涉及到许多底层的细节、错误检查、兼容性处理以及与其他系统调用的交互。


accept()的工作原理是什么

accept() 是一个系统调用,用于从监听套接字的连接请求队列中取出第一个连接请求,并创建一个新的套接字文件描述符,以便于与发起连接的客户端通信。

参数:

  • sockfd: 是由 socket() 调用返回的文件描述符,该套接字应已经通过 bind() 绑定到一个地址并通过 listen() 开始监听。
  • addr: 是一个指针,用于存储客户端的地址信息。
  • addrlen: 是一个输入输出参数。在调用 accept() 之前,它应该被设置为 addr 所指向的缓冲区的大小。当 accept() 返回时,addrlen 将被设置为实际地址的长度。

工作流:

  1. accept() 会检查与 sockfd 关联的连接请求队列。
  2. 如果队列不为空,accept() 会取出第一个连接请求,并为其创建一个新的套接字文件描述符。
  3. 如果队列为空并且 sockfd 是非阻塞的,accept() 会立即返回错误。
  4. 如果队列为空但 sockfd 是阻塞的,accept() 会挂起调用线程,直到有一个连接请求可用为止。

伪代码实现:

这是一个非常简化的 accept() 函数实现伪代码。请注意,实际的系统调用实现会在操作系统内核中进行,并涉及许多底层细节。

typedef struct {Queue *connection_requests;  // 保存连接请求的队列int is_listening;            // 标志位,指示套接字是否正在监听
} Socket;int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {// 获取与文件描述符关联的Socket对象Socket *sock = get_socket_object_from_fd(sockfd);// 检查套接字是否处于监听状态if (!sock->is_listening) {return -1;  // 返回错误}// 如果没有连接请求,并且套接字是非阻塞的,返回错误if (queue_is_empty(sock->connection_requests) && is_socket_non_blocking(sockfd)) {return -1;  // 返回错误}// 如果没有连接请求,等待一个连接请求while (queue_is_empty(sock->connection_requests)) {wait_for_connection_request(sock);}// 从队列中取出一个连接请求ConnectionRequest *request = dequeue(sock->connection_requests);// 使用连接请求信息填充addr和addrlenif (addr != NULL && addrlen != NULL && *addrlen >= request->addr_length) {memcpy(addr, &request->client_addr, request->addr_length);*addrlen = request->addr_length;}// 为连接请求创建一个新的套接字文件描述符int new_sockfd = create_new_socket_for_request(request);return new_sockfd;  // 返回新的文件描述符
}

与先前的伪代码一样,这只是为了说明 accept() 的基本逻辑,实际的系统调用实现会涉及更复杂的逻辑、错误处理、资源管理、以及与其他系统调用和内核组件的交互。


如何理解:如果没有连接请求,并且套接字是非阻塞的,返回错误

在非阻塞模式下,系统调用(如accept())不会挂起调用线程直到请求完成。相反,它们会立即返回并可能报告一个“立即可用”的或“没有数据”的类型的错误。

对于accept()系统调用:

  • 当套接字设置为阻塞模式时:如果没有待处理的连接请求,accept()调用将阻塞,直到有连接请求到来为止。

  • 当套接字设置为非阻塞模式时:如果没有待处理的连接请求,accept()不会阻塞。它会立即返回,并通过返回值或设置某种错误状态来表示“没有可接受的连接”。

在非阻塞模式下,返回错误(通常是EAGAINEWOULDBLOCK)的原因是为了告诉调用者目前没有连接请求可接受,并允许调用者决定下一步的操作。这为设计高效的事件驱动或异步系统提供了便利,因为它们可以在没有活动发生时执行其他任务,而不是被系统调用挂起。

非阻塞的accept()是事件驱动编程模型(如select()poll()epoll()等)中的常见用法,这种模型可以在单一线程或进程中高效地处理大量并发连接。

如何将套接字设置为非阻塞模式

要将套接字设置为非阻塞模式,通常需要使用fcntl函数修改套接字的文件描述符标志。以下是如何为套接字设置非阻塞模式的示例:

#include <fcntl.h>
#include <stdio.h>
#include <sys/socket.h>int set_nonblocking(int sockfd) {int flags;// 获取当前文件描述符的标志flags = fcntl(sockfd, F_GETFL, 0);if (flags == -1) {perror("fcntl");return -1;}// 添加O_NONBLOCK标志flags |= O_NONBLOCK;// 使用修改后的标志更新文件描述符if (fcntl(sockfd, F_SETFL, flags) == -1) {perror("fcntl");return -1;}return 0;
}

使用上述set_nonblocking函数,可以为任何套接字设置非阻塞模式,例如:

int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {perror("socket");exit(1);
}if (set_nonblocking(sockfd) == -1) {// 处理错误exit(1);
}

一旦设置为非阻塞模式,涉及此套接字的系统调用(如accept(), read(), write()等)都不会阻塞,而是在无法立即完成请求时立即返回。


从客户端的角度来看呢?

当一个客户端尝试连接到监听套接字上时,内核将会为该连接请求创建一个与之相关的数据结构(例如,一个表示连接的数据结构)。然后,该连接请求会被加入到与监听套接字相关联的连接请求队列中。具体的机制和时机取决于底层的网络实现和操作系统。

在典型的TCP实现中,当一个SYN分段(表示开始一个新的连接的TCP分段)到达服务器时,以下步骤会发生:

  1. SYN分段的接收:当服务器接收到来自客户端的SYN分段时,它意味着客户端希望建立一个新的连接。

  2. 半连接队列:在某些实现中,刚刚到达的连接首先会被放置在一个所谓的"半连接队列"中。此时,连接尚未完全建立(它仍然处于三次握手的中间阶段)。

  3. SYN-ACK的发送:服务器会响应一个SYN-ACK分段,表示它已经接收到连接请求并且愿意建立连接。

  4. 完成队列:当客户端回应一个ACK分段,三次握手就完成了,此时连接从"半连接队列"移动到"完成队列"中。

  5. 从队列中取出连接:当服务器上的应用程序调用accept()函数时,它实际上是从这个"完成队列"中取出一个已完成的连接。如果队列为空(即没有等待的连接),accept()的行为取决于套接字是否为非阻塞:如果是阻塞模式,它会挂起等待直到有一个连接可用,而如果是非阻塞模式,它会立即返回一个错误。

对于上面accept()伪代码实现中的dequeue(sock->connection_requests),可以将其视为一个从"完成队列"中取出一个已完成连接的抽象表示。而连接请求是在三次握手完成时被加入到这个队列中的。当然,这只是一个简化的描述,实际的TCP和套接字实现可能会有更多的细节和考虑因素。

什么是半连接队列

当我们谈到TCP连接时,实际上涉及了很多资源。为每个连接分配的资源可能包括:

  • 套接字结构和与之关联的缓冲区。
  • 对于传入和传出数据的内存分配。
  • 与连接状态、计时器、重传等相关的控制结构。

在正常的三次握手过程中,当服务器收到一个SYN分段(第一步)时,它会响应一个SYN-ACK分段(第二步)并等待客户端的最后一个ACK分段(第三步)。正是在这个等待期间,半连接队列发挥了其关键作用。

为了理解如何节约资源,让我们深入探讨半连接队列的工作原理:

  1. 限制记录大小:当一个SYN请求到达服务器时,服务器不会立即为这个连接分配所有必要的资源。相反,它只是在半连接队列中为该连接存储一个简化的记录。这个记录通常仅包含必要的信息,例如源IP、源端口和其他一些用于标识这个连接请求的信息。这个记录的大小远小于一个完整的套接字结构,因此在内存使用上更为高效。

  2. 有界队列:半连接队列的大小是有限的。当它满了以后,新到达的SYN请求可能会被丢弃。这自然地为系统提供了一个保护机制,使其不会因为大量的SYN请求而耗尽资源。

  3. 超时机制:为了防止由于恶意SYN请求或网络问题导致的记录堆积,半连接队列中的每个记录都有一个超时值。如果在超时时间内没有收到客户端的ACK响应,该记录将被从队列中删除。这确保了即使在SYN洪水攻击的情况下,旧的、未完成的连接请求也会被清理出队列。

  4. 动态调整:在一些现代操作系统中,根据当前的网络条件和系统负载,半连接队列的大小和行为可以动态调整。

通过这些方式,半连接队列为系统提供了一个防火墙,保护系统免受大量SYN请求的侵害,并确保只有真正想要建立连接的客户端可以进入系统。这不仅限制了资源使用,还为有效连接提供了更好的服务质量。

半连接队列的优点

半连接队列(也被称为"SYN队列")的优点:

  1. 处理连接洪水攻击:在所谓的SYN洪水攻击中,攻击者快速地发送大量的SYN分段(连接请求)到目标服务器,但从不完成三次握手。这导致服务器为每一个到达的SYN请求分配资源,等待来自客户端的响应,从而可能耗尽系统资源。半连接队列限制了这种资源分配,因为在三次握手完成之前,连接不会被完全建立。

  2. 提高效率:当服务器接收到SYN分段时,它并不立即为该连接分配所有必要的资源(例如,完整的套接字数据结构或相关的内存缓冲区)。相反,它只是在半连接队列中存储一个简化的连接记录。只有当连接确实建立(即三次握手完成)时,才会为其分配完整的资源。

  3. 异步处理:在高并发的网络环境中,服务器可能会同时收到大量的SYN请求。半连接队列允许服务器以异步的方式处理这些请求,先对它们进行排队,然后再逐一处理。

  4. 避免不必要的资源分配:并不是所有的SYN请求都会完成三次握手。有些可能是由于网络中断、客户端崩溃或其他原因而永远不会完成。通过使用半连接队列,服务器可以避免为这些不会完成的连接分配不必要的资源。

在实际的实现中,半连接队列的大小是有限的。当队列满时,新到达的连接请求可能会被丢弃,直到有足够的空间为止。这也是为什么在高并发场景下,服务器可能需要对半连接队列的大小进行调整,以应对大量的并发连接请求。

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

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

相关文章

10.17七段数码管单个多个(部分)

单个数码管的实现 第一种方式 一端并接称为位码&#xff1b;一端分别接收电平信号以控制灯的亮灭&#xff0c;称为段码 8421BCD码转七段数码管段码是将BCD码表示的十进制数转换成七段LED数码管的7个驱动段码&#xff0c; 段码就是LED灯的信号 a为1表示没用到a&#xff0c;a为…

文件读取结束的判定

大家好啊&#xff0c;我们今天来补充文件操作的读取结束的判定。 被错误使用的feof 牢记&#xff1a;在文件读取过程中&#xff0c;不能用feof函数的返回值直接用来判断文件的是否结束而是应用于当文件读取结束的时候&#xff0c;判断是读取失败结束&#xff0c;还是遇到文件尾…

快速排序(sort用法)

在头文件#include<algorithm>中 for (int i 0; i < m; i)cin >> arr[i];sort(arr, arr m);for (int i 0; i < m; i)cout << arr[i]; sort()函数可以对给定区间所有元素进行排序。 它有三个参数sort(begin, end, cmp)&#xff0c; 其中begin为指向…

Qt第六十五章:自定义菜单栏的隐藏、弹出

目录 一、效果图 二、qtDesigner 三、ui文件如下&#xff1a; 四、代码 一、效果图 二、qtDesigner 原理是利用属性动画来控制QFrame的minimumWidth属性。 ①先拖出相应的控件 ②布局一下 ③填上一些样式 相关QSS background-color: rgb(238, 242, 255); border:2px sol…

量子力学期末复习--1

量子力学解题技巧--1 基础知识 薛定谔方程 Ehrenfest 定理 不确定性原理&#xff1a;正则对易关系&#xff1a;自由粒子&#xff1a;对于自由粒子&#xff0c;分离变量解不代表物理上可实现的态。但其含时薛定谔方程的一般解仍旧是分离变量解的线性组合 典型题目 自由粒子…

Ajax 笔记/练习

Ajax 异步JavaScript和XML 作用 实现 HTML 在不整体刷新的情况下&#xff0c;通过后台服务器&#xff0c;请求数据并局部更新页面内容 操作流程 Ajax 使用 XMLHttpRequest 通过new 关键字可以创建XMLHttpRequest() 对象。 var req new XMLHttpRequest();方法和属性说明req.…

Rclone连接Onedrive

一、Rclone介绍 Rclone是一款的命令行工具&#xff0c;支持在不同对象存储、网盘间同步、上传、下载数据。 我们这里连接的onedrive&#xff0c;其他网盘请查看官方文档。 注意&#xff1a; 需要先在Windows下配置好了&#xff0c;然后再将rclone配置文件复制到Linux的rclone配…

Go语言中:list := make([]Item, 0) var list []Item 这两种写法有什么区别?

list : make([]Item, 0) 和 var list []Item 都是用于创建一个切片&#xff08;slice&#xff09;的语法&#xff0c;但它们在 Go 中有一些区别。 list : make([]Item, 0)&#xff1a; 这是一种使用 make 函数来创建切片的方式&#xff0c;其中 Item 是切片的元素类型。make 函…

Elasticsearch:painless script 语法基础和实战

摘要&#xff1a;Elasticsearch&#xff0c;Java script的作用 script是Elasticsearch的拓展功能&#xff0c;通过定制的表达式实现已经预设好的API无法完成的个性化需求&#xff0c;比如完成以下操作 字段再加工/统计输出字段之间逻辑运算定义查询得分的计算公式定义特殊过…

cnn 直线检测笔记

目录 mlsd直线检测: ULSD-ISPRS曲线检测 划线标注工具: 可视化标注代码: mlsd直线检测: mlsd_pytorch

JavaScript中 判断网络状态的几种方法

1. 使用 Navigator onLine 属性 Navigator onLine 属性判断浏览器是否在线&#xff0c;在线返回 true&#xff0c;离线返回 false&#xff1b; Navigator onLine 是只读属性&#xff0c;所有主流浏览器都支持 onLine 属性&#xff1b; if (window.navigator.onLine) {console…

【proteus】8086仿真、汇编语言

1.创建好新项目 2.点击source code 弹出VSM 3. 4.注意两个都不勾选 可以看到schematic有原理图出现 5. 再次点击source code 6.project/project settings&#xff0c;取消勾选embed 7. add 8.输入文件名保存后&#xff1a; 注意&#xff1a;proteus不用写dos的相关语句 。

【NPM】particles.vue3 + tsparticles 实现粒子效果

在 NPM 官网搜索这两个库并安装&#xff1a; npm install element-plus --save npm i tsparticles使用提供的 vue 案例和方法&#xff1a; <template><div><vue-particlesid"tsparticles":particlesInit"particlesInit":particlesLoaded&…

Kubernetes 学习总结(39)—— Kubernetes 之 Pause 容器详解

一、概念和作用 在 Kubernetes 中&#xff0c;Pause 容器是一种特殊类型的容器&#xff0c;它的主要作用是充当依赖其他容器的容器&#xff0c;为其他容器提供一个可靠的、隔离的运行环境。 Pause 容器是一种轻量级的容器&#xff0c;它本身不包含任何业务逻辑&#xff0c;只是…

华为OD 磁盘容量排序(100分)【java】A卷+B卷

华为OD统一考试A卷+B卷 新题库说明 你收到的链接上面会标注A卷还是B卷。目前大部分收到的都是B卷。 B卷对应20022部分考题以及新出的题目,A卷对应的是新出的题目。 我将持续更新最新题目 获取更多免费题目可前往夸克网盘下载,请点击以下链接进入: 我用夸克网盘分享了「华为O…

自然语言处理---Transformer机制详解之BERT模型特点

1 BERT的优点和缺点 1.1 BERT的优点 通过预训练, 加上Fine-tunning, 在11项NLP任务上取得最优结果.BERT的根基源于Transformer, 相比传统RNN更加高效, 可以并行化处理同时能捕捉长距离的语义和结构依赖.BERT采用了Transformer架构中的Encoder模块, 不仅仅获得了真正意义上的b…

华为OD机试 - 代表团坐车 - 动态规划(Java 2023 B卷 200分)

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路五、Java算法源码六、效果展示1、输入2、输出3、说明 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&#…

Ubuntu 22.04 中安装 fcitx5

Ubuntu 22.04 中安装 fcitx5 可以按照以下步骤进行&#xff1a; 添加 fcitx5 的 PPA 首先&#xff0c;添加 fcitx5 的官方 PPA&#xff1a; sudo add-apt-repository ppa:fcitx-team/fcitx5更新软件包列表 sudo apt update安装 fcitx5 sudo apt install fcitx5 fcitx5-conf…

信息学奥赛一本通1000:入门测试题目

1000&#xff1a;入门测试题目 时间限制: 1000 ms 内存限制: 32768 KB 提交数: 318392 通过数: 190210 【题目描述】 求两个整数的和。 【输入】 一行&#xff0c;两个用空格隔开的整数。 【输出】 两个整数的和。 【输入样例】 2 3 【输出样例】 5 思路&#…

Mysql表结构差异比较

1、背景 我们在开发过程中&#xff0c;大部分情况下都是好几个版本一起并行&#xff0c;有时候如果某个版本表结构改动较大&#xff0c;但是忘记了记录DDL脚本&#xff0c;这个时候需要人工去把新增或修改的DDL脚本整理出来&#xff08;主要是为了解决 数据库新增字段&#xff…