Linux网络编程杂谈(聊聊网络编程背后的故事)
数据是如何传输到物理网络上的?
以TCP为例,当 TCP 决定发送数据时,这些数据需要经过多个处理阶段才能真正被传输到物理网络。其中一个关键步骤是将数据移动到网络接口卡 (NIC)。以下是这个过程的详细描述:
-
数据序列化:
- TCP 会为要发送的数据添加 TCP 头部,这创建了一个 TCP 数据段。
- 这个 TCP 数据段再被 IP 层封装,添加 IP 头部,形成一个 IP 数据包。
- 依据使用的物理媒体和链路层协议,如 Ethernet,此 IP 数据包再被封装,添加例如以太网的 MAC 头部和尾部。
-
队列化:
- 准备好的数据包(例如以太网帧)被放置在一个队列中,等待 NIC 的传输。
- 操作系统通常为每个 NIC 维护一个传输队列(也称为发送缓冲区或发送环)。
-
NIC 的 DMA (Direct Memory Access):
- 网络接口卡使用 DMA 来从系统内存中直接获取数据,而无需 CPU 的干预。
- DMA 允许 NIC 在准备好时从发送队列中取出数据包,并将它们直接发送到网络。
-
物理传输:
- 一旦 NIC 使用 DMA 获取了数据包,它就开始处理物理层的细节,如信号调制和编码。
- 数据随后通过物理介质(例如电缆、光纤或无线电波)传输。
-
中断和确认:
- 当 NIC 完成数据包的发送,它可能会产生一个中断通知 CPU 数据已被发送。
- 这样,操作系统可以从其发送缓冲区中释放空间或进行其他必要的清理工作。
- 对于 TCP 数据,一旦远端确认已接收到数据,TCP 将从其发送缓冲区中释放相关的数据。
这个过程的各种优化和细节可能会依据操作系统、NIC 设计和网络配置而有所不同,但上述流程为数据在大多数现代系统中的通常路径提供了一个概述。
listen()的工作原理是什么
listen()
是一个系统调用,用于使一个套接字处于监听状态,准备接受来自客户端的连接请求。它不会接受连接,只是将套接字设置为可以通过 accept()
接受连接。
参数:
sockfd
: 是由socket()
调用返回的文件描述符。backlog
: 定义了系统应该为此套接字维护的尚未由accept()
接受的传入连接请求的最大数量。
基本工作流:
- 当客户端使用
connect()
调用请求连接到服务器时,连接请求被放入服务器的一个队列中。 - 此队列的大小由
backlog
参数指定。 - 当队列满时,系统开始拒绝额外的连接请求。
- 服务器进程可以调用
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
将被设置为实际地址的长度。
工作流:
accept()
会检查与sockfd
关联的连接请求队列。- 如果队列不为空,
accept()
会取出第一个连接请求,并为其创建一个新的套接字文件描述符。 - 如果队列为空并且
sockfd
是非阻塞的,accept()
会立即返回错误。 - 如果队列为空但
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()
不会阻塞。它会立即返回,并通过返回值或设置某种错误状态来表示“没有可接受的连接”。
在非阻塞模式下,返回错误(通常是EAGAIN
或EWOULDBLOCK
)的原因是为了告诉调用者目前没有连接请求可接受,并允许调用者决定下一步的操作。这为设计高效的事件驱动或异步系统提供了便利,因为它们可以在没有活动发生时执行其他任务,而不是被系统调用挂起。
非阻塞的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分段)到达服务器时,以下步骤会发生:
-
SYN分段的接收:当服务器接收到来自客户端的SYN分段时,它意味着客户端希望建立一个新的连接。
-
半连接队列:在某些实现中,刚刚到达的连接首先会被放置在一个所谓的"半连接队列"中。此时,连接尚未完全建立(它仍然处于三次握手的中间阶段)。
-
SYN-ACK的发送:服务器会响应一个SYN-ACK分段,表示它已经接收到连接请求并且愿意建立连接。
-
完成队列:当客户端回应一个ACK分段,三次握手就完成了,此时连接从"半连接队列"移动到"完成队列"中。
-
从队列中取出连接:当服务器上的应用程序调用
accept()
函数时,它实际上是从这个"完成队列"中取出一个已完成的连接。如果队列为空(即没有等待的连接),accept()
的行为取决于套接字是否为非阻塞:如果是阻塞模式,它会挂起等待直到有一个连接可用,而如果是非阻塞模式,它会立即返回一个错误。
对于上面accept()伪代码实现中的dequeue(sock->connection_requests)
,可以将其视为一个从"完成队列"中取出一个已完成连接的抽象表示。而连接请求是在三次握手完成时被加入到这个队列中的。当然,这只是一个简化的描述,实际的TCP和套接字实现可能会有更多的细节和考虑因素。
什么是半连接队列
当我们谈到TCP连接时,实际上涉及了很多资源。为每个连接分配的资源可能包括:
- 套接字结构和与之关联的缓冲区。
- 对于传入和传出数据的内存分配。
- 与连接状态、计时器、重传等相关的控制结构。
在正常的三次握手过程中,当服务器收到一个SYN分段(第一步)时,它会响应一个SYN-ACK分段(第二步)并等待客户端的最后一个ACK分段(第三步)。正是在这个等待期间,半连接队列发挥了其关键作用。
为了理解如何节约资源,让我们深入探讨半连接队列的工作原理:
-
限制记录大小:当一个SYN请求到达服务器时,服务器不会立即为这个连接分配所有必要的资源。相反,它只是在半连接队列中为该连接存储一个简化的记录。这个记录通常仅包含必要的信息,例如源IP、源端口和其他一些用于标识这个连接请求的信息。这个记录的大小远小于一个完整的套接字结构,因此在内存使用上更为高效。
-
有界队列:半连接队列的大小是有限的。当它满了以后,新到达的SYN请求可能会被丢弃。这自然地为系统提供了一个保护机制,使其不会因为大量的SYN请求而耗尽资源。
-
超时机制:为了防止由于恶意SYN请求或网络问题导致的记录堆积,半连接队列中的每个记录都有一个超时值。如果在超时时间内没有收到客户端的ACK响应,该记录将被从队列中删除。这确保了即使在SYN洪水攻击的情况下,旧的、未完成的连接请求也会被清理出队列。
-
动态调整:在一些现代操作系统中,根据当前的网络条件和系统负载,半连接队列的大小和行为可以动态调整。
通过这些方式,半连接队列为系统提供了一个防火墙,保护系统免受大量SYN请求的侵害,并确保只有真正想要建立连接的客户端可以进入系统。这不仅限制了资源使用,还为有效连接提供了更好的服务质量。
半连接队列的优点
半连接队列(也被称为"SYN队列")的优点:
-
处理连接洪水攻击:在所谓的SYN洪水攻击中,攻击者快速地发送大量的SYN分段(连接请求)到目标服务器,但从不完成三次握手。这导致服务器为每一个到达的SYN请求分配资源,等待来自客户端的响应,从而可能耗尽系统资源。半连接队列限制了这种资源分配,因为在三次握手完成之前,连接不会被完全建立。
-
提高效率:当服务器接收到SYN分段时,它并不立即为该连接分配所有必要的资源(例如,完整的套接字数据结构或相关的内存缓冲区)。相反,它只是在半连接队列中存储一个简化的连接记录。只有当连接确实建立(即三次握手完成)时,才会为其分配完整的资源。
-
异步处理:在高并发的网络环境中,服务器可能会同时收到大量的SYN请求。半连接队列允许服务器以异步的方式处理这些请求,先对它们进行排队,然后再逐一处理。
-
避免不必要的资源分配:并不是所有的SYN请求都会完成三次握手。有些可能是由于网络中断、客户端崩溃或其他原因而永远不会完成。通过使用半连接队列,服务器可以避免为这些不会完成的连接分配不必要的资源。
在实际的实现中,半连接队列的大小是有限的。当队列满时,新到达的连接请求可能会被丢弃,直到有足够的空间为止。这也是为什么在高并发场景下,服务器可能需要对半连接队列的大小进行调整,以应对大量的并发连接请求。
相关文章:
Linux网络编程杂谈(聊聊网络编程背后的故事)
数据是如何传输到物理网络上的? 以TCP为例,当 TCP 决定发送数据时,这些数据需要经过多个处理阶段才能真正被传输到物理网络。其中一个关键步骤是将数据移动到网络接口卡 (NIC)。以下是这个过程的详细描述: 数据序列化: TCP 会为要…...
执行Maven项目时,无法解析项目的依赖关系
报错[ERROR] Failed to execute goal on project pdms-services: Could not resolve dependencies for project ..... 在IDEA ----> setting ---->Remote Jar Repositories ----> Maven jar repositories中添加远程仓库的http地址。 再次进行maven的clean和install就好…...
索引有哪些缺点以及具体有哪些索引类型
索引的优缺点 优点: 合理的增加索引,可以提高数据查询的效率,减少查询时间 有一些特殊的索引,可以保证数据的完整性,比如唯一索引 缺点: 创建索引和维护索引需要消耗时间 索引需要额外占用物理空间 对创建…...

前端学成在线项目详细解析二
12-banner区域-课程表布局 HTML布局 <div class"right"><h3>我的课程表</h3><div class"content">1</div> </div> CSS样式 /* 课程表 */ .banner .right {margin-top: 60px;width: 218px;height: 305px;background-…...
Linux 网卡性能优化设置
在高速网络传输中,每秒传输的数据量非常大。网络设备设置有一种缓存机制,即“缓存区”,在 Linux 系统中,网卡缓冲分为两种类型:软件缓冲区和硬件缓冲区。 要提高网络吞吐率,首先当然是升级linux kernel。其…...
华为OD 最大嵌套括号深度(100分)【java】B卷
华为OD统一考试A卷+B卷 新题库说明 你收到的链接上面会标注A卷还是B卷。目前大部分收到的都是B卷。 B卷对应20022部分考题以及新出的题目,A卷对应的是新出的题目。 我将持续更新最新题目 获取更多免费题目可前往夸克网盘下载,请点击以下链接进入: 我用夸克网盘分享了「华为O…...

“微信小程序登录与用户信息获取详解“
目录 引言微信小程序微信登录介绍1. 微信登录的基本概念2. 微信小程序中的微信登录 微信小程序登录的wxLogin与getUserProfile的区别1. wx.login()2. wx.getUserProfile()3.两者区别 微信小程序登录的理论概念1. 微信登录流程2. 用户授权与登录态维护 微信小程序登录的代码演示…...
软考-防火墙技术与原理
本文为作者学习文章,按作者习惯写成,如有错误或需要追加内容请留言(不喜勿喷) 本文为追加文章,后期慢慢追加 by 2023年10月 防火墙概念 根据网络的安全信任程度和需要保护的对象,人为地划分若干安全区域…...
MOS管型号
MOS 管型号 N型 型号类型电压电流Rds封装资料AP60N03DFN30V60A45mΩPDFN3x3-8L手册 P型 型号类型电压电流Rds封装资料AO4447AP30V-18.5A8.2mΩSOIC-8手册 NP型 型号类型电压电流Rds封装资料NP4606PN30V7A、-6A45mΩSOP8手册KS3640MBPN30V20A、-22A45mΩPDFN3333手册NCE30…...
龙测票选,5本最受欢迎的软件测试书籍
随着技术的发展,软件测试所涉猎的领域越来越广泛,包括测试理论、方法、管理、工具等,一直在随之变化。对新手来说,这时候需要有一个引路明灯,避免走弯路,提高学习效率。而书籍就扮演着这样的角色。一本好的…...

C#中各种循环遍历的功能与应用
在C#编程中,循环遍历是一种重要的技巧,它使我们能够有效地处理集合、数组和其他数据结构。本文将深入探讨C#中常见的循环遍历方式,包括for循环、foreach循环、while循环和do while循环,并给出它们在实际应用中的使用场景、示例和最…...

【必看技巧】Access开发者必备:如何用代码隐藏功能区、导航区、状态栏?
hi,大家好呀! 今天想着给大家分享点啥呢?最近几个月断更的有些“勤快”了,那就给大家分享个几行代码。 当我们在access中开发完成后,为了让我们的系统更加的像一个系统,我们会把access的功能区࿰…...

领先一步,效率翻倍:PieCloudDB Database 预聚集特性让查询速度飞起来!
在大数据时代,如何有效地管理和处理海量数据成为了企业面临的核心挑战。为此,拓数派推出了首款数据计算引擎 PieCloudDB Database,作为一款全新的云原生虚拟数仓,旨在提供更高效、更灵活的数据处理解决方案。 PieCloudDB 的设计理…...

系统性认知网络安全
前言:本文旨在介绍网络安全相关基础知识体系和框架 目录 一.信息安全概述 信息安全研究内容及关系 信息安全的基本要求 保密性Confidentiality: 完整性Integrity: 可用性Availability: 二.信息安全的发展 20世纪60年代&…...

MySQL查看数据库、表容量大小
1. 查看所有数据库容量大小 selecttable_schema as 数据库,sum(table_rows) as 记录数,sum(truncate(data_length/1024/1024, 2)) as 数据容量(MB),sum(truncate(index_length/1024/1024, 2)) as 索引容量(MB)from information_schema.tablesgroup by table_schemaorder by su…...

什么是全链路压测?
随着互联网技术的发展和普及,越来越多的互联网公司开始重视性能压测,并将其纳入软件开发和测试的流程中。 阿里巴巴在2014 年双11 大促活动保障背景下提出了全链路压测技术,能更好的保障系统可用性和稳定性。 什么是全链路压测?…...
EGL函数翻译--eglChooseConfig
EGL函数翻译–eglChooseConfig 函数名 EGLBoolean eglChooseConfig( EGLDisplay display,EGLint const* attrib_list,EGLConfig* configs,EGLint config_size,EGLint* num_config);参数描述 参数display: EGLDisplay的显示连接。 参数attrib_list: 指定"frame Buffer(帧…...

详细介绍如何使用Ipopt非线性求解器求解带约束的最优化问题
本文中将详细介绍如何使用Ipopt非线性求解器求解带约束的最优化问题,结合给出的带约束的最优化问题示例,给出相应的完整的C程序,并给出详细的解释和注释,以及编译规则等 一、Ipopt库的安装和测试 本部分内容在之前的文章《Ubuntu2…...

跳跃游戏Ⅱ-----题解报告
题目:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台 与Ⅰ不同的是,这次要求找出最小的跳跃次数。思路也很简单,在每一次跳跃之后都更新最远的跳跃距离。 举个列子: 输入:2,3,1,1,4 第一次…...

JVM 基础篇:类加载器
一.了解JVM 1.1什么是JVM JVM是Java Virtual Machine(Java虚拟机)的缩写,是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟计算机功能来实现的,JVM屏蔽了与具体操作系统平台相关的信息,Java程序只需…...
Java 语言特性(面试系列2)
一、SQL 基础 1. 复杂查询 (1)连接查询(JOIN) 内连接(INNER JOIN):返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...

地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...

C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...

【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
质量体系的重要
质量体系是为确保产品、服务或过程质量满足规定要求,由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面: 🏛️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限,形成层级清晰的管理网络…...

cf2117E
原题链接:https://codeforces.com/contest/2117/problem/E 题目背景: 给定两个数组a,b,可以执行多次以下操作:选择 i (1 < i < n - 1),并设置 或,也可以在执行上述操作前执行一次删除任意 和 。求…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...

Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
4. TypeScript 类型推断与类型组合
一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式,自动确定它们的类型。 这一特性减少了显式类型注解的需要,在保持类型安全的同时简化了代码。通过分析上下文和初始值,TypeSc…...