Linux TCP与Socket与IO多路复用(Epoll)

目录

一、背景

二、交互流程

2.1 数据流动

2.2 对象之间的关系

三、TCP

3.1 为什么需要三次握手

3.2 三次握手流程

3.3 三次握手后的产物

3.4 TCB

四、Socket

4.1 Java Socket和C++ Socket

4.2 Socket的本质

4.3 Socket和TCB的关系

4.4 通过文件描述符调用Socket的流程

五、Epoll

5.1 Epoll 结构

5.2 Epoll简要工作流程

5.3 Epoll代码

5.4 epoll_ctl过程

5.5 epoll_wait过程

5.6 水平触发(LT)与边缘触发(ET)

5.7 与Java NIO关系


一、背景

网络传输无处不在,正确理解网络传输的步骤有助于我们写出高性能的程序,也有助于我们解决程序中出现的问题。

二、交互流程

下面的不理解可以跳过 二、交互流程

2.1 数据流动

网卡 → DMA缓冲区 → 协议栈处理(IP/TCP) → TCB接收缓冲区 → recv() → 用户空间缓冲区
应用程序 → send() → 用户空间缓冲区 → 内核发送缓冲区 → 协议栈处理 → 网卡队列 → 网络

2.2 对象之间的关系

epollfd → struct file → struct socket → struct sock(TCB 的核心数据结构)

三、TCP

老生常谈的东西了,基本就是三次握手,但这次我会从操作系统角度,谈谈还干了什么,还包括三次握手的生成对象TCB

3.1 为什么需要三次握手

  • 第一次握手(SYN):服务端确认客户端的发送能力正常。

  • 第二次握手(SYN-ACK):客户端确认服务端的接收和发送能力正常。

  • 第三次握手(ACK):服务端确认客户端的接收能力正常。

  • 只有三次握手后,双方才能确保彼此能正常收发数据。

TCP三次握手发生在网络协议的传输层

3.2 三次握手流程

  1. 客户端发起连接

    • 用户调用connect(),内核发送SYN报文(设置初始序列号ISN)。

    • 创建TCB(传输控制块):内核为连接分配资源(如struct tcp_sock),初始化序列号(ISN)、窗口大小等参数,TCB状态变为SYN_SENT

  2. 服务端响应

    • DMA写入内存:网卡通过DMA直接将报文数据(包括TCP头、IP头、以太网帧等)写入内核预分配的接收缓冲区(如sk_buff结构)。

    • 触发软中断:随后网卡触发软中断(如Linux中的NET_RX_SOFTIRQ),通知内核有新的数据包需要处理。

    • 创建半连接
      内核协议栈解析SYN包,创建传输控制块(TCB),初始化连接状态(如序列号、窗口大小),并将连接状态设为SYN_RCVD(半开连接)。

    • 加入半连接队列(SYN Queue)
      该连接(TCB)被存入半连接队列,等待客户端确认。

    • 构造SYN-ACK包
      内核生成SYN-ACK响应(设置SYN和ACK标志,分配服务器初始序列号,确认号为客户端的序列号+1)。

    • DMA发送数据
      报文通过内核协议栈封装(TCP头→IP头→MAC头),存入网卡发送缓冲区,网卡通过DMA读取并发送。、

    • 启动重传定时器
      为防止丢包,内核启动定时器(默认约1秒),若未收到客户端的ACK,将重传SYN-ACK

  3. 最终确认

    • 客户端收到SYN-ACK后,状态变为ESTABLISHED,发送ACK

    • 服务端收到ACK后,做3-6步骤的操作

    • DMA与软中断:客户端的ACK包由网卡通过DMA写入内存,再次触发NET_RX_SOFTIRQ软中断。
    • 验证ACK:内核检查ACK的合法性(确认号是否为服务器序列号+1)。
    • 连接状态迁移:若验证通过,连接状态转为ESTABLISHED,并从半连接队列移至全连接队列(Accept Queue)。
    • 通知应用层:应用通过accept()系统调用从全连接队列中获取新连接,开始数据传输。

3.3 三次握手后的产物

主要是TCB和全连接队列

全连接队列:存放已建立连接但未被accept()取出的TCB

3.4 TCB

1. 存储连接状态信息

  • 连接状态:记录 TCP 状态机的当前阶段(如 ESTABLISHEDTIME_WAITSYN_RECEIVED 等)。

  • 端点信息:保存本地和远端的 IP 地址及端口号,唯一标识一个 TCP 连接。

  • 序列号和确认号:维护发送和接收数据的序列号(SEQ)和确认号(ACK),保证数据有序性和可靠性。

2. 完成数据传输

  • 每个 TCP 连接由 四元组 唯一标识:
    <本地 IP, 本地端口, 远端 IP, 远端端口>
    只要四元组中任意一个元素不同,内核就会视为 不同的 TCP 连接,并为其分配独立的 TCB(传输控制块)。

    当应用程序通过socket fd执行read()/write()时,内核会通过关联的TCB完成实际的数据传输

3. 缓冲区和数据管理

  • 发送缓冲区:暂存应用层待发送的数据,直到收到对方的确认。

  • 接收缓冲区:存储已接收但尚未被应用层读取的数据。

4. 流量控制与窗口管理

  • 滑动窗口:记录接收方的可用缓冲区大小(窗口大小),控制发送速率以避免接收方溢出。

  • 发送和接收窗口:跟踪当前允许发送的数据范围和已确认的数据范围。

5. 连接生命周期管理

  • 三次握手:跟踪 SYNSYN-ACKACK 的交换过程,完成连接建立。

  • 四次挥手:管理 FIN 包的交换,确保连接正常关闭或终止。

​​​​

四、Socket

4.1 Java Socket和C++ Socket

Java Socket 和 C++ Socket 在 Linux 上的本质是相同的,它们的底层实现均基于 Linux 内核提供的同一套 Socket 接口。无论是 Java 的 java.net.Socket 还是 C++ 的 sys/socket.h,最终都会通过系统调用(如 socket()bind()connect() 等)与内核交互。区别仅在于语言层面的封装和 API 设计。

既然是一样的,我们后面主要是在操作系统层级进行分析

4.2 Socket的本质

Socket是文件描述符的一种类型。文件描述符可以表示多种资源(文件、管道、Socket等),Socket是其中用于网络通信的一种。

通过文件描述符操作Socket:Socket的读写、关闭等操作均可通过其关联的文件描述符完成:

使用通用I/O函数:如 read()write()close()

使用Socket专用函数:如 send()recv()bind()connect() 等,这些函数需要文件描述符作为参数。

4.3 Socket和TCB的关系

一一对应关系

  • 每个 TCP Socket 对应一个 TCB

    • 当应用调用 socket() 创建一个 TCP Socket 后,内核会为这个 Socket 分配一个 TCB。

    • TCB 的生命周期与 Socket 绑定:Socket 被创建时 TCB 初始化,Socket 关闭时 TCB 释放。

  • Socket 是 TCB 的“用户态句柄”

    • 应用通过 Socket 文件描述符操作连接(如发送数据、接收数据、关闭连接),内核根据 Socket 找到对应的 TCB,修改其状态或触发协议行为(如重传、流量控制)。

  • 发送数据

    • 应用通过 send() 写入 Socket 的数据会暂存到 TCB 的发送缓冲区,TCP 协议根据 TCB 中的窗口和拥塞控制参数决定何时发送。

  • 接收数据

    • 内核将收到的数据存入 TCB 的接收缓冲区,应用通过 recv() 从 Socket 读取时,数据从接收缓冲区复制到用户空间。

每个 Socket 文件描述符(fd)在内核中关联到一个 struct socket 结构,该结构指向对应的 struct sock(即 TCB 的核心数据结构)。
关系链
fd → struct file → struct socket → struct sock(含接收缓冲区)。

4.4 通过文件描述符调用Socket的流程

对象级调用流程:

用户调用 read(sockfd, buf, len)↓
通过 sockfd 找到进程文件描述符表中的 struct file↓
struct file 的 f_op->read() 调用 Socket 的具体实现(如 sock_read())↓
内核通过 struct file 的 private_data 找到 struct socket↓
最终操作 struct sock 的接收缓冲区(sk_receive_queue)读取数据

接收流程:

  1. 数据到达内核
    数据包经过网卡接收、协议栈解析(IP/TCP 层处理)后,最终存入对应 TCP 连接的 TCB 接收缓冲区。该缓冲区位于内核空间,由内核管理。

  2. 应用程序调用 recv()
    当应用程序调用 recv(fd, buf, len, flags) 时:

    • fd(文件描述符):关联到特定的 Socket,而该 Socket 绑定到唯一的 TCB。

    • 内核操作
      内核从该 TCB 的接收缓冲区中,复制数据到用户提供的缓冲区 buf(位于用户空间)。

    • 数据移除
      被成功复制的数据会从 TCB 的接收缓冲区中移除,释放空间,接收窗口(rwnd)随之扩大。

  3. 返回结果

    • 若接收缓冲区中有数据,recv() 立即返回实际读取的字节数。

    • 若接收缓冲区为空:

      • 阻塞模式:进程休眠,直到新数据到达或连接关闭。

      • 非阻塞模式:立即返回错误码(如 EAGAIN 或 EWOULDBLOCK)。

Q:TCB接收缓冲区的作用是什么?

(1)数据暂存与排序

  • TCB 的接收缓冲区存储已通过 TCP 协议验证的、按序排列的数据。例如:

    • 若数据包乱序到达,内核会在缓冲区中等待缺失的序列号填补后,再通知应用层读取。

    • 若数据重复(如重传包),内核直接丢弃冗余数据。

(2)流量控制的基础

  • 接收缓冲区的剩余空间决定了 TCP 的 接收窗口(rwnd。此窗口通过 ACK 报文通告给发送方,控制其发送速率。

  • 若缓冲区满,rwnd=0,发送方暂停发送,避免数据被丢弃。

(3)内核与用户空间的桥梁

  • 数据从内核的 TCB 接收缓冲区到用户空间的 buf,必须通过 拷贝(如 copy_to_user())。
    (注:零拷贝技术如 splice() 或 sendfile() 可绕过此步骤,但常规 recv() 需要拷贝。)

发送流程:

1. 应用程序提交数据

  • send()/write() 系统调用
    应用程序将数据写入用户空间缓冲区,调用 send() 触发系统调用。

  • 数据拷贝到内核
    数据从用户空间拷贝到 TCB 的发送缓冲区(内核空间)。

2. 发送缓冲区管理

  • 分片与封装
    内核根据 MSS 将数据分片,添加 TCP/IP 头部,生成 TCP 段。

  • 发送窗口约束
    仅允许发送窗口内的数据(已发送未确认数据 + 可发送未发送数据 ≤ 发送窗口大小)。

3. 协议栈处理

  • 滑动窗口与序列号
    每个 TCP 段携带序列号(seq),接收方据此确认数据顺序。

  • 重传机制
    已发送但未确认的 TCP 段保留在发送缓冲区的重传队列中,超时(RTO)或收到重复 ACK 时触发重传。

4. 网络层与网卡发送

  • IP 层处理
    添加 IP 头部,路由选择,分片(若超过 MTU)。

  • 网卡队列
    TCP 段交给网卡驱动,存入发送队列(如 tx_ring),通过 DMA 发送到网络。

五、Epoll

经过上面的介绍,我们对文件描述符、Socket、TCP有相应的了解,也明白了文件描述符是如何操作网络连接的。

那假如有大量的网络连接时,我们该如何管理呢?

目前主流的方法是I/O 多路复用,即单线程/少量线程监听多个socket事件

Epoll是Linux下的一种I/O多路复用机制,用于高效地处理大量文件描述符

5.1 Epoll 结构

epoll 的实现依赖于两个核心数据结构:红黑树(Red-Black Tree) 和 就绪队列(Ready List)。它们共同协作,使得 epoll 能够高效地管理海量文件描述符(FD)的 I/O 事件。

红黑树是 epoll 实例中用于 存储所有被监控的文件描述符(FD) 的数据结构。每个通过 epoll_ctl 添加的 FD(如套接字、管道等)都会在红黑树中注册为一个节点。

设计原因

  • 高效动态操作:红黑树是一种自平衡二叉搜索树,插入、删除、查找的时间复杂度均为 O(log N),适合频繁增删 FD 的场景(例如 Web 服务器处理大量短连接)。

  • 快速定位 FD:当某个 FD 发生事件(如可读、可写)时,内核需要快速找到该 FD 的监控信息(例如用户关注的事件类型),红黑树的特性确保了这一过程的效率。

  • 避免重复注册:红黑树的唯一性保证同一个 FD 不会被重复添加,避免资源浪费。

就绪队列是 epoll 实例中用于 临时存储已触发事件的 FD 的链表结构。当某个被监控的 FD 发生事件(例如套接字收到数据),内核会将该 FD 添加到就绪队列中。

设计原因

  • 快速事件通知:用户调用 epoll_wait 时,内核无需遍历所有被监控的 FD,而是直接检查就绪队列,时间复杂度接近 O(1)

  • 事件去重与合并:如果同一 FD 的多个事件连续触发(如多次可读),就绪队列会合并这些事件,避免重复通知。

  • 支持边缘触发(Edge-Triggered, ET)模式:在 ET 模式下,事件仅在状态变化时触发一次,就绪队列确保事件不会被遗漏。

工作流程

  1. 事件触发:当某个 FD 发生事件(如数据到达),内核调用与该 FD 关联的回调函数。

  2. 加入就绪队列:回调函数将 FD 插入就绪队列,并标记触发的事件类型。

  3. 用户获取事件:用户调用 epoll_wait 时,内核将就绪队列中的事件拷贝到用户空间,并清空队列(取决于触发模式)。

5.2 Epoll简要工作流程

  1. 创建epoll实例

    • 使用 epoll_create1() 创建epoll文件描述符。

  2. 创建并配置监听Socket

    • 创建TCP Socket,设置为非阻塞模式,绑定地址并开始监听。

  3. 注册监听Socket到epoll

    • 通过 epoll_ctl() 将监听Socket加入epoll监控,关注 EPOLLIN 事件(新连接事件)。

  4. 事件循环

    • 使用 epoll_wait() 阻塞等待事件发生。

    • 遍历就绪事件列表,处理不同类型的事件:

      • 新连接:接受连接,将新Socket加入epoll监控。

      • 数据可读:读取数据并处理,必要时注册 EPOLLOUT 事件准备写入。

      • 数据可写:发送数据,完成后取消 EPOLLOUT 监控。

      • 错误/关闭:移除并关闭Socket。

  5. 清理资源

    • 关闭所有Socket和epoll实例。

5.3 Epoll代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <fcntl.h>#define MAX_EVENTS 10
#define PORT 8080
#define BUFFER_SIZE 1024// 设置文件描述符为非阻塞模式
void set_nonblocking(int fd) {int flags = fcntl(fd, F_GETFL, 0);fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}int main() {int listen_fd, epoll_fd, nfds;struct epoll_event ev, events[MAX_EVENTS];struct sockaddr_in addr;// 1. 创建监听Socketlisten_fd = socket(AF_INET, SOCK_STREAM, 0);if (listen_fd == -1) {perror("socket");exit(EXIT_FAILURE);}// 绑定并监听memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_addr.s_addr = htonl(INADDR_ANY);addr.sin_port = htons(PORT);if (bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr)) {perror("bind");close(listen_fd);exit(EXIT_FAILURE);}if (listen(listen_fd, SOMAXCONN)) {perror("listen");close(listen_fd);exit(EXIT_FAILURE);}set_nonblocking(listen_fd); // 非阻塞模式// 2. 创建epoll实例epoll_fd = epoll_create1(0);if (epoll_fd == -1) {perror("epoll_create1");close(listen_fd);exit(EXIT_FAILURE);}// 3. 注册监听Socket到epollev.events = EPOLLIN; // 关注可读事件ev.data.fd = listen_fd;if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev)) {perror("epoll_ctl: listen_fd");close(listen_fd);exit(EXIT_FAILURE);}printf("Server listening on port %d...\n", PORT);// 4. 事件循环while (1) {nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);if (nfds == -1) {perror("epoll_wait");break;}for (int i = 0; i < nfds; i++) {int fd = events[i].data.fd;// 处理新连接if (fd == listen_fd) {struct sockaddr_in client_addr;socklen_t addrlen = sizeof(client_addr);int client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &addrlen);if (client_fd == -1) {perror("accept");continue;}set_nonblocking(client_fd); // 新Socket设为非阻塞ev.events = EPOLLIN | EPOLLET; // 边缘触发模式(可选)ev.data.fd = client_fd;if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev) == -1) {perror("epoll_ctl: client_fd");close(client_fd);}printf("New connection: fd %d\n", client_fd);// 处理数据可读} else if (events[i].events & EPOLLIN) {char buffer[BUFFER_SIZE];ssize_t nread = recv(fd, buffer, BUFFER_SIZE, 0);if (nread > 0) {printf("Received from fd %d: %.*s\n", fd, (int)nread, buffer);// 回显数据(示例)send(fd, buffer, nread, 0);} else if (nread == 0 || (nread == -1 && errno != EAGAIN)) {// 关闭连接printf("Closing fd %d\n", fd);epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL);close(fd);}}// 处理其他事件(如EPOLLOUT、EPOLLERR等)}}// 清理资源close(listen_fd);close(epoll_fd);return 0;
}

5.4 epoll_ctl过程

当通过 epoll_ctl(EPOLL_CTL_ADD) 将一个fd(如Socket)添加到epoll实例时,内核会执行以下操作:

(1) 注册回调函数到fd的等待队列

  1. 为fd创建epoll条目(epitem:包含fd的信息和关注的事件(如EPOLLIN)。

  2. 将回调函数 ep_poll_callback 注册到fd的等待队列

    • 调用fd的 poll 方法(如Socket的 sock_poll)。

    • poll 方法将 ep_poll_callback 添加到fd的等待队列中。

(2) 事件触发时的回调流程

  1. 数据到达触发硬件中断:网卡接收数据后,内核协议栈处理数据并标记Socket为可读。

  2. 唤醒等待队列

    • 内核调用Socket等待队列中的回调函数 ep_poll_callback

    • ep_poll_callback 将对应的fd(封装为 epitem)加入epoll的就绪队列(rdllist)。

  3. 通知用户程序

    • 如果用户程序阻塞在 epoll_wait,内核唤醒该线程,使其从 epoll_wait 返回并处理就绪事件。

5.5 epoll_wait过程

epoll_wait 是 epoll 机制的核心函数,它负责 等待并获取已就绪的事件

步骤 1:进入内核态

  • 用户程序调用 epoll_wait 时,会从用户态切换到内核态。

  • 内核访问 epoll 实例的数据结构(包括 红黑树 和 就绪队列)。

步骤 2:检查就绪队列

  • epoll 实例维护一个 就绪队列(ready list),其中保存所有已触发事件的fd。

  • 如果就绪队列非空,内核直接从中取出事件,填充到用户空间的 events 数组。

  • 如果队列为空,且 timeout=-1,线程阻塞在此处,直到有新事件到来或信号中断。

步骤 3:监控fd状态(若就绪队列为空)

  • 若就绪队列为空,内核通过 回调机制 监控所有注册的fd:

    • 当某个fd发生事件(如socket接收数据),内核会将该fd添加到就绪队列。

    • 这一过程由内核的 事件驱动机制 实现,无需轮询所有fd,效率极高。

步骤 4:返回就绪事件

  • 将就绪队列中的事件复制到用户空间的 events 数组。

  • 返回就绪事件的数量 nfds,用户程序通过遍历 events[0..nfds-1] 处理事件。

  • 如果此时有线程阻塞在 epoll_wait,内核会唤醒该线程

5.6 水平触发(LT)与边缘触发(ET)

行为水平触发(LT)边缘触发(ET)
触发时机只要缓冲区有数据/可写,持续触发仅在缓冲区状态变化时触发一次(如新数据到达)
数据未读尽的后果下次 epoll_wait 继续报告事件不再触发事件,可能导致数据滞留
编程复杂度较低(无需一次性处理所有数据)较高(需循环读写至 EAGAIN
适用场景简单场景、小数据量高性能场景、需精细化控制

5.7 与Java NIO关系

Java NIO 在不同的操作系统和 JDK 版本中确实会使用不同的底层实现,其中在 Linux 系统上,Java NIO 的 Selector(多路复用机制)默认是基于 epoll 实现的

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

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

相关文章

字节跳动旗下火山引擎都覆盖哪些领域

首先&#xff0c;我需要确认火山引擎的主要业务范围。根据之前的资料&#xff0c;火山引擎是字节跳动的企业技术服务平台&#xff0c;可能包括云服务、人工智能、大数据分析等。不过需要更详细的信息&#xff0c;比如具体的产品和服务&#xff0c;覆盖的行业等。 接下来&#x…

如何配置jmeter做分布式压测

问&#xff1a;为何需要做分布式 答&#xff1a;当我们本地机器jmeter进行压测时&#xff0c;单台JMeter机器通常无法稳定生成2000 QPS&#xff08;受限于CPU、内存、网络带宽&#xff09;&#xff0c;本地端口耗尽&#xff1a;操作系统可用的临时端口&#xff08;Ephemeral P…

【算法提升】牛牛冲钻五 最长无重复子数组 重排字符串 one_day

算法提升 1.牛牛冲钻五1.2 解析 2.最长无重复子数组2.1解析 3.重排字符串3.1解析 1.牛牛冲钻五 1.2 解析 后面的数据要根据前面两个的状态来确定&#xff0c;我的做法是使用动态规划的方式 #include<iostream> #include<string> #include<vector> using n…

数学建模MathAI智能体-2025电工杯A题实战

题目&#xff1a; 光伏电站发电功率日前预测问题 光伏发电是通过半导体材料的光电效应&#xff0c;将太阳能直接转化为电能的技术。光伏电站是由众多光伏发电单元组成的规模化发电设施。 光伏电站的发电功率主要由光伏板表面接收到的太阳辐射总量决定&#xff0c;不同季节太阳…

VR 展厅开启一场穿越时空的邂逅​

在文化艺术领域&#xff0c;VR 展厅宛如一扇通往奇妙世界的大门&#xff0c;让观众得以突破时间与空间的枷锁&#xff0c;以一种前所未有的沉浸式体验&#xff0c;与历史文化和艺术作品展开亲密无间的互动。博物馆&#xff0c;作为承载着厚重历史文化的璀璨宝库&#xff0c;长久…

linux中使用make clean重新编译

是的&#xff0c;在编译完成后&#xff0c;你可以通过以下方式清除之前的编译结果并重新编译&#xff1a; 方法 1&#xff1a;直接删除 build 目录&#xff08;推荐&#xff09; 这是最彻底的清理方式&#xff0c;适用于需要完全重新配置或解决构建问题的情况。 # 进入项目根…

【Linux】的火墙管理及优化

目录 iptables与firewalld服务 iptables的三表五链 iptables的安装和启用 iptables命令的格式及常用参数 命令格式 常用参数 编写规则示例 firewalld的域 firewalld的启用 firewalld-cmd命令的常用参数 firewalld的高级规则 firewalld的地址伪装与端口转发 iptable…

古文时空重构:当AI把课本诗词做成4D电影

当青铜编钟声由远及近&#xff0c;AI生成的水墨粒子逐渐凝聚成标题 当苔痕在石阶上悄然蔓延时&#xff0c;你听见刘禹锡笔下的呼吸了吗&#xff1f; 当镜头突然穿透墨迹&#xff0c;3D古卷如星河铺展&#xff01; 当AI把课本诗词做成4D电影&#xff0c;这样的视频流量会不会高…

自动生成图标小程序(iOS)

续上篇《iOS应用程序开发(图片处理器&#xff09;》 这是一个图片浏览器和处理器&#xff0c;增加一些功能&#xff0c;可以自动生成小图标。 (This is a picture viewer and editor.You can add some functions,generate the icon automatically.You can select the object …

Netty应用:从零搭建Java游戏服务器网络框架

在游戏开发领域,服务器网络框架是连接玩家与游戏世界的桥梁,其稳定性和高效性直接影响玩家的游戏体验。本文将详细介绍如何使用Java语言和Netty框架,搭建一个兼具TCP和UDP协议支持的游戏服务器网络框架,并配套开发客户端,助你快速掌握游戏网络开发的核心技术。 1.项目概览…

SpringBoot-13-多表查询之一对一查询association

文章目录 1 mysql数据库1.1 account账户表1.2 user用户表2 实体类2.1 model/Account.java2.2 model/User.java3 mapper3.1 AccountToUserMapper.java3.2 AccountToUserMapper.xml3.2.1 mapper3.2.2 resultMap3.2.3 association3.2.4 select4 AccountController.java5 测试5.1 有…

Python如何赋能自动驾驶地图构建?从点云处理到实时导航

Python如何赋能自动驾驶地图构建?从点云处理到实时导航 自动驾驶的核心是什么?毫无疑问,精准的地图 是整个系统的灵魂。没有高精度地图,自动驾驶汽车就如同在迷雾中航行。而 Python,凭借其强大的数据处理能力和丰富的开源生态,正在成为自动驾驶地图构建的关键工具。 今…

QT之巧用对象充当信号接收者

备注&#xff1a;以下仅为演示不代表合理性&#xff0c;适合简单任务&#xff0c;逻辑简单、临时使用&#xff0c;可保持代码简洁&#xff0c;对于复杂的任务应创建一个专门的类来管理信号和线程池任务. FileScanner类继承QObject和QRunnable&#xff0c;扫描指定目录下的文件获…

Transformer,多头注意力机制 隐式学习子空间划分

Transformer,多头注意力机制 隐式学习子空间划分 在Transformer中,多头注意力机制天然支持隐式学习子空间划分——每个注意力头可以专注于输入的不同方面(如语义、句法、位置关系等),从而隐式形成多个子空间。 一、核心思路:将多头注意力视为隐式子空间 原理 Transfo…

java基础(继承)

什么是继承 继承好处 提高代码的复用性 继承注意事项 权限修饰符 单继承、Object类 冲突&#xff1a; 方法重写 扩展&#xff1a; 其实我们不想看地址&#xff0c;地址看来没用&#xff0c;我们是用来看对象有没有问题 重写toString: 比如这个如果返回的是地址值&#xff0c;…

【每日渲美学】3ds Max橱柜材质教程:厨房高光烤漆、木纹、亚克力、亚光板材渲染优化指南

在室内可视化项目中&#xff0c;厨房往往是一个集中展现材质表现力与光影质感的关键区域。橱柜作为厨房空间的视觉主体&#xff0c;其材质选择与渲染设置不仅影响整体空间的风格呈现&#xff0c;也对渲染效率提出更高要求。 本期「每日渲美学」&#xff0c;我们聚焦3ds Max环境…

Python Day34

Task&#xff1a; GPU训练及类的call方法 1.CPU性能的查看&#xff1a;看架构代际、核心数、线程数 2.GPU性能的查看&#xff1a;看显存、看级别、看架构代际 3.GPU训练的方法&#xff1a;数据和模型移动到GPU device上 4.类的call方法&#xff1a;为什么定义前向传播时可以直接…

HTTP协议版本的发展(HTTP/0.9、1.0、1.1、2、3)

目录 HTTP协议层次图 HTTP/0.9 例子 HTTP/1.0 Content-Type 字段 Content-Encoding 字段 例子 1.0版本存在的问题&#xff1a;短链接、队头阻塞 HTTP/1.1 Host字段 Content-Length 字段 分块传输编码 1.1版本存在的问题 HTTP/2 HTTP/2数据传输 2版本存在的问题…

开源 OIDC(OpenID Connect)身份提供方(IdP)、iam选型

文章目录 开源 OIDC(OpenID Connect)身份提供方(IdP)、iam选型主流开源 OIDC(OpenID Connect)身份提供方(IdP)zitadeldexory开源 OIDC(OpenID Connect)身份提供方(IdP)、iam选型 主流开源 OIDC(OpenID Connect)身份提供方(IdP) 当前主流的**开源 OIDC(OpenI…

第三十二天打卡

作业&#xff1a;参考pdpbox官方文档中的其他类&#xff0c;绘制相应的图&#xff0c;任选即可 1. 安装并导入库 确保安装与文档版本一致的 pdpbox&#xff08;此处以 0.3.0 为例&#xff09;&#xff1a; bash 复制 下载 pip install pdpbox0.3.0 导入所需库&#xff1a…