深入理解网络 I/O 多路复用:Epoll

news2024/12/23 20:58:24

在这里插入图片描述

🔭 嗨,您好 👋 我是 vnjohn,在互联网企业担任 Java 开发,CSDN 优质创作者
📖 推荐专栏:Spring、MySQL、Nacos、Java,后续其他专栏会持续优化更新迭代
🌲文章所在专栏:网络 I/O
🤔 我当前正在学习微服务领域、云原生领域、消息中间件等架构、原理知识
💬 向我询问任何您想要的东西,ID:vnjohn
🔥觉得博主文章写的还 OK,能够帮助到您的,感谢三连支持博客🙏
😄 代词: vnjohn
⚡ 有趣的事实:音乐、跑步、电影、游戏

目录

  • 前言
  • Epoll 函数
    • EPOLL_CREATE
    • EPOLL_CTL
    • EPOLL_WAIT
    • epoll_event 数据结构
    • 边沿触发、水平触发
    • 小结
  • Epoll 内核源码
  • 图解分析
    • epoll VS select/poll 工作原理
    • epoll VS select/poll 日志追踪
    • Epoll 优势之处
  • 总结

前言

Unix/Linux 下可用的 I/O 模型有以下五种:

  1. 阻塞式 I/O
  2. 非阻塞式 I/O
  3. I/O 复用(select、poll)
  4. 信号驱动式 I/O(SIGIO)
  5. 异步 I/O

在 Linux 中操作内核时,所有的无非三种操作,分别是输入、输出、报错输出

0-输入
1-输出
2-报错输出

一个输入操作通常包括两个不同的阶段:

  • 等待数据准备好
  • 从内核向进程复制数据

对于一个套接字(Socket)的输入操作,第一步通常涉及等待数据从网络中;当所等待分组到达时,它被复制到内核中的某个缓冲区,第二步就是把数据从内核缓冲区复制到应用进程缓冲区

Epoll 函数

在 Epoll 多路复用模型中,最主要的是涉及到了三个系统函数指令,分别是:epoll_create、epoll_ctl、epoll_wait

EPOLL_CREATE

借助:man 2 epoll_create 帮助文档来学习该函数

通过 epoll_create、epoll_create1 函数,打开 epoll 文件描述符

int epoll_create(int size);
int epoll_create1(int flags);

epoll_create 返回指向新的 Epoll 实例的文件描述符,该文件描述符用于对 epoll 接口的所有后续调用,当不再需要时,将调用 close 函数关闭 epoll_create 返回的文件描述符,当引用 epoll 实例的所有文件描述符都关闭时,内核将销毁该实例并释放相关资源以供资源重用

epoll_create1:若 flags 为 0,除了删除过时的 size 参数之外,epoll_create1 与 epoll_create 是相同的

如果指向新的 epoll 实例描述符成功的话,这些系统调用函数将会返回一个非负数的文件描述符,若出现错误,将返回 -1,并设置 errno 来指示错误.

EPOLL_CTL

借助:man 2 epoll_ctl 帮助文档来学习该函数

通过 epoll_ctl 来承担 epoll 描述符的控制接口

int epoll_ctl(int epfd, int op, 
				int fd, struct epoll_event *event);

这个系统调用对文件描述符 epfd 引用的 epoll 实例执行控制操作,它请求对目前文件描述符 fd 执行 op 操作

op 参数可选值如下:

  1. EPOLL_CTL_ADD:在文件描述符 epfd 引用的 epoll 实例上注册目标文件描述符 fd,并关联事件,内部文件链接到 fd
  2. EPOLL_CTL_MOD:更改与目标文件描符 fd 关联的事件
  3. EPOLL_CTL_DEL:从 epfd 引用的 epoll 实例中删除或注销目标文件描述符 fd,该事件被忽略,并且可以为空

EPOLL_WAIT

借助:man 2 epoll_wait 帮助文档来学习该函数

int epoll_wait(int epfd, struct epoll_event *events,
                      int maxevents, int timeout);

epoll_wait 系统调用等待文件描述符 epfd 引用的 epoll 实例,事件指向的内存区域将包含调用者可用的事件,epoll_wait 最多返回 maxevents 个事件,其参数必须大于 0

timeout 函数指定 epoll_wait 将阻塞的最小毫秒数,若指定 timeout 为 -1 会导致 epoll_wait 无限期阻塞,而指定 timeout 为 0 会导致 epoll_wait 立即返回,即使没有事件可用

在 epoll_wait 调用时,在给定的 timeout 时间内,当在监控的所有 fd 中有事件发生时,就返回用户态的进程

epoll_event 数据结构

typedef union epoll_data {
    void    *ptr;
    int      fd;
    uint32_t u32;
    uint64_t u64;
} epoll_data_t;

struct epoll_event {
    uint32_t     events;    /* Epoll events */
    epoll_data_t data;      /* User data variable */
};

作为 epoll 中重要返回的数据结构,每个返回结构的数据将包含用户使用:epoll_ctl「EPOLL_CTL_ADD、EPOLL_CTL_MOD」设置的相同数据,而 events 成员将包含返回的事件位字段

边沿触发、水平触发

Epoll 提供两种事件接口:边沿触发(ET:edge-triggered)、水平触发(LT:level-triggered)

边沿触发

  • socket 接收缓冲区状态变化时触发读事件,即空的接收缓冲区刚接收到数据时触发读事件
  • socket 发送缓冲区状态变化时触发写事件,即满的缓冲区刚空出空间时触发读事件

水平触发

  • socket 接收缓冲区不为空时,有数据可读,读事件一直触发
  • socket 发送缓冲区不满,可以继续写入数据,写事件一直触发

使用 EPOLLET 标志的应用程序应该使用非阻塞文件描述符,以避免在处理多个文件描述符的任务中出现阻塞读写。建议使用 epoll 作为边缘触发(EPOLLET)接口的方法如下:

  1. 使用非阻塞文件描述符
  2. 在 read 或 write 之后等待事件返回 EAGAIN

例如,两条线分别有数据 ABC、DEF,水平触发的处理顺序:ADBECF,边缘触发的处理顺序:ABCDEF

Nginx、Redis 都使用了 Epoll 多路复用模型

Nginx 使用的是边缘触发 ET、Redis 使用的是水平触发 LT

再举例而言说明两者的区别:你有急事打电话找人,如果对方一直不接,那你只有一直打,直到他接电话为止,这就是 LT 模式;如果不急,电话打过去对方不接,那就等有空再打,这就是 ET 模式

小结

从调用方式可以看出 epoll 对比 select/poll 优越之处,因为 每次调用时都要传递你所要监控的所有 socket 给 select/poll 进行系统调用,这也就意味着需要将用户态的 socket 列表 copy 到内核态,若以万计数的 socket 会导致每次都要 copy 几十或几百 KB 的内存到达内核态,非常的低效,而当我们调用 epoll_wait 时就相当于以往调用 select/poll,但这此时却不用传递 socketfd 给到内核,因为内核通过 epoll_ctl 函数已经拿到了所要监控的 socketfd 列表

实际在你调用 epoll_create 后,内核就已经在开始准备帮你存储要监控的 socket 了,每次调用 epoll_ctl 只是在往内核的数据结构 > 红黑树,塞入新的 socketfd 罢了

Epoll 内核源码

#define MAX_EVENTS 10
struct epoll_event ev, events[MAX_EVENTS];
int listen_sock, conn_sock, nfds, epollfd;

/* Set up listening socket, 'listen_sock' (socket(),
   bind(), listen()) */

epollfd = epoll_create(10);
if (epollfd == -1) {
    perror("epoll_create");
    exit(EXIT_FAILURE);
}

ev.events = EPOLLIN;
ev.data.fd = listen_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
    perror("epoll_ctl: listen_sock");
    exit(EXIT_FAILURE);
}

for (;;) {
    nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
    if (nfds == -1) {
        perror("epoll_pwait");
        exit(EXIT_FAILURE);
    }

    for (n = 0; n < nfds; ++n) {
        if (events[n].data.fd == listen_sock) {
            conn_sock = accept(listen_sock,
                            (struct sockaddr *) &local, &addrlen);
            if (conn_sock == -1) {
                perror("accept");
                exit(EXIT_FAILURE);
            }
            setnonblocking(conn_sock);
            ev.events = EPOLLIN | EPOLLET;
            ev.data.fd = conn_sock;
            if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,
                        &ev) == -1) {
                perror("epoll_ctl: conn_sock");
                exit(EXIT_FAILURE);
            }
        } else {
            do_use_fd(events[n].data.fd);
        }
	}
}

socket()、bind()、listen() 是所有的 I/O 模型都必须要经过的操作

  • epollfd=epoll_create(10):创建一个 epoll 文件描述符 > epfd,用于执行后续所有的 epoll 操作
  • 若 epoll_create 函数返回 -1,代表操作失败,失败的原因可能是内核中文件描述符的大小超出了限制
  • 通过 epoll_ctl 函数将新获取的 socket 套接字放入到 epoll 红黑树中,在内核会单独为 epoll 开辟一些数据结构,存放这些 socket 信息
  • EPOLL_CTL_ADD 代表新增 op 操作,成功返回 0,失败则返回 -1
  • 第一个死循环进行 wait 阻塞等待,主要是调用 epoll_wait 函数,类似 select/poll 的操作,从红黑树中复制出来的链表有状态的文件描述符,将拿到的结果存放在 events 数组中

epoll_wait(epollfd, events, MAX_EVENTS, -1):第四个参数的 -1 代表不超时

  • 当拿到有结果的 events 数组以后,对这些有状态的文件描述符进行遍历,若当前文件描述符等于传入的文件描述符,那么则对当前描述符进行 accept 函数调用,将套接字对应的 IP、Port 进行绑定,成功则返回文件描述符,否则返回 -1

此时,代表有新的客户端连接了,需要进行 accept 监听,生成一个新的 socketfd,并且设置为非阻塞运行的方式,调用 epoll_ctl 将新的 socketfd 放入到红黑树中

  • 若当前文件描述符不等于传入的文件描述符,那么就使用已被监听的文件描述符中的数据

通过命令:cat /proc/sys/fs/epoll/max_user_watches,可以查看系统上所有 epoll 实例注册的文件描述符总数的最大限制

在这里插入图片描述

图解分析

其实 Epoll 的模型大致上和 select/poll 模型大致上是一样的,只不过它们额外做的处理工作不一样而已,下面具体来介绍

epoll VS select/poll 工作原理

在这里插入图片描述

如上图,所有的 I/O 模型,都会经过三个函数的调用:socket -> bind -> listen,然后 accept 等待客户端建立连接再分配新的 fd 文件描述符!

经历过三个函数调用以后,epoll、select/poll 做以下对比:

  1. 经过通用的函数调用以后,在 Epoll 中会调用 epoll_create 函数生成 fd7,再调用 epoll_ctl 将应用程序与客户端之间新创建的文件描述符放入到内核中维护的红黑树数据结构中,当有客户端有数据 Send-Queue 发送到网卡,然后会到达对应文件描述符 fd4「socket 函数生成的文件描述符」Buffer 缓冲区中,在此时,epoll 对比 select/poll 多做了一下延伸处理工作:将红黑树里有状态的文件描述符 fds 拷贝到链表中,随即在调用 epoll_wait 函数就可以从链表中取出所有有状态(R/W)的 fds
  2. 经过通用的函数调用以后,在 select/poll 中会通过 socket 生成的文件描述符 fd 到内核循环遍历全量的文件描述符以后,返回哪些有状态(R/W)的 fds,当我们在应用程序什么时候调用 select 方法就会触发一次全量的 fds 遍历
  3. 无论是 epoll 还是 select/poll,即使不调用内核,内核也会随着中断的处理机制完成所有 fd 文件描述符的状态设置,而 Epoll 就是在中断处理时多做了一件事情:将红黑树里有状态的 fd 拷贝到了链表中,当应用程序要取来进行处理时,直接取链表里面的 fds 即可(O(1)),而不需要像 select/poll 那样去循环遍历(O(n)

epoll VS select/poll 日志追踪

可以通过 strace 命令来追踪 epoll、select/poll 内核底层的源码是如何处理的,Java 代码与上一篇讲解的 select/poll 多路复用是一致的。

select/poll

运行命令:

strace -ff -o poll java -Djava.nio.channels.spi.SelectorProvider=sun.nio.ch.PollSelectorProvider SelectMultiplexingSocketThread

JVM native 分配了数组来保存 fd 信息,strace 源码追踪:

socket(AF_INET6, SOCK_STREAM, IPPROTO_IP) = 4
# 如 Java 代码:server.configureBlocking(false)
fcntl(4, F_SETFL, O_RDWR|O_NONBLOCK)    = 0

bind(4, {sa_family=AF_INET6, sin6_port=htons(8090), inet_pton(AF_INET6, "::", &sin6_addr), ...) = 0

listen(4, 50)
# 返回 = 1,代表一个 fd 有事件到来
# 返回 = -1,代表非阻塞情况下,没有事件
# 返回 = 0,代表调用超时且没有事件返回
ppoll([{fd=5, events=POLLIN}, {fd=4, events=POLLIN}], 2, NULL, NULL, 0) = 1 ([{fd=4, revents=POLLIN}])
# 新的客户端连接进来,分配的是 fd7
accept(4, {sa_family=AF_INET6, sin6_port=htons(60292), inet_pton(AF_INET6, "::1", ...) = 7
fcntl(7, F_SETFL, O_RDWR|O_NONBLOCK)    = 0

epoll

strace -ff -o epoll java -Djava.nio.channels.spi.SelectorProvider=sun.nio.ch.EPollSelectorProvider SelectMultiplexingSocketThread

运行命令:

socket(AF_UNIX, SOCK_STREAM, 0)         = 4
fcntl(4, F_SETFL, O_RDWR|O_NONBLOCK)    = 0
bind(4, {sa_family=AF_INET6, sin6_port=htons(8090), inet_pton(AF_INET6, "::",...) = 0
listen(4, 50) 
# 创建了 epfd 7
epoll_create1(0)                        = 7
# 将 socket 返回的文件描述符放入红黑树中
epoll_ctl(7, EPOLL_CTL_ADD, 4, {EPOLLIN, {u32=4, u64=545460846596}}) = 0
# 未设置超时时间会一直阻塞,设置了超时时间,在时间内无事件会返回 0
epoll_pwait(7,
# 新的客户端连接进来,分配 fd8
accept(4, {sa_family=AF_INET6, sin6_port=htons(60294), inet_pton(AF_INET6, "::1", &sin6_addr), ...) = 8
fcntl(8, F_SETFL, O_RDWR|O_NONBLOCK)    = 0
# 将新客户端 fd8 天假到红黑树中
epoll_ctl(7, EPOLL_CTL_ADD, 8, {EPOLLIN, {u32=8, u64=545460846600}}) = 0
# 继续循环 epoll_wait
epoll_pwait(7,

在 Java NIO 包下 Selector 通过一套代码在底层实现了 select/poll、epoll 两种 I/O 模型,对应的实现类分别是:sun.nio.ch.PollSelectorProvider、sun.nio.ch.EPollSelectorProvider

Epoll 优势之处

Epoll 高效在于:当我们调用 epoll_ctl 往内核塞入百万个 socket 时,epoll_wait 仍然可以飞快的返回,并会有效的将有发生事件的 socket 给到应用程序;这主要是在调用 epoll_create 时,内核除了在文件系统里建了 epfd,还在内核中建立了一个红黑树结构用于存储以后 epoll_ctl 传来的 socket 以外,还会再建立一个链表,用于存储哪些准备就绪的事件,当 epoll_wait 调用时,只需要仅仅观察这个链表有没有数据即可,有数据就返回,无数据就 sleep 等待 timeout 时间到,所以,epoll_wait 非常快.

每次都是 O(1) 的操作,不会在内核中发生循环遍历寻找的动作,以及也会减少用户态、内核态之间的大额数据交互,减少了资源的浪费及无效时间的行为.

总结

该篇博文主要介绍的就是比较重要比较核心的多路复用模型 Epoll,先简略说明 Epoll 重要的三大函数:epoll_create、epoll_ctl、epoll_wait,在其中说到了 Epoll 事件接口:边沿触发(ET:edge-triggered)、水平触发(LT:level-triggered),提及到了 Epoll 内核中关键的源码部分,使用三大函数巧妙结合起来实现 epoll 高效的多路复用,在底层采用红黑树结构存储所有的 socket fd 信息,采用链表结构存储所有有事件状态的 socket fd 信息,最后图解+日志追踪分析了 Epoll 与 select/poll 之间的区别以及介绍 Epoll 优势之处,希望您能够喜欢,感谢三连支持!

参考文献:

  1. 《UNIX网络编程 卷1:套接字联网API(第3版)》— [美] W. Richard Stevens Bill Fenner Andrew M. Rudoff
  2. Epoll原理 — 千寻
  3. Epoll — dreamgoing

学习帮助文档:

  • man pages:yum install man
  • pthread man pages:yum -y install man-pages

🌟🌟🌟愿你我都能够在寒冬中相互取暖,互相成长,只有不断积累、沉淀自己,后面有机会自然能破冰而行!

博文放在 网络 I/O 专栏里,欢迎订阅,会持续更新!

如果觉得博文不错,关注我 vnjohn,后续会有更多实战、源码、架构干货分享!

推荐专栏:Spring、MySQL,订阅一波不再迷路

大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下文见!

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

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

相关文章

Visual Studio调试技巧合集

Visual Studio调试技巧合集 1 如何同一个项目运行不同main文件&#xff1f; 1 如何同一个项目运行不同main文件&#xff1f; &#xff08;1&#xff09;移动鼠标到需要关掉调试的文件&#xff0c;点击右键属性–常规–从生成中排除–是–确定&#xff0c;即显示“-”号排除&am…

python实现形态学建筑物指数MBI提取建筑物及数据获取

前言 形态学建筑物指数MBI通过建立建筑物的隐式特征和形态学算子之间的关系进行建筑物的提取[1]。 原理 上图源自[2]。 实验数据 简单找了一张小图片&#xff1a; test.jpg 代码 为了支持遥感图像&#xff0c;读写数据函数都是利用GDAL写的。 import numpy as np import …

【数据结构(十一·多路查找树)】B树、B+树、B*树(6)

文章目录 1. 二叉树 与 B树1.1. 二叉树存在的问题1.2. 多叉树 的概念1.3. B树 的基本介绍 2. 多叉树——2-3树2.1. 基本概念2.2. 实例应用2.3. 其他说明 3. B 树、B树 和 B*树3.1. B树 的介绍3.2. B树 的介绍3.2. B*树 的介绍 1. 二叉树 与 B树 1.1. 二叉树存在的问题 二叉树…

【FPGA/verilog -入门学习7】 条件判断if与分支判断case语句的语法介绍

需求 使用if 和case 产生格雷码 / /*条件判断if与分支判断case语句的语法介绍 需求 使用if 和case 产生格雷码*/ / timescale 1ns/1ps module vlg_design(input [3:0] i_data, output reg [3:0] o_data,output reg [3:0] o_datac);always (*) begin if (4b0000 i_data) o_d…

ros的slam建图和导航(含工作空间)

工作空间的结构 准备工作 创建工作空间&#xff08;ros_zy&#xff09; mkdir ros_zy进入工作空间 cd ros_zy创建src文件夹&#xff08;放源程序&#xff09; mkdir src编译工作空间 catkin_make打开vscode&#xff08;从终端打开此工程&#xff09; code .进入工作空间的…

如何查看自己的文章是否被数据库收入?【查收查引】

致谢&#xff1a;特别感谢图书馆的蔡老师&#xff0c;告诉我怎么操作&#xff01; 另外&#xff0c;查收查引报告中的文章可以分开开&#xff0c;放在一起开不是必须的。&#xff08;放在一起开大概是院士工作量需要的。不是很了解。&#xff09; 如何查看自己的文章是否被数据…

tomcat部署以及虚拟主机的部署

Tomcat概述 Tomcat是Java语言开发的&#xff0c;服务器是一个免费的开放源代码的Web应用服务器&#xff0c;属于轻量级应用服务器&#xff0c;在中小型系统和并发访问用户不是很多的场合下被普遍使用&#xff0c;是开发和调试JSP程序的首选。一般来说&#xff0c;Tomcat虽然和…

c语言 词法分析器 《编译原理》课程设计

设计、编制并调试一个词法分析程序&#xff0c;加深对词法分析原理的理解。 针对表达各类词语的一组正规表达式&#xff0c;设计一个确定化的最简的有限自动机&#xff0c;对输入的符号串进行单词划分及词类识别。 要求词法分析器的输入是字符串&#xff0c;输出是源程序中各…

苍穹外卖项目笔记(8)— 缓存商品、购物车功能

前言 代码链接&#xff1a; Echo0701/take-out⁤ (github.com) 1 缓存菜品 1.1 问题说明 【注】很多时候系统性能的瓶颈就在于数据库这端 1.2 实现思路 通过 Redis 来缓存数据&#xff0c;减少数据库查询操作 【注】Redis 基于内存来保存数据的&#xff0c;访问 Redis 数据…

python:五种算法(GA、OOA、DBO、SSA、PSO)求解23个测试函数(python代码)

一、五种算法简介 1、遗传算法GA 2、鱼鹰优化算法OOA 3、蜣螂优化算法DBO 4、麻雀搜索算法SSA 5、粒子群优化算法PSO 二、5种算法求解23个函数 &#xff08;1&#xff09;23个函数简介 参考文献&#xff1a; [1] Yao X, Liu Y, Lin G M. Evolutionary programming made…

JVS物联网、低代码、智能BI本周更新功能已上线

物联网应用更新功能 新增: 1.新增驱动管理功能&#xff0c;可新增、编辑、修改、删除、查看驱动实例&#xff1b; 驱动管理功能主要负责管理物联网设备的驱动实例。这些驱动实例可以新增、编辑、修改、删除或查看。通过这些驱动实例&#xff0c;平台可以与设备进行通信&…

Git 常用命令速查

一、 Git 常用命令速查 git branch 查看本地所有分支git status 查看当前状态git commit 提交git branch -a 查看所有的分支git branch -r 查看远程所有分支git commit -am "init" 提交并且加注释git remote add origin git192.168.1.119:ndshowgit push origin mas…

XML学习及应用

介绍XML语法及应用 1.XML基础知识1.1什么是XML语言1.2 XML 和 HTML 之间的差异1.3 XML 用途 2.XML语法2.1基础语法2.2XML元素2.3 XML属性2.4XML命名空间 3.XML验证3.1xml语法验证3.2自定义验证3.2.1 XML DTD3.2.2 XML Schema3.2.3PCDATA和CDATA区别3.2.4 参考 4.xml解析4.1准备…

Onlyoffice本地部署超详细教程(附协作空间2.0新资讯)

陈老老老板&#x1f934; &#x1f9d9;‍♂️本文专栏&#xff1a;生活&#xff08;主要讲一下自己生活相关的内容&#xff09;生活就像海洋,只有意志坚强的人,才能到达彼岸。 &#x1f9d9;‍♂️本文简述&#xff1a;ONLYOFFICE相信大家已经有所了解&#xff0c;本篇讲一下o…

香橙派orangepi5 定制ubuntu rootfs

问题与需求 公司3588s开发板外设少, 没有usb,网卡,扩展gpio. 需要使用其它3588开发板做验证. 香橙派orangepi5属于性价比很高的开发板. 需要部署环境rosopencv配置; 每次烧录,配置wifi, ip, frpc, 配置环境要30分钟. 问题: 烧录部署一台orangepi5, 需要30分钟, 浪费时间 …

用重建大师生成后的模型,和DLG有点偏差,不是特别吻合,是什么原因?另外这个读取实体多边形失败是为什么?

答&#xff1a;可以先检查下是否有先生成三维模型&#xff0c;三维模型和DLG是否位置是对应一起的。模型可以先检查位置精度是否满足要求。 重建大师是一款专为超大规模实景三维数据生产而设计的集群并行处理软件&#xff0c;输入倾斜照片&#xff0c;激光点云&#xff0c;POS…

参加汽车销售技巧培训师司铭宇老师的课程的总结

参加汽车销售技巧培训师司铭宇老师的课程的总结 作为一名汽车销售人员&#xff0c;我深知销售技巧对于提升销售业绩的重要性。为了进一步提升自己的销售能力&#xff0c;我参加了司铭宇老师的汽车销售技巧培训课程。通过这次课程的学习&#xff0c;我收获颇丰&#xff0c;以下…

计网 - TCP扫盲

文章目录 知识点TCP头格式TCP有限状态机&#xff08;FSM&#xff09;为何需要TCP协议TCP的定义TCP连接的概念如何唯一确定一个TCP连接TCP vs UDPTCP拥塞控制TCP流量控制 导图 知识点 TCP头格式 TCP头部包含多个字段&#xff0c;其中一些是必需的&#xff0c;而另一些是可选的…

基于docker容器化部署微服务

前言 在笔者系列文章中微服务配置隔离已经完成服务之间的配置隔离&#xff0c;服务整体来说是已经通了。 为了方便后续测试已经环境统一&#xff0c;笔者本章节会对服务进行容器化部署。由于服务器性能问题&#xff0c;本次部署采用maven完成镜像构建&#xff0c;结合docker-c…

Linux 系统 SSH 和 SCP 服务器搭建、配置、访问以及出现的问题

SSH是Secure Shell的缩写&#xff0c;是一种网络协议&#xff0c;用于通过本地或远程网络在计算机上进行远程登录和命令操作。SSH 是 Telnet 协议的演变&#xff1a;正如其名称所描述的&#xff0c;SSH 是安全的&#xff0c;并对通过网络传输的数据进行加密。 SSH 是目前较为可…