透视Linux内核:深度剖析Socket机制的本质
在Linux操作系统构建的网络世界里,Socket 宛如纵横交错的交通枢纽,承担着不同应用程序间数据往来的重任。无论是日常浏览网页时,浏览器与 Web 服务器间信息的快速交互;还是畅玩网络游戏过程中,玩家操作指令与游戏服务器状态数据的频繁传输,Socket 都在幕后默默地发挥着关键作用。然而,多数开发者在使用 Socket 进行编程时,往往停留在应用层接口的调用层面,对其在内核中的本质运作机制知之甚少。你是否好奇,当你在代码中轻松调用send、recv函数实现数据收发时,Linux 内核内部究竟发生了什么?那些看似简单的 Socket 连接建立与关闭过程,又涉及到内核哪些复杂的数据结构和精妙算法?
从 Linux 内核的发展历程来看,Socket 机制并非一蹴而就。早期的 Linux 系统,网络功能相对简单,Socket 的设计也较为基础。但随着互联网的蓬勃发展,网络应用场景日益复杂多样,对 Socket 性能、稳定性和兼容性的要求呈指数级增长。这促使 Linux 内核开发者不断对 Socket 机制进行优化与完善,从数据结构的精心设计,到协议栈交互逻辑的反复打磨,每一次改进都让 Socket 能更好地适应复杂多变的网络环境。如今,Socket 已成为 Linux 内核网络子系统中最为核心且复杂的部分之一,其高效稳定的运行支撑着整个 Linux 网络生态的繁荣。
接下来,让我们一同深入 Linux 内核的神秘世界,层层剥开 Socket 机制的 “外衣”,从其底层数据结构、工作流程,到与网络协议栈的协同运作,全方位、深层次地剖析 Socket 机制的本质,探寻那些隐藏在内核深处,却又深刻影响着网络通信效率与可靠性的关键技术 。
一、Socket套接字概述
在网络编程的广袤世界里,Socket(套接字)是一个极为重要的概念。简单来说,Socket 是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象 。从本质上讲,它是应用层与 TCP/IP 协议族通信的中间软件抽象层,是一组接口,把复杂的 TCP/IP 协议族隐藏在 Socket 接口后面,让开发者只需面对一组简单的接口,就能实现网络通信。就如同我们日常使用电话,无需了解电话线路复杂的电路原理和信号传输机制,只要拿起听筒拨号,就能与远方的人通话,Socket 就是这样一个方便我们进行网络通信的 “听筒”。
Socket 在进程间通信(IPC,Inter - Process Communication)和网络通信中起着关键作用。在本地进程间通信中,我们有管道(PIPE)、命名管道(FIFO)、消息队列、信号量、共享内存等方式。但当涉及到网络中的进程通信时,Socket 就成为了首选工具。网络中的不同主机,其进程的 PID(进程标识符)在本地虽能唯一标识进程,但在网络环境下,PID 冲突几率很大。而 Socket 利用 IP 地址 + 协议 + 端口号的组合,能够唯一标识网络中的一个进程,从而巧妙地解决了网络进程间通信的难题。
比如,我们日常使用的 Web 浏览器,当在浏览器地址栏输入网址并回车后,浏览器进程就会通过 Socket 向对应的 Web 服务器进程发起连接请求,服务器响应后,双方通过 Socket 进行数据传输,这样我们就能看到网页内容了。再如,即时通讯软件如 QQ、微信,通过 Socket 实现客户端之间或客户端与服务器之间的即时消息传输;网络游戏中,客户端通过 Socket 连接到游戏服务器,实现实时的游戏状态同步和玩家互动。Socket 就像一座无形的桥梁,跨越网络的边界,让不同主机上的进程能够顺畅地交流。
二、Socket在Linux内核中的地位
2.1Socket与网络协议栈的关系
Socket 在 Linux 内核中处于应用层与 TCP/IP 协议栈之间,起着承上启下的关键作用 。从网络协议栈的角度来看,TCP/IP 协议栈是一个复杂的层次结构,包括网络接口层、网络层(IP 层)、传输层(TCP、UDP 等)和应用层。而 Socket 就像是一个 “翻译官”,将应用层的简单请求 “翻译” 成 TCP/IP 协议栈能够理解的指令,同时把协议栈处理后的结果 “翻译” 回应用层能够使用的数据形式。
以常见的 HTTP 请求为例,当我们在浏览器中输入网址并访问网页时,浏览器作为应用层程序,通过 Socket 向 TCP/IP 协议栈发起请求。Socket 首先将请求封装成符合 TCP 协议格式的数据包,交给传输层的 TCP 协议处理。TCP 协议负责建立可靠的连接,进行流量控制和错误重传等操作。然后,数据包被交给网络层的 IP 协议,IP 协议负责根据目标 IP 地址进行路由选择,将数据包发送到正确的网络路径上。最后,数据包通过网络接口层到达物理网络,传输到目标服务器。服务器端的 Socket 接收到数据包后,按照相反的流程将数据解包,最终将请求传递给 Web 服务器应用程序进行处理。整个过程中,Socket 作为中间抽象层,隐藏了 TCP/IP 协议栈的复杂性,让应用程序开发者无需深入了解底层协议细节,就能轻松实现网络通信功能。
基于 TCP 协议的客户端和服务器:
-
服务端和客户端初始化 socket,得到文件描述符;
-
服务端调用 bind,绑定 IP 地址和端口;
-
服务端调用 listen,进行监听;
-
服务端调用 accept,等待客户端连接;
-
客户端调用 connect,向服务器端的地址和端口发起连接请求;
-
服务端 accept 返回 用于传输的 socket的文件描述符;
-
客户端调用 write 写入数据;服务端调用 read 读取数据;
-
客户端断开连接时,会调用 close,那么服务端 read 读取数据的时候,就会读取到了 EOF,待处理完数据后,服务端调用 close,表示连接关闭。
这里需要注意的是,服务端调用 accept 时,连接成功了会返回一个已完成连接的 socket,后续用来传输数据;所以,监听的 socket 和真正用来传送数据的 socket,是「两个」 socket,一个叫作监听 socket,一个叫作已完成连接 socket;成功连接建立之后,双方开始通过 read 和 write 函数来读写数据,就像往一个文件流里面写东西一样。
2.2在文件系统中的角色
在 Linux 系统中,一切皆文件,Socket 也不例外。Socket 属于文件系统的一部分,这一设计理念体现了 Linux 系统的简洁与统一。每个 Socket 都有一个对应的文件描述符(File Descriptor),文件描述符是一个非负整数,在进程中用于标识打开的文件或 Socket 等 I/O 资源。通过文件描述符,进程可以像操作普通文件一样对 Socket 进行读写操作,这使得 Socket 的操作与其他文件操作具有一致性,大大简化了编程模型。
例如,在使用 C 语言进行 Socket 编程时,我们可以使用read和write函数对 Socket 进行数据的接收和发送,就如同对文件进行读写操作一样:
#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>int main() {int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0) {perror("socket creation failed");return 1;}// 假设已经完成Socket的绑定、监听和连接等操作char buffer[1024];ssize_t bytes_read = read(sockfd, buffer, sizeof(buffer));if (bytes_read > 0) {buffer[bytes_read] = '\0';printf("Received data: %s\n", buffer);} else if (bytes_read == 0) {printf("Connection closed by peer\n");} else {perror("read failed");}close(sockfd);return 0;
}
上述代码中,read函数从 Socket 对应的文件描述符sockfd中读取数据,write函数则用于向 Socket 写入数据。这种将 Socket 与文件系统统一的设计,使得开发者可以利用熟悉的文件操作函数来处理网络通信,提高了开发效率和代码的可维护性。同时,也方便了系统对资源的管理,因为文件描述符是操作系统管理 I/O 资源的重要方式,Socket 作为文件系统的一部分,可以纳入统一的资源管理体系中。
三、Socket 的本质剖析
3.1从通信端点角度理解
从通信端点的角度来看,Socket 可以被视为两个网络各自通信连接中的端点 。它是网络中进程间通信的抽象表示,就像现实生活中,我们通过电话与他人沟通时,电话两端的听筒就是通信端点,而 Socket 在网络通信中就扮演着这样的 “听筒” 角色。
以浏览器与服务器的通信为例,当我们在浏览器中输入一个网址,比如https://www.example.com,浏览器会创建一个 Socket 对象,并通过这个 Socket 向服务器的 IP 地址和对应的端口(通常是 80 端口用于 HTTP 协议,443 端口用于 HTTPS 协议)发起连接请求。服务器端也会有一个 Socket 在监听这个端口,当它接收到客户端的连接请求后,双方的 Socket 就建立了一个通信链路。在这个链路中,客户端 Socket 将用户请求的数据(如 HTTP 请求报文)发送出去,服务器端 Socket 接收这些数据并进行处理,然后将处理结果(如 HTTP 响应报文)通过 Socket 返回给客户端。整个过程中,Socket 作为通信端点,负责数据的发送和接收,使得浏览器和服务器之间能够实现数据交互,让我们最终在浏览器中看到网页的内容。
再比如,在即时通讯软件中,每个客户端和服务器之间都通过 Socket 建立连接。当用户 A 发送一条消息给用户 B 时,用户 A 的客户端 Socket 将消息数据发送出去,经过网络传输,到达服务器的 Socket,服务器再将消息转发到用户 B 的客户端 Socket,从而实现消息的传递。Socket 就像是网络世界中各个进程之间沟通的 “大门”,通过它,不同主机上的进程能够相互交流,实现各种网络应用的功能。
3.2基于内核缓冲区的实现
在 Linux 内核中,Socket 本质上是借助内核缓冲区形成的伪文件 。当应用程序创建一个 Socket 时,内核会为其分配相应的内核缓冲区,包括发送缓冲区和接收缓冲区。发送缓冲区用于存储应用程序要发送的数据,接收缓冲区则用于存储从网络中接收到的数据。
从文件操作的角度来看,Socket 与普通文件有很多相似之处。我们可以使用类似文件操作的函数来对 Socket 进行操作,如read和write函数。当应用程序调用write函数向 Socket 写入数据时,实际上是将数据从应用程序的缓冲区拷贝到 Socket 的发送缓冲区中;而调用read函数从 Socket 读取数据时,是从 Socket 的接收缓冲区中读取数据到应用程序的缓冲区。这种设计使得 Socket 的操作方式与文件操作方式统一,大大降低了开发者的学习成本和编程难度。
这种基于内核缓冲区的实现方式有诸多优势。首先,内核缓冲区可以对数据进行缓存,减少了网络通信的次数,提高了数据传输的效率。例如,当应用程序有大量数据要发送时,如果没有缓冲区,每次都直接发送数据,会导致频繁的网络交互,增加网络开销。而通过发送缓冲区,应用程序可以将数据先写入缓冲区,当缓冲区达到一定大小或者满足一定条件时,再一次性将数据发送出去,这样就减少了网络传输的次数,提高了传输效率。
其次,缓冲区还可以在一定程度上缓解网络拥塞。当网络出现拥塞时,数据的传输速度会变慢,接收方可能无法及时接收数据。此时,发送缓冲区可以暂时存储数据,避免数据丢失,等网络状况好转后再继续发送;接收缓冲区则可以存储接收到的数据,让应用程序有足够的时间来处理这些数据,保证了数据传输的稳定性和可靠性。
四、Socket的类型及设计
4.1Socket的类型
在 Socket 编程中,不同类型的 Socket 适用于不同的应用场景,它们各自具有独特的特点和协议基础。了解这些 Socket 类型,对于我们选择合适的网络通信方式至关重要。
(1)流式套接字(SOCK_STREAM)
流式套接字基于 TCP 协议,提供可靠的双向顺序数据流 。在这种类型的 Socket 通信中,数据就像水流一样,源源不断且有序地在发送方和接收方之间流动。它具有以下几个关键特点:
-
可靠性:TCP 协议通过一系列机制确保数据的可靠传输。例如,它会对发送的数据进行编号,接收方收到数据后会发送确认消息(ACK),如果发送方在一定时间内没有收到 ACK,就会重发数据,从而保证数据不会丢失。
-
顺序性:数据按照发送的顺序进行接收,不会出现乱序的情况。这是因为 TCP 协议在传输过程中会对数据进行排序,确保接收方能够按照正确的顺序组装数据。
-
面向连接:在进行数据传输之前,需要先建立连接,就像打电话之前要先拨通对方号码一样。连接建立后,双方才能进行数据传输,传输结束后再关闭连接。
以 Web 服务器与客户端的通信为例,当我们在浏览器中输入网址并访问网页时,浏览器会创建一个流式套接字,并通过这个套接字向 Web 服务器发起连接请求。服务器接收到请求后,与浏览器建立 TCP 连接。在这个连接上,浏览器向服务器发送 HTTP 请求报文,服务器处理请求后,将 HTTP 响应报文通过相同的连接返回给浏览器。由于流式套接字的可靠性和顺序性,浏览器能够完整、正确地接收到服务器返回的网页数据,从而正常显示网页内容。
(2)数据报套接字(SOCK_DGRAM)
数据报套接字基于 UDP 协议,提供双向的数据传输,但不保证数据传输的可靠性 。与流式套接字相比,它具有以下特点:
-
不可靠性:UDP 协议不保证数据一定能到达目标,也不保证数据的顺序和完整性。数据在传输过程中可能会丢失、重复或乱序,这是因为 UDP 没有像 TCP 那样的确认和重传机制。
-
无连接:在数据传输前不需要建立连接,就像寄信一样,直接把信(数据)发送出去即可,不需要事先通知对方。这种方式使得数据报套接字的传输效率较高,因为省去了建立和拆除连接的开销。
-
固定长度数据传输:每个 UDP 数据报都有一个固定的最大长度,超过这个长度的数据需要分割成多个数据报进行传输。
以视频通话应用为例,视频通话需要实时传输大量的视频和音频数据。由于对实时性要求很高,如果采用可靠性高但传输延迟较大的 TCP 协议,可能会导致画面卡顿、声音延迟等问题。而 UDP 协议的低延迟特性更适合视频通话场景,虽然可能会丢失一些数据,但只要丢失的数据量在可接受范围内,视频和音频仍然可以正确解析,不会对通话质量产生太大影响。在视频通话过程中,发送方通过数据报套接字将视频和音频数据以 UDP 数据报的形式发送出去,接收方接收到数据后进行实时播放,即使有少量数据丢失,也能通过一些算法进行补偿,保证视频和音频的流畅播放。
(3)原始套接字(SOCK_RAW)
原始套接字允许进程直接访问底层协议,这使得它在网络协议开发、网络测试等场景中发挥着重要作用 。与流式套接字和数据报套接字不同,原始套接字可以读写内核没有处理的 IP 数据包,开发者可以通过它来实现自定义的网络协议,或者对网络数据包进行更深入的分析和处理。
-
网络协议开发:在开发新的网络协议时,原始套接字是必不可少的工具。开发者可以利用它直接操作 IP 数据包,实现新协议的各种功能。例如,假设要开发一种新的物联网通信协议,就可以通过原始套接字来构建和发送符合该协议格式的 IP 数据包,同时接收和解析来自其他设备的数据包,进行协议的测试和验证。
-
网络测试与诊断:在网络测试和故障诊断中,原始套接字可以帮助我们获取更详细的网络信息。比如,使用 ping 命令时,实际上就是利用原始套接字发送 ICMP(Internet Control Message Protocol)回显请求报文,并接收 ICMP 回显应答报文,以此来测试网络的连通性。再如,在网络安全领域,通过原始套接字可以捕获和分析网络中的数据包,检测是否存在异常流量或攻击行为。
4.2Socket的设计
现在我们抛开socket,重新设计一个内核网络传输功能。我们想要将数据从 A 电脑的某个进程发到 B 电脑的某个进程,从操作上来看,就是发数据给远端和从远端接收数据,也就是写数据和读数据。
但这里有两个问题:
-
接收端和发送端可能不止一个,因此需要用 IP 和端口做区分,IP 用来定位是哪台电脑,端口用来定位是这台电脑上的哪个进程。
-
发送端和接收端的传输方式有很多区别,如可靠的 TCP 协议、不可靠的 UDP 协议,甚至还需要支持基于 icmp 协议的 ping 命令。
为了支持这些功能,需要定义一个数据结构 sock,在 sock 里加入 IP 和端口字段。这些协议虽然各不相同,但有一些功能相似的地方,可以将不同的协议当成不同的对象类(或结构体),将公共的部分提取出来,通过“继承”的方式复用功能。于是,定义了一些数据结构:sock 是最基础的结构,维护一些任何协议都有可能会用到的收发数据缓冲区。
在 Linux 内核 2.6 相关的源码中,sock 结构体的定义可能类似于:
struct sock {// 相关字段struct sk_buff_head sk_receive_queue; // 接收数据缓冲区struct sk_buff_head sk_write_queue; // 发送数据缓冲区// 其他可能的字段
};
inet_sock 特指用了网络传输功能的 sock,在 sock 的基础上还加入了 TTL、端口、IP 地址这些跟网络传输相关的字段信息。比如 Unix domain socket,用于本机进程之间的通信,直接读写文件,不需要经过网络协议栈。
可能的定义:
struct inet_sock {struct sock sk; // 继承自 sock__be32 port; // 端口__be32 saddr; // IP 地址// 其他相关字段
};
inet_connection_sock 是指面向连接的 sock,在 inet_sock 的基础上加入面向连接的协议里相关字段,比如 accept 队列、数据包分片大小、握手失败重试次数等。虽然现在提到面向连接的协议就是指 TCP,但设计上 Linux 需要支持扩展其他面向连接的新协议。
例如:
struct inet_connection_sock {struct inet_sock inet; // 继承自 inet_sockstruct request_sock_queue accept_queue; // accept 队列// 其他相关字段
};
tcp_sock 就是正儿八经的 TCP 协议专用的 sock 结构,在 inet_connection_sock 基础上还加入了 TCP 特有的滑动窗口、拥塞避免等功能。同样 UDP 协议也会有一个专用的数据结构,叫 udp_sock。
大概如下:
struct tcp_sock {struct inet_connection_sock icsk; // 继承自 inet_connection_sock// TCP 特有的字段,如滑动窗口、拥塞避免等相关字段
};
有了这套数据结构,将它跟硬件网卡对接一下,就实现了网络传输的功能。
4.3提供 Socket 层
由于这里面的代码复杂,还操作了网卡硬件,需要较高的操作系统权限,再考虑到性能和安全,于是将它放在操作系统内核里。
为了让用户空间的应用程序使用这部分功能,将这部分功能抽象成简单的接口,将内核的 sock 封装成文件。创建 sock 的同时也创建一个文件,文件有个文件描述符 fd,通过它可以唯一确定是哪个 sock。将fd暴露给用户,用户就可以像操作文件句柄那样去操作这个 sock 。
struct file{//文件相关的字段.....void *private_data; //指向sock
}
创建socket时,其实就是创建了一个文件结构体,并将private_data字段指向sock。有了 sock_fd 句柄后,提供了一些接口,如 send()、recv()、bind()、listen()、connect() 等,这些就是 socket 提供出来的接口。所以说,socket 其实就是个代码库或接口层,它介于内核和应用程序之间,提供了一堆接口,让我们去使用内核功能,本质上就是一堆高度封装过的接口。
我们平时写的应用程序里代码里虽然用了socket实现了收发数据包的功能,但其 实真正执行网络通信功能的,不是应用程序,而是linux内核。
在操作系统内核空间里,实现网络传输功能的结构是sock,基于不同的协议和应用场景,会被泛化为各种类型的xx_sock,它们结合硬件,共同实现了网络传输功能。为了将这部分功能暴露给用户空间的应用程序使用,于是引入了socket层,同时将sock嵌入到文件系统的框架里,sock就变成了一个特殊的文件,用户就可以在用户空间使用文件句柄,也就是socket_fd来操作内核sock的网络传输能力。
五、Socket 的工作机制
5.1创建与初始化
当应用程序需要进行网络通信时,首先会调用 socket 函数来创建一个套接字 。以 C 语言为例,socket 函数的原型如下:
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
其中,domain参数指定协议族,如AF_INET表示 IPv4 协议族,AF_INET6表示 IPv6 协议族;type参数指定套接字类型,如SOCK_STREAM表示流式套接字,SOCK_DGRAM表示数据报套接字;protocol参数通常设置为 0,表示使用默认协议。
当应用程序调用 socket 函数时,内核会为该套接字分配相应的资源,包括内存空间和文件描述符 。内核会在内存中创建一个套接字结构体,用于存储与该套接字相关的控制信息,如套接字的状态、连接的对端地址和端口、发送和接收缓冲区等。同时,内核会为套接字分配一个唯一的文件描述符,并将该文件描述符返回给应用程序。应用程序通过这个文件描述符来标识和操作该套接字,就像通过文件描述符操作普通文件一样。
5.2连接建立(仅针对面向连接的套接字)
以 TCP 协议的流式套接字为例,连接建立需要通过三次握手来完成 。三次握手的过程如下:
-
第一次握手:客户端向服务器发送一个 SYN(同步)报文段,该报文段中包含客户端的初始序列号(Sequence Number,简称 Seq),假设为 x 。此时,客户端进入 SYN_SENT 状态,等待服务器的响应。这个过程就好比客户端给服务器打电话说:“我想和你建立连接,这是我的初始序号 x”。
-
第二次握手:服务器接收到客户端的 SYN 报文段后,会回复一个 SYN-ACK(同步确认)报文段 。该报文段中包含服务器的初始序列号,假设为 y,同时 ACK(确认)字段的值为 x + 1,表示服务器已经收到客户端的 SYN 报文段,并且确认号为客户端的序列号加 1。此时,服务器进入 SYN_RCVD 状态。这就像是服务器接起电话回应客户端:“我收到你的连接请求了,这是我的初始序号 y,我确认收到了你的序号 x”。
-
第三次握手:客户端接收到服务器的 SYN-ACK 报文段后,会发送一个 ACK 报文段给服务器 。该报文段的 ACK 字段的值为 y + 1,表示客户端已经收到服务器的 SYN-ACK 报文段,并且确认号为服务器的序列号加 1。此时,客户端进入 ESTABLISHED 状态,服务器接收到 ACK 报文段后也进入 ESTABLISHED 状态,连接建立成功。这相当于客户端再次回应服务器:“我收到你的回复了,连接建立成功,我们可以开始通信了”。
三次握手的作用在于确保双方的通信能力正常,并且能够同步初始序列号,为后续的数据传输建立可靠的基础 。通过三次握手,客户端和服务器都能确认对方可以正常接收和发送数据,避免了旧连接请求的干扰,保证了连接的唯一性和正确性。
5.3数据传输
在数据传输阶段,发送端和接收端的数据流动过程如下:
-
发送端:应用程序调用write或send函数将数据发送到 Socket 。这些函数会将应用程序缓冲区中的数据拷贝到 Socket 的发送缓冲区中。然后,内核会根据 Socket 的类型和协议,对数据进行封装。对于 TCP 套接字,数据会被分割成 TCP 段,并添加 TCP 头部,包括源端口、目标端口、序列号、确认号等信息;对于 UDP 套接字,数据会被封装成 UDP 数据报,并添加 UDP 头部,包含源端口和目标端口。接着,数据会被传递到网络层,添加 IP 头部,包含源 IP 地址和目标 IP 地址,形成 IP 数据包。最后,IP 数据包通过网络接口层发送到物理网络上。
-
接收端:数据从物理网络进入接收端的网络接口层 。网络接口层接收到 IP 数据包后,会进行解包,将 IP 头部去除,然后将数据传递到网络层。网络层根据 IP 头部中的目标 IP 地址,判断该数据包是否是发给本机的。如果是,则去除 IP 头部,将数据传递到传输层。传输层根据协议类型(TCP 或 UDP),对数据进行相应的处理。对于 TCP 数据,会检查序列号和确认号,进行流量控制和错误重传等操作;对于 UDP 数据,直接去除 UDP 头部,将数据传递到 Socket 的接收缓冲区。最后,应用程序调用read或recv函数从 Socket 的接收缓冲区中读取数据到应用程序缓冲区中,完成数据的接收。
5.4连接关闭
对于 TCP 连接,关闭过程需要通过四次挥手来完成 。四次挥手的过程如下:
-
第一次挥手:主动关闭方(可以是客户端或服务器)发送一个 FIN(结束)报文段,表示自己已经没有数据要发送了,准备断开连接 。此时,主动关闭方进入 FIN_WAIT_1 状态。这就像一方说:“我这边数据发完了,准备断开连接”。
-
第二次挥手:被动关闭方接收到 FIN 报文段后,会发送一个 ACK 确认报文段,表示已收到主动关闭方的断开请求,并同意断开连接 。但此时被动关闭方可能还没有完成数据处理,它需要继续处理缓冲区中的数据。此时,被动关闭方进入 CLOSE_WAIT 状态,主动关闭方接收到 ACK 报文段后进入 FIN_WAIT_2 状态。相当于另一方回应:“我收到你的断开请求了,等我处理完数据就断开”。
-
第三次挥手:当被动关闭方完成数据处理后,它会向主动关闭方发送一个 FIN 报文段,表示自己的数据也已经发送完毕,准备关闭连接 。此时,被动关闭方进入 LAST_ACK 状态。即另一方说:“我数据处理完了,现在可以断开了”。
-
第四次挥手:主动关闭方收到被动关闭方的 FIN 报文段后,会发送一个 ACK 确认报文段,确认接收到了被动关闭方的断开请求 。此时,主动关闭方进入 TIME_WAIT 状态,等待一段时间(通常为 2 倍的最大报文段生存时间,即 2MSL)后,自动进入 CLOSE 状态,连接完全关闭。被动关闭方收到 ACK 报文段后,直接进入 CLOSE 状态。这一步就像是最初发起断开的一方回应:“我确认收到你的断开请求,我们可以彻底断开了”。
之所以需要四次挥手来确保连接的可靠关闭,是因为 TCP 连接是全双工的,每个方向都必须单独关闭 。在第一次挥手中,主动关闭方只是表示自己不再发送数据,但仍可以接收数据;被动关闭方发送 ACK 确认后,还需要时间处理剩余数据,处理完后再发送 FIN 报文表示自己也不再发送数据。通过四次挥手,双方都能确认对方已经完成数据传输,并且所有数据都已被正确接收,从而保证了连接关闭的可靠性,避免数据丢失或不完全传输。
六、Socket 在Linux系统中的应用实例
6.1简单的 TCP 服务器与客户端程序
下面是一个使用 C 语言编写的简单 TCP 服务器和客户端程序示例,通过这个示例,我们可以更直观地了解 Socket 在实际应用中的使用方法。
TCP 服务器代码(server.c):
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>#define PORT 8888
#define MAX_CONNECTIONS 5
#define BUFFER_SIZE 1024int main() {// 创建套接字int server_socket = socket(AF_INET, SOCK_STREAM, 0);if (server_socket == -1) {perror("Socket creation failed");exit(EXIT_FAILURE);}struct sockaddr_in server_addr;server_addr.sin_family = AF_INET;server_addr.sin_port = htons(PORT);server_addr.sin_addr.s_addr = INADDR_ANY;// 绑定套接字到指定地址和端口if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {perror("Bind failed");close(server_socket);exit(EXIT_FAILURE);}// 监听连接请求if (listen(server_socket, MAX_CONNECTIONS) == -1) {perror("Listen failed");close(server_socket);exit(EXIT_FAILURE);}printf("Server is listening on port %d...\n", PORT);while (1) {struct sockaddr_in client_addr;socklen_t client_addr_len = sizeof(client_addr);// 接受客户端连接请求int client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_addr_len);if (client_socket == -1) {perror("Accept failed");continue;}printf("Client connected.\n");char buffer[BUFFER_SIZE] = {0};// 接收客户端发送的数据ssize_t bytes_read = recv(client_socket, buffer, sizeof(buffer), 0);if (bytes_read > 0) {buffer[bytes_read] = '\0';printf("Received from client: %s\n", buffer);// 向客户端发送响应数据const char *response = "Message received by server";ssize_t bytes_sent = send(client_socket, response, strlen(response), 0);if (bytes_sent == -1) {perror("Send failed");}} else if (bytes_read == 0) {printf("Client disconnected.\n");} else {perror("Receive failed");}// 关闭客户端套接字close(client_socket);}// 关闭服务器套接字close(server_socket);return 0;
}
-
socket 函数:创建一个基于 IPv4 的流式套接字(SOCK_STREAM),用于 TCP 通信。
-
bind 函数:将套接字绑定到指定的 IP 地址(INADDR_ANY 表示接受任意 IP 地址的连接)和端口(PORT)。
-
listen 函数:使套接字进入监听状态,等待客户端的连接请求,最大允许同时有MAX_CONNECTIONS个连接请求排队。
-
accept 函数:阻塞等待并接受客户端的连接请求,返回一个新的套接字client_socket,用于与该客户端进行通信。
-
recv 函数:从客户端套接字接收数据,存储在buffer中。
-
send 函数:向客户端套接字发送响应数据。
TCP 客户端代码(client.c):
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>#define PORT 8888
#define SERVER_IP "127.0.0.1"
#define BUFFER_SIZE 1024int main() {// 创建套接字int client_socket = socket(AF_INET, SOCK_STREAM, 0);if (client_socket == -1) {perror("Socket creation failed");exit(EXIT_FAILURE);}struct sockaddr_in server_addr;server_addr.sin_family = AF_INET;server_addr.sin_port = htons(PORT);if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {perror("Invalid address/ Address not supported");close(client_socket);exit(EXIT_FAILURE);}// 连接到服务器if (connect(client_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {perror("Connect failed");close(client_socket);exit(EXIT_FAILURE);}printf("Connected to server.\n");const char *message = "Hello, server!";// 向服务器发送数据ssize_t bytes_sent = send(client_socket, message, strlen(message), 0);if (bytes_sent == -1) {perror("Send failed");close(client_socket);exit(EXIT_FAILURE);}char buffer[BUFFER_SIZE] = {0};// 接收服务器返回的数据ssize_t bytes_read = recv(client_socket, buffer, sizeof(buffer), 0);if (bytes_read > 0) {buffer[bytes_read] = '\0';printf("Received from server: %s\n", buffer);} else if (bytes_read == 0) {printf("Server disconnected.\n");} else {perror("Receive failed");}// 关闭客户端套接字close(client_socket);return 0;
}
-
socket 函数:同样创建一个基于 IPv4 的流式套接字。
-
inet_pton 函数:将点分十进制的 IP 地址(SERVER_IP)转换为网络字节序的二进制形式,存储在server_addr.sin_addr中。
-
connect 函数:向服务器发起连接请求,连接到指定的 IP 地址和端口。
-
send 函数:向服务器发送数据。
-
recv 函数:接收服务器返回的数据。
通过这两个程序,我们可以看到 Socket 在 TCP 通信中的基本应用,服务器端监听端口并处理客户端的连接和数据请求,客户端连接到服务器并进行数据的发送和接收。
6.2UDP 数据传输示例
下面是一个使用 UDP 协议进行数据传输的代码示例,展示了如何发送和接收 UDP 数据报。
UDP 发送端代码(sender.c):
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>#define PORT 9999
#define DEST_IP "127.0.0.1"
#define BUFFER_SIZE 1024int main() {// 创建UDP套接字int sender_socket = socket(AF_INET, SOCK_DGRAM, 0);if (sender_socket == -1) {perror("Socket creation failed");exit(EXIT_FAILURE);}struct sockaddr_in dest_addr;dest_addr.sin_family = AF_INET;dest_addr.sin_port = htons(PORT);if (inet_pton(AF_INET, DEST_IP, &dest_addr.sin_addr) <= 0) {perror("Invalid address/ Address not supported");close(sender_socket);exit(EXIT_FAILURE);}while (1) {char buffer[BUFFER_SIZE] = {0};printf("Enter message to send (or 'exit' to quit): ");fgets(buffer, sizeof(buffer), stdin);buffer[strcspn(buffer, "\n")] = '\0';if (strcmp(buffer, "exit") == 0) {break;}// 发送UDP数据报ssize_t bytes_sent = sendto(sender_socket, buffer, strlen(buffer), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));if (bytes_sent == -1) {perror("Sendto failed");}}// 关闭套接字close(sender_socket);return 0;
}
-
socket 函数:创建一个基于 IPv4 的数据报套接字(SOCK_DGRAM),用于 UDP 通信。
-
inet_pton 函数:将目标 IP 地址(DEST_IP)转换为网络字节序的二进制形式,存储在dest_addr.sin_addr中。
-
sendto 函数:向指定的目标地址(dest_addr)发送 UDP 数据报,数据存储在buffer中。
UDP 接收端代码(receiver.c):
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>#define PORT 9999
#define BUFFER_SIZE 1024int main() {// 创建UDP套接字int receiver_socket = socket(AF_INET, SOCK_DGRAM, 0);if (receiver_socket == -1) {perror("Socket creation failed");exit(EXIT_FAILURE);}struct sockaddr_in server_addr;server_addr.sin_family = AF_INET;server_addr.sin_port = htons(PORT);server_addr.sin_addr.s_addr = INADDR_ANY;// 绑定套接字到指定地址和端口if (bind(receiver_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {perror("Bind failed");close(receiver_socket);exit(EXIT_FAILURE);}printf("Receiver is listening on port %d...\n", PORT);while (1) {char buffer[BUFFER_SIZE] = {0};struct sockaddr_in client_addr;socklen_t client_addr_len = sizeof(client_addr);// 接收UDP数据报ssize_t bytes_read = recvfrom(receiver_socket, buffer, sizeof(buffer), 0, (struct sockaddr *)&client_addr, &client_addr_len);if (bytes_read > 0) {buffer[bytes_read] = '\0';printf("Received from client: %s\n", buffer);} else if (bytes_read == 0) {printf("Connection closed.\n");} else {perror("Receivefrom failed");}}// 关闭套接字close(receiver_socket);return 0;
}
-
socket 函数:创建 UDP 套接字。
-
bind 函数:将套接字绑定到指定的 IP 地址(INADDR_ANY)和端口(PORT),以便接收来自客户端的数据报。
-
recvfrom 函数:从客户端接收 UDP 数据报,数据存储在buffer中,并获取发送端的地址信息(client_addr)。
通过这个 UDP 数据传输示例,我们可以看到 UDP 通信的基本流程,发送端将数据报发送到指定的目标地址和端口,接收端在绑定的地址和端口上等待接收数据报。与 TCP 不同,UDP 不需要建立连接,数据报的发送和接收更加简单直接,但也不保证数据的可靠性和顺序性 。
相关文章:
透视Linux内核:深度剖析Socket机制的本质
在Linux操作系统构建的网络世界里,Socket 宛如纵横交错的交通枢纽,承担着不同应用程序间数据往来的重任。无论是日常浏览网页时,浏览器与 Web 服务器间信息的快速交互;还是畅玩网络游戏过程中,玩家操作指令与游戏服务器…...
PostgreSQL数据表操作SQL
数据表操作 创建表 CREATE TABLE t_test(id SERIAL PRIMARY KEY,name varchar(30),birthday date);修改表名 ALTER TABLE t_test RENAME TO t_test1;添加列 ALTER TABLE t_test1 ADD COLUMN score numeric(5,2);删除列 ALTER TABLE t_test1 DROP COLUMN score;修改数据类型 AL…...
OpenAI最新发布的GPT-4.1系列模型,性能体验如何?
简单来说,这次GPT-4.1的核心思路就是:更实用、更懂开发者、更便宜!OpenAI这次没搞太多花里胡哨的概念,而是实实在在地提升了大家最关心的几个点:写代码、听指令、处理超长文本,而且知识库也更新到了2024年6月。 写代码。要说这次GPT-4.1最亮眼的地方,可能就是写代码这块…...
2025五一数学建模C题完整分析论文(共36页)(含模型、可运行代码、数据)
2025年五一数学建模C题完整分析论文 摘要 一、问题分析 二、问题重述 三、模型假设 四、符号定义 五、 模型建立与求解 5.1问题1 5.1.1问题1思路分析 5.1.2问题1模型建立 5.1.3问题1代码 5.1.4问题1求解结果 5.2问题2 5.2.1问题2思路分析 5.2.2问题…...
Vue2基础速成
一、准备工作 首先下载vue2的JavaScript库,并且命名为vue.min.js 下载链接:https://cdn.jsdelivr.net/npm/vue2(若链接失效可去vue官网寻找) CTRLS即可下载保存 文件目录结构 二、使用操作原生DOM与使用VUE操作DOM的便捷性比较…...
Java大厂硬核面试:Flink流处理容错、Pomelo JVM调优、MyBatis二级缓存穿透防护与Kubernetes服务网格实战解析
第二幕:系统架构设计 面试官:设计一个处理10万QPS的秒杀系统需要的技术方案和技术选型 xbhog:采用基础架构: 存储层:Redis限流分布式锁服务层:Sentinel流量控制消息层:RocketMQ事务消息保证最…...
Python实现简易博客系统
下面我将介绍如何使用Python实现一个简易的博客系统,包含前后端完整功能。这个系统将使用Flask作为Web框架,SQLite作为数据库,并包含用户认证、文章发布、评论等基本功能。 1. 系统架构设计 技术栈选择 后端:Flask (Python Web框架)数据库:SQLite (轻量…...
【T型三电平仿真】SPWM调制
自然采样法和规则采样法的特点和计算 https://blog.csdn.net/u010632165/article/details/110889621 单极性和双极性的单双体现在什么地方 单极性和双极性的单双是指载波三角波的极性 为什么simulink进行电路仿真时,都需要放置一个powergui模块 任何使用SimPow…...
Astral Ascent 星界战士(星座上升) [DLC 解锁] [Steam] [Windows SteamOS macOS]
Astral Ascent 星界战士(星座上升) [DLC 解锁] [Steam] [Windows & SteamOS & macOS] 需要有游戏正版基础本体,安装路径不能带有中文,或其它非常规拉丁字符; DLC 版本 至最新全部 DLC 后续可能无法及时更新文章…...
Ubuntu20.04如何优雅的安装ROS 1(胎教级教程)
1、USTC的源: sudo sh -c . /etc/lsb-release && echo "deb http://mirrors.ustc.edu.cn/ros/ubuntu/ lsb_release -cs main" > /etc/apt/sources.list.d/ros-latest.list2、设置的ROS源添加密钥: sudo apt-key adv --keyserver …...
terraform生成随机密码
在 Terraform 中生成安全随机密码可以通过 random_password 资源实现,以下是完整实现方案及安全实践: 基础实现 (生成随机密码) terraform {required_providers {random {source "hashicorp/random"version "~> 3.5.1" # 使…...
一个linux系统电脑,一个windows电脑,怎么实现某一个文件夹共享
下载Samba linux主机名字不能超过15个字符 sudo dnf install samba samba-client -y 创建共享文件夹 sudo mkdir /shared 配置文件 vim /etc/samba/smb.conf [shared] path /shared available yes valid users linux电脑用户 read only no browsable yes p…...
等保系列(一):网络安全等级保护介绍
一、基本概念 网络安全等级保护(以下简称:等保)是根据《中华人民共和国网络安全法》及配套规定(如《信息安全技术 网络安全等级保护基本要求》等)建立的系统性安全防护机制,要求网络运营者根据信息系统的重…...
【专题五】位运算(2)
📝前言说明: 本专栏主要记录本人的基础算法学习以及LeetCode刷题记录,按专题划分每题主要记录:(1)本人解法 本人屎山代码;(2)优质解法 优质代码;ÿ…...
【2025五一数学建模竞赛A题】 支路车流量推测问题|建模过程+完整代码论文全解全析
你是否在寻找数学建模比赛的突破点?数学建模进阶思路! 作为经验丰富的美赛O奖、国赛国一的数学建模团队,我们将为你带来本次数学建模竞赛的全面解析。这个解决方案包不仅包括完整的代码实现,还有详尽的建模过程和解析,…...
案例:自动化获取Web页面小说(没钱修什么仙)——selenium
目录 前言一、目标即结果1. 目标:2. 提前了解网页信息3. 结果 二、逐步分析1 . selenium启动2. 获取所有章节3.打开对应章节链接,获取小说文本4. 内容写入本地文件 三、完整代码四、声名 前言 提示:通过案例掌握selenium语法 涉及技术&#…...
硬件工程师面试常见问题(11)
第五十一问:器件手册的翻译题目 要学英语啊,孩子。 第五十二问:二极管三极管常识题 1.二极管的导通电压一般是 0.7V 2.MOS管根据掺杂类型可以分为 NMOS和PMOS 3.晶体三极管在工作时,发射结和集电结均处于正向偏置,该晶体管工作在一饱和态。…...
TTL、LRU、LFU英文全称及释义
以下是 TTL、LRU 和 LFU 的英文全称及其简要解释: 1. TTL 全称:Time To Live(存活时间)含义: 表示数据在缓存或存储中的有效存活时间,过期后自动删除。 Redis 示例:SET key value EX 60&#x…...
本地部署 n8n 中文版
本地部署 n8n 中文版 0. n8n的核心价值1. 本地部署 n8n 中文版2. 访问 n8n 在技术团队寻求高效自动化解决方案的今天,n8n 作为一款安全的工作流自动化平台脱颖而出!它将代码的灵活性与低代码的便捷性深度融合,为开发者提供了独特的工具选择。…...
蓝桥杯比赛
蓝桥杯全国软件和信息技术专业人才大赛是由工业和信息化部人才交流中心主办,国信蓝桥教育科技(北京)股份有限公司承办的计算机类学科竞赛。以下是其相关信息: 参赛对象 具有正式全日制学籍且符合相关科目报名要求的研究生、本科生…...
【Linux】Makefile
Makefile常用用法介绍。 部分图片和经验来源于网络,还有正点原子的Linux驱动开发教程,若有侵权麻烦联系我删除,主要是做笔记的时候忘记写来源了,做完笔记很久才写博客。 专栏目录:记录自己的嵌入式学习之路-CSDN博客 1…...
【工具】Windows批量文件复制教程:用BAT脚本自动化文件管理
一、引言 在日常开发与部署过程中,文件的自动化复制是一个非常常见的需求。无论是在构建过程、自动部署,还是备份任务中,开发者经常需要将某个目录中的 DLL、配置文件、资源文件批量复制到目标位置。相比使用图形界面的复制粘贴操作…...
字节一面:后端开发
前言 这是我字节一面的回忆录,可能有些不全。 由于博主是Java面试Go岗,操作系统和计网问的还是比较多。 个人感觉字节很喜欢追问,博主被追问拷打的找不到北了,总结还是学的太浅了。 面试官给我的建议:再更深挖一些…...
西式烹饪实训室建设路径
在餐饮行业持续变革与教育信息化快速发展的当下,西式烹饪实训室的智能化建设成为提升教学质量、培养适应新时代需求烹饪人才的关键举措。通过引入先进技术,创新教学与管理模式,为学生打造更高效、更具沉浸感的学习环境。凯禾瑞华——实训室建…...
[更新完毕]2025五一杯A题五一杯数学建模思路代码文章教学:支路车流量推测问题
完整内容请看文章最下面的推广群 支路车流量推测问题 摘要 本文针对支路车流量推测问题展开研究,通过建立数学模型解决不同场景下的车流量分析需求。 针对问题一(Y型道路场景),研究两支路汇入主路的车流量推测。通过建立线性增长…...
2025年五一杯C题详细思路分析
C题 社交媒体平台用户分析问题 问题背景 近年来,社交媒体平台打造了多元化的线上交流空间和文化圈,深刻影响着人们社交互动与信息获取。博主基于专业知识或兴趣爱好等创作出高质量内容,吸引并获得用户的关注。用户可以随时通过观看、点赞、…...
攻防世界 dice_game
dice_game dice_game (1) motalymotaly-VMware-Virtual-Platform:~/桌面$ file game game: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]254…...
如何进行 JVM 性能调优?
进行 JVM 性能调优是一个系统性的过程,旨在提高 Java 应用程序的响应速度、吞吐量、降低资源消耗(如 CPU 和内存)以及提高稳定性。 以下是一个通用的 JVM 性能调优步骤和常用方法: 第一步:明确目标与建立基线 (Defin…...
艺华直播 5.0 |专注于提供港澳台及央视频道的电视直播应用,加载快,播放流畅
艺华直播是一款专注于提供港澳台及央视频道的电视直播应用。它以加载速度快、播放流畅不卡顿著称,是目前少数能够稳定观看港澳台频道的应用之一。此次分享的版本为测试版,支持4K秒播,带来极致的观看体验。尽管该应用已开始收费,但…...
【软件设计师:复习】上午题核心知识点总结(三)
一、编译原理(基础题) 1.编译过程概述(必考) 编译过程分为六个阶段,各阶段核心任务与典型输出如下: 阶段核心任务输入输出关键方法/工具词法分析将字符流转换为标记(Token)流源代码字符串Token序列(如<ID, "x">)正则表达式、有限自动机(DFA/NFA)…...
SAE极速部署弹性微服务商城——实验记录
SAE极速部署弹性微服务商城 本实验带您体验在SAE上快速部署一个弹性的在线商城微服务应用,使得终端用户可以通过公网访问该商城,并进行压力测试以验证其性能与稳定性。 文章目录 SAE极速部署弹性微服务商城使用SAE部署应用有哪些优势? 对商城…...
内存 “舞台” 上,进程如何 “翩翩起舞”?(转)
在数字世界里,计算机的每一次高效运转都离不开内存与进程的默契配合。内存,恰似一座宏大且有序的舞台,为进程提供了施展拳脚的空间。而进程,则如同舞台上的舞者,它们在内存的舞台上,遵循着一套复杂而精妙的…...
产品手册小程序开发制作方案
公司产品手册小程序系统主要是为了解决传统纸质或PDF格式手册更新成本高、周期长,难以及时反映最新产品信息。线下分发效率低,线上分享体验差,不利于品牌推广。传统手册单向传递信息,无法与用户进行互动,企业难以了解用…...
【dify—8】Agent实战——占星师
目录 一、创建Agent应用 二、创建提示词 三、创建变量 四、添加工具 五、发布更新 六、运行 第一部分 安装difydocker教程:【difydocker安装教程】-CSDN博客 第二部分 dock重装教程:【dify—2】docker重装-CSDN博客 第三部分 dify拉取镜像ÿ…...
Redis的键过期删除策略与内存淘汰机制详解
Redis 的键过期删除策略与内存淘汰机制详解 一、键过期删除策略 Redis 通过 定期删除(Active Expire) 和 惰性删除(Lazy Expire) 两种方式结合,管理键的过期清理。 1. 惰性删除(Lazy Expire) …...
数据结构——树(中篇)
今日名言: 人生碌碌,竞短论长,却不道枯荣有数,得失难量 上次我们讲了树的相关知识,接下来就进一步了解二叉树吧。本文为个人学习笔记,如有侵权,请 联系删除,如有错误,欢…...
实验三 软件黑盒测试
实验三 软件黑盒测试使用测试界的一个古老例子---三角形问题来进行等价类划分。输入三个整数a、b和c分别作为三角形的三条边,通过程序判断由这三条边构成的三角形类型是等边三角形、等腰三角形、一般三角形或非三角形(不能构成一个三角形)。其中要求输入变量&#x…...
PHP-Cookie
Cookie 是什么? cookie 常用于识别用户。cookie 是一种服务器留在用户计算机上的小文件。每当同一台计算机通过浏览器请求页面时,这台计算机将会发送 cookie。通过 PHP,您能够创建并取回 cookie 的值。 设置Cookie 在PHP中,你可…...
提升采购管理,打造核心竞争力七步战略采购法详解P94(94页PPT)(文末有下载方式)
资料解读:《提升采购管理,打造核心竞争力 —— 七步战略采购法详解》 详细资料请看本解读文章的最后内容。 在当今竞争激烈的商业环境中,采购管理已成为企业打造核心竞争力的关键环节。这份文件围绕七步战略采购法展开,深入剖析了…...
单片机-89C51部分:13、看门狗
飞书文档https://x509p6c8to.feishu.cn/wiki/LefkwDPU7iUUWBkfKE9cGLvonSh 一、作用 程序发生死循环的时候(跑飞),能够自动复位。 启动看门狗计数器->计数器计数->指定时间内不对计数器赋值(主程序跑飞,无法喂…...
基于MyBatis的银行转账系统开发实战:从环境搭建到动态代理实现
目标: 掌握mybatis在web应用中怎么用 mybatis三大对象的作用域和生命周期 ThreadLocal原理及使用 巩固MVC架构模式 为学习MyBatis的接口代理机制做准备 实现功能: 银行账户转账 使用技术: HTML Servlet MyBatis WEB应用的名称&am…...
纹理采样+光照纹理采样
普通纹理显示 导入纹理 1.将纹理拷贝到项目中 2.配置纹理 纹理显示原理 原始纹理(边长是),如果原始图的边长不是,游戏引擎在运行时,会自动将 纹理的边长补偿为,所以补偿是有损耗的(纹理不一定是…...
408真题笔记
2024 年全国硕士研究生招生考试 计算机科学与技术学科联考 计算机学科专业基础综合 (科目代码:408) 一、单项选择题 第 01~40 小题,每小题 2 分,共 80 分。下列每小题给出的四个选项中,只有一个…...
【Shell 脚本编程】详细指南:第一章 - 基础入门与最佳实践
Shell 脚本编程完全指南:第一章 - 基础入门与最佳实践 引言:Shell 脚本在现代开发中的重要性 Shell 脚本作为 Linux/Unix 系统的核心自动化工具,在 DevOps、系统管理、数据处理等领域扮演着关键角色。本章将系统性地介绍 Shell 脚本的基础知…...
PostgreSQL数据库操作SQL
数据库操作SQL 创建 创建数据库 create database db_test;创建并指定相关参数 with owner : 所有者encoding : 编码connection limit :连接限制 create database db_test1 with owner postgresencoding utf-8connection limit 100;修改 修改数据库名称 renam…...
RAG工程-基于LangChain 实现 Advanced RAG(预检索-查询优化)(下)
Multi-Query 多路召回 多路召回流程图 多路召回策略利用大语言模型(LLM)对原始查询进行拓展,生成多个与原始查询相关的问题,再将原始查询和生成的所有相关问题一同发送给检索系统进行检索。它适用于用户查询比较宽泛、模糊或者需要…...
VBA数据库解决方案第二十讲:Select From Where条件表达式
《VBA数据库解决方案》教程(版权10090845)是我推出的第二套教程,目前已经是第二版修订了。这套教程定位于中级,是学完字典后的另一个专题讲解。数据库是数据处理的利器,教程中详细介绍了利用ADO连接ACCDB和EXCEL的方法…...
Linux架构篇、第1章_02源码编译安装Apache HTTP Server 最新稳定版本是 2.4.62
Linux_基础篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:源码编译安装Apache HTTP Server 最新稳定版本是 2.4.62 版本号: 1.0,0 作者: 老王要学习 日期: 2025.05.01 适用环境: Centos7 文档说明 本文…...
【Machine Learning Q and AI 读书笔记】- 03 小样本学习
Machine Learning Q and AI 中文译名 大模型技术30讲,主要总结了大模型相关的技术要点,结合学术和工程化,对LLM从业者来说,是一份非常好的学习实践技术地图. 本文是Machine Learning Q and AI 读书笔记的第3篇,对应原…...
Webug4.0靶场通关笔记08- 第11关万能密码登录(SQL注入漏洞)
目录 第13关 万能密码登录 1.打开靶场 2.源码分析 3.渗透方法1 4.渗透方法2 第13关 万能密码登录 本文通过《webug靶场第13关 万能密码登录》来进行渗透实战。 万能密码是利用 SQL 注入漏洞,构造出能够绕过登录验证的特殊密码字符串。通常,登录验…...