Redis(10)| I/O多路复用(mutiplexing)

上文select/epoll

在上文《Redis(09)| Reactor模式》 思考问题可以使用I/O多路复用技术解决多多客户端TCP连接问题,同时也提到为了解决最早期的UNIX系统select调用存在的四个问题。
select(int nfds, fd_set *r, fd_set *w, fd_set *e, struct timeval *timeout)

  • 这3个bitmap有大小限制(FD_SETSIZE,通常为1024);
  • 由于这3个集合在返回时会被内核修改,因此我们每次调用时都需要重新设置;
  • 全量fd集合扫描,效率比较低下;
  • 每次扫描筛选出实际发生时间的fd,在读/写比较稀疏的情况下同样存在效率问题;

如何解决的呢:

  • poll传递的不是固定大小的bitmap,解决问题1;
  • poll将感兴趣事件和实际发生事件分开了,解决问题2;
  • 系统调用返回的是实际发生相应事件的fd集合,解决问题3;
  • 有状态,epoll和kqueue,创建一个上下文context,解决问题4;

什么是 IO 多路复用机制

在这里插入图片描述
看上图,通俗讲就是I/O multiplexing 这里面的 multiplexing 指的其实是在单个线程通过记录跟踪每一个Sock(I/O流)的状态(对应空管塔里面的Fight progress strip槽)来同时管理多个I/O流. 发明它的原因,是尽量多的提高服务器的吞吐能力。

那么什么是IO多路复用机制呢,Linux 中的 IO 多路复用机制是指一个线程处理多个 IO 流,就是我们经常听到的 select/epoll 机制。简单来说,在 Redis 只运行单线程的情况下,该机制允许内核中,同时存在多个监听套接字和已连接套接字。内核会一直监听这些套接字上的连接请求或数据请求。一旦有请求到达,就会交给 Redis 线程处理,这就实现了一个 Redis 线程处理多个 IO 流的效果。

下图就是基于多路复用的 Redis IO 模型。图中的多个 FD 就是刚才所说的多个套接字。Redis 网络框架调用 epoll 机制,让内核监听这些套接字。此时,Redis 线程不会阻塞在某一个特定的监听或已连接套接字上,也就是说,不会阻塞在某一个特定的客户端请求处理上。正因为此,Redis 可以同时和多个客户端连接并处理请求,从而提升并发性。
在这里插入图片描述
为了在请求到达时能通知到 Redis 线程,select/epoll 提供了基于事件的回调机制,即针 对不同事件的发生,调用相应的处理函数。

那么,回调机制是怎么工作的呢?
其实,select/epoll 一旦监测到 FD 上有请求到达时,就会触发相应的事件。
这些事件会被放进一个事件队列,Redis 单线程对该事件队列不断进行处理。这样一来,Redis 无需一直轮询是否有请求实际发生,这就可以避免造成 CPU 资源浪费。同时,Redis 在对事件队列中的事件进行处理时,会调用相应的处理函数,这就实现了基于事件的回调。因为 Redis 一直在对事件队列进行处理,所以能及时响应客户端请求,提升 Redis 的响应性能。

我再以连接请求和读数据请求为例,具体解释一下。这两个请求分别对应 Accept 事件和 Read 事件,Redis 分别对这两个事件注册 accept 和 get 回调函数。当 Linux 内核监听到有连接请求或读数据请求时,就会触发 Accept 事件和 Read 事件,此时,内核就会回调 Redis 相应的 accept 和 get 函数进行处理。
不过,需要注意的是,即使你的应用场景中部署了不同的操作系统,多路复用机制也是适用的。因为这个机制的实现有很多种,既有基于 Linux 系统下的 select 和 epoll 实现,也有基于 FreeBSD 的 kqueue 实现,以及基于 Solaris 的 evport 实现,这样,你可以根据 Redis 实际运行的操作系统,选择相应的多路复用实现。

所以,IO多路复用模型是建立在内核提供的多路分离函数select基础之上的,使用select函数可以避免同步非阻塞IO模型中轮询等待的问题。
在这里插入图片描述
用户首先将需要进行IO操作的socket添加到select中,然后阻塞等待select系统调用返回。当数据到达时,socket被激活,select函数返回。用户线程正式发起read请求,读取数据并继续执行。
从流程上来看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。但是,使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。
用户线程使用select函数的伪代码描述为:

{
select(socket);while(1) {sockets = select();for(socket in sockets) {if(can_read(socket)) {read(socket, buffer);process(buffer);}}}
}

其中while循环前将socket添加到select监视中,然后在while内一直调用select获取被激活的socket,一旦socket可读,便调用read函数将socket中的数据读取出来。
然而,使用select函数的优点并不仅限于此。虽然上述方式允许单线程内处理多个IO请求,但是每个IO请求的过程还是阻塞的(在select函数上阻塞),平均时间甚至比同步阻塞IO模型还要长。如果用户线程只注册自己感兴趣的socket或者IO请求,然后去做自己的事情,等到数据到来时再进行处理,则可以提高CPU的利用率。
IO多路复用模型使用了Reactor设计模式实现了这一机制。这一步可以读上一篇《Redis(09)| Reactor模式》 。
在这里插入图片描述
上图,EventHandler抽象类表示IO事件处理器,它拥有IO文件句柄Handle(通过get_handle获取),以及对Handle的操作handle_event(读/写等)。继承于EventHandler的子类可以对事件处理器的行为进行定制。Reactor类用于管理EventHandler(注册、删除等),并使用handle_events实现事件循环,不断调用同步事件多路分离器(一般是内核)的多路分离函数select,只要某个文件句柄被激活(可读/写等),select就返回(阻塞),handle_events就会调用与文件句柄关联的事件处理器的handle_event进行相关操作。
在这里插入图片描述
上图所示,通过Reactor的方式,可以将用户线程轮询IO操作状态的工作统一交给handle_events事件循环进行处理。用户线程注册事件处理器之后可以继续执行做其他的工作(异步),而Reactor线程负责调用内核的select函数检查socket状态。当有socket被激活时,则通知相应的用户线程(或执行用户线程的回调函数),执行handle_event进行数据读取、处理的工作。由于select函数是阻塞的,因此多路IO复用模型也被称为异步阻塞IO模型。注意,这里的所说的阻塞是指select函数执行时线程被阻塞,而不是指socket。一般在使用IO多路复用模型时,socket都是设置为NONBLOCK的,不过这并不会产生影响,因为用户发起IO请求时,数据已经到达了,用户线程一定不会被阻塞。

阻塞IO(Blocking I/O)

虽然还有很多其它的 I/O 模型,但是在这里都不会具体介绍。

在linux中,默认情况下所有的socket都是blocking,一个典型的读操作流程大概是这样:
在这里插入图片描述
当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。

而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。
所以,blocking IO的特点就是在IO执行的两个阶段(等待数据和拷贝数据两个阶段)都被block了。
几乎所有的程序员第一次接触到的网络编程都是从listen()、send()、recv() 等接口开始的,使用这些接口可以很方便的构建服务器/客户机的模型。然而大部分的socket接口都是阻塞型的。如下图
在这里插入图片描述
实际上,除非特别指定,几乎所有的IO接口 ( 包括socket接口 ) 都是阻塞型的。这给网络编程带来了一个很大的问题,如在调用recv(1024)的同时,线程将被阻塞,在此期间,线程将无法执行任何运算或响应任何的网络请求。

先来看一下传统的阻塞 I/O 模型到底是如何工作的:当使用 read 或者 write 对某一个文件描述符(File Descriptor 以下简称 FD)进行读写时,如果当前FD 不可读或不可写,整个 Redis 服务就不会对其它的操作作出响应,导致整个服务不可用。
这也就是传统意义上的,也就是我们在编程中使用最多的阻塞模型:
阻塞模型虽然开发中非常常见也非常易于理解,但是由于它会影响其他 FD 对应的服务,所以在需要处理多个客户端任务的时候,往往都不会使用阻塞模型。

I/O 多路复用

在 I/O 多路复用模型中,最重要的函数调用就是 select,该方法能够同时监控多个文件描述符的可读可写情况,当其中的某些文件描述符可读或者可写时,select 方法就会返回可读以及可写的文件描述个数。文件事件处理器使用 I/O 多路复用模块同时监听多个 FD,当 accept、read、write 和 close 文件事件产生时,文件事件处理器就会回调 FD 绑定的事件处理器。虽然整个文件事件处理器是在单线程上运行的,但是通过 I/O 多路复用模块的引入,实现了同时对多个 FD 读写的监控,提高了网络通信模型的性能,同时也可以保证整个 Redis 服务实现的简单。Redis 是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都是阻塞的,所以 I/O 操作在一般情况下往往不能直接返回,**这会导致某一文件的 I/O 阻塞导致整个进程无法对其它客户提供服务,**而I/O 多路复用就是为了解决这个问题而出现的。

简述下源码

I/O 多路复用模块封装了底层的 select、epoll、avport 以及 kqueue 这些 I/O 多路复用函数,为上层提供了相同的接口。
在这里我们简单介绍 Redis 是如何包装 select 和 epoll 的,简要了解该模块的功能,整个 I/O 多路复用模块抹平了不同平台上 I/O 多路复用函数的差异性,提供了相同的接口:

static int aeApiCreate(aeEventLoop *eventLoop)
static int aeApiResize(aeEventLoop *eventLoop, int setsize)
static void aeApiFree(aeEventLoop *eventLoop)
static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask)
static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask)
static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp)

同时,因为各个函数所需要的参数不同,我们在每一个子模块内部通过一个 aeApiState 来存储需要的上下文信息:

// select
typedef struct aeApiState {fd_set rfds, wfds;fd_set _rfds, _wfds;
} aeApiState;// epoll
typedef struct aeApiState {int epfd;struct epoll_event *events;
} aeApiState;

这些上下文信息会存储在 eventLoop 的 void *state 中,不会暴露到上层,只在当前子模块中使用。

封装 select 函数

select 可以监控 FD 的可读、可写以及出现错误的情况。
在介绍 I/O 多路复用模块如何对 select 函数封装之前,先来看一下 select 函数使用的大致流程:

int fd = /* file descriptor */
//初始化一个可读的 fd_set 集合,保存需要监控可读性的 FD;
fd_set rfds;
//使用 FD_SET 将 fd 加入 rfds;
FD_ZERO(&rfds);
FD_SET(fd, &rfds)
for ( ; ; ) {//调用 select 方法监控 rfds 中的 FD 是否可读;select(fd+1, &rfds, NULL, NULL, NULL);//当 select 返回时,检查 FD 的状态并完成对应的操作。if (FD_ISSET(fd, &rfds)) {/* file descriptor `fd` becomes readable */}
}

而在 Redis 的 ae_select 文件中代码的组织顺序也是差不多的,首先在 aeApiCreate 函数中初始化 rfds 和 wfds

static int aeApiCreate(aeEventLoop *eventLoop) {aeApiState *state = zmalloc(sizeof(aeApiState));if (!state) return -1;FD_ZERO(&state->rfds);FD_ZERO(&state->wfds);eventLoop->apidata = state;return 0;
}

而 aeApiAddEvent 和 aeApiDelEvent 会通过 FD_SET 和 FD_CLR 修改 fd_set 中对应 FD 的标志位:

static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) {aeApiState *state = eventLoop->apidata;if (mask & AE_READABLE) FD_SET(fd,&state->rfds);if (mask & AE_WRITABLE) FD_SET(fd,&state->wfds);return 0;
}

整个 ae_select 子模块中最重要的函数就是 aeApiPoll,它是实际调用 select 函数的部分,其作用就是在 I/O 多路复用函数返回时,将对应的 FD 加入 aeEventLoop 的 fired 数组中,并返回事件的个数:

static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {aeApiState *state = eventLoop->apidata;int retval, j, numevents = 0;memcpy(&state->_rfds,&state->rfds,sizeof(fd_set));memcpy(&state->_wfds,&state->wfds,sizeof(fd_set));retval = select(eventLoop->maxfd+1,&state->_rfds,&state->_wfds,NULL,tvp);if (retval > 0) {for (j = 0; j <= eventLoop->maxfd; j++) {int mask = 0;aeFileEvent *fe = &eventLoop->events[j];if (fe->mask == AE_NONE) continue;if (fe->mask & AE_READABLE && FD_ISSET(j,&state->_rfds))mask |= AE_READABLE;if (fe->mask & AE_WRITABLE && FD_ISSET(j,&state->_wfds))mask |= AE_WRITABLE;eventLoop->fired[numevents].fd = j;eventLoop->fired[numevents].mask = mask;numevents++;}}return numevents;
}

封装 epoll 函数

Redis 对 epoll 的封装其实也是类似的,使用 epoll_create 创建 epoll 中使用的 epfd:

static int aeApiCreate(aeEventLoop *eventLoop) {aeApiState *state = zmalloc(sizeof(aeApiState));if (!state) return -1;state->events = zmalloc(sizeof(struct epoll_event)*eventLoop->setsize);if (!state->events) {zfree(state);return -1;}state->epfd = epoll_create(1024); /* 1024 is just a hint for the kernel */if (state->epfd == -1) {zfree(state->events);zfree(state);return -1;}eventLoop->apidata = state;return 0;
}

在 aeApiAddEvent 中使用 epoll_ctl 向 epfd 中添加需要监控的 FD 以及监听的事件:

static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) {aeApiState *state = eventLoop->apidata;struct epoll_event ee = {0}; /* avoid valgrind warning *//* If the fd was already monitored for some event, we need a MOD* operation. Otherwise we need an ADD operation. */int op = eventLoop->events[fd].mask == AE_NONE ?EPOLL_CTL_ADD : EPOLL_CTL_MOD;ee.events = 0;mask |= eventLoop->events[fd].mask; /* Merge old events */if (mask & AE_READABLE) ee.events |= EPOLLIN;if (mask & AE_WRITABLE) ee.events |= EPOLLOUT;ee.data.fd = fd;if (epoll_ctl(state->epfd,op,fd,&ee) == -1) return -1;return 0;
}

由于 epoll 相比 select 机制略有不同,在 epoll_wait 函数返回时并不需要遍历所有的 FD 查看读写情况;在 epoll_wait 函数返回时会提供一个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 事件 */epoll_data_t data;
};

其中保存了发生的 epoll 事件(EPOLLIN、EPOLLOUT、EPOLLERR 和 EPOLLHUP)以及发生该事件的 FD。
aeApiPoll 函数只需要将epoll_event 数组中存储的信息加入 eventLoop 的 fired 数组中,将信息传递给上层模块:

static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {aeApiState *state = eventLoop->apidata;int retval, numevents = 0;retval = epoll_wait(state->epfd,state->events,eventLoop->setsize,tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);if (retval > 0) {int j;numevents = retval;for (j = 0; j < numevents; j++) {int mask = 0;struct epoll_event *e = state->events+j;if (e->events & EPOLLIN) mask |= AE_READABLE;if (e->events & EPOLLOUT) mask |= AE_WRITABLE;if (e->events & EPOLLERR) mask |= AE_WRITABLE;if (e->events & EPOLLHUP) mask |= AE_WRITABLE;eventLoop->fired[j].fd = e->data.fd;eventLoop->fired[j].mask = mask;}}return numevents;
}

子模块的选择

因为 Redis 需要在多个平台上运行,同时为了最大化执行的效率与性能,所以会根据编译平台的不同选择不同的 I/O 多路复用函数作为子模块,提供给上层统一的接口;在 Redis 中,我们通过宏定义的使用,合理的选择不同的子模块

#ifdef HAVE_EVPORT
#include "ae_evport.c"
#else
#ifdef HAVE_EPOLL
#include "ae_epoll.c"
#else
#ifdef HAVE_KQUEUE
#include "ae_kqueue.c"
#else
#include "ae_select.c"
#endif
#endif
#endif

因为 select 函数是作为 POSIX 标准中的系统调用,在不同版本的操作系统上都会实现,所以将其作为保底方案:
Redis 会优先选择时间复杂度为 的 I/O 多路复用函数作为底层实现,包括 Solaries 10 中的 evport、Linux 中的 epoll 和 macOS/FreeBSD 中的 kqueue,上述的这些函数都使用了内核内部的结构,并且能够服务几十万的文件描述符。
但是如果当前编译环境没有上述函数,就会选择 select 作为备选方案,由于其在使用时会扫描全部监听的描述符,所以其时间复杂度较差 ,并且只能同时服务 1024 个文件描述符,所以一般并不会以 select 作为第一方案使用。

select/poll/epoll区别

在这里插入图片描述
共同点就是大家都是io多路复用,不同点就是epoll采用事件驱动的方式。使得连接没有上限。

总结

Redis 对于 I/O 多路复用模块的设计非常简洁,通过宏保证了 I/O 多路复用模块在不同平台上都有着优异的性能,将不同的 I/O 多路复用函数封装成相同的 API 提供给上层使用。整个模块使 Redis 能以单进程运行的同时服务成千上万个文件描述符,避免了由于多进程应用的引入导致代码实现复杂度的提升,减少了出错的可能性。

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

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

相关文章

动手学深度学习——第七次学

LeNet&#xff08;LeNet-5&#xff09;由两个部分组成&#xff1a; 卷积编码器和全连接层密集块 卷积把高宽不断变小&#xff0c;把通道数逐渐增多&#xff0c;&#xff08;最后高宽会变成&#xff0c;通道会变得很大&#xff0c;然后做全连接进行输出&#xff09;通道信息可以…

7+共病思路。WGCNA+多机器学习+实验简单验证,易操作

今天给同学们分享一篇共病WGCNA多机器学习实验的生信文章“Shared diagnostic genes and potential mechanism between PCOS and recurrent implantation failure revealed by integrated transcriptomic analysis and machine learning”&#xff0c;这篇文章于2023年5月16日发…

人到中年应该怎么交朋友

听人劝、吃饱饭,奉劝各位小伙伴,不要订阅该文所属专栏。 作者:不渴望力量的哈士奇(哈哥),十余年工作经验, 跨域学习者,从事过全栈研发、产品经理等工作,现任研发部门 CTO 。荣誉:2022年度博客之星Top4、博客专家认证、全栈领域优质创作者、新星计划导师,“星荐官共赢计…

虚拟机克隆

linux系统的组成&#xff1b; 主根目录和根目录; 所有的根目录都包含在主根目录中&#xff1b; 根目录&#xff1a; /root /home/xxx,yyy,zzz;主根目录&#xff1b;/ 一个重要的子目录&#xff1a;etc passwd, 保存了所有的三类用户信息&#xff1b;bashrc, 可以设置别名 及…

HarmonyOS开发:基于http开源一个网络请求库

前言 网络封装的目的&#xff0c;在于简洁&#xff0c;使用起来更加的方便&#xff0c;也易于我们进行相关动作的设置&#xff0c;如果&#xff0c;我们不封装&#xff0c;那么每次请求&#xff0c;就会重复大量的代码逻辑&#xff0c;如下代码&#xff0c;是官方给出的案例&am…

阿里云推出通义千问App,提供全方位的协助

&#x1f989; AI新闻 &#x1f680; 阿里云推出通义千问App&#xff0c;提供全方位的协助 摘要&#xff1a;阿里云旗下大模型通义千问App登陆各大安卓应用市场&#xff0c;具有超大规模预训练模型&#xff0c;可在创意文案、办公助理、学习助手、趣味生活等方面协助用户。功…

Transformer.js简明教程【Web AI】

Transformers.js 可在你的 Web 浏览器中实现最先进的机器学习&#xff0c;无需服务器。 它提供预训练模型和熟悉的 API&#xff0c;支持自然语言处理、计算机视觉、音频和多模态领域的任务。 借助 Transformers.js&#xff0c;开发人员可以直接在浏览器中运行文本分类、图像分类…

RPC与HTTP的关系

首选理清楚关系 RPC与HTTP是两个不同维度的东西 HTTP 协议&#xff08;Hyper Text Transfer Protocol&#xff09;&#xff0c;又叫做超文本传输协议&#xff0c;是一种传输协议&#xff0c;平时通过浏览器浏览网页网页&#xff0c;用到的就是 HTTP 协议。 而 RPC&#xff0…

Flutter FittedBox

&#x1f525; 英文单词FittedBox &#x1f525; Fitted 通过有道翻译如下 &#xff1a; Box 通过有道翻译如下 &#xff1a; 对 FittedBox 的理解 我们可以将 FittedBox 理解为合适的盒子&#xff0c;将其它布局放到FittedBox这样一个盒子中&#xff0c;从而实现 盒子里面的…

ceph高可用

配置基础环境 # 关闭防火墙 systemctl stop firewalld systemctl disable firewalld# 关闭selinux setenforce 0 sed -i s/^SELINUX.*/SELINUXdisabled/ /etc/selinux/config 安装基础环境 然后安装ceph的密钥&#xff0c;centos7和8都要执行&#xff0c;下面不特别说明都是c…

一文详解汽车电子LIN总线

0.摘要 汽车电子LIN总线不同于CAN总线。 LIN总线基本上是CAN总线的廉价补充&#xff0c;相比于CAN总线&#xff0c;它提供较低的可靠性和性能。同时LIN总线也是一个应用非常广泛的网络协议&#xff0c;并且越来越受欢迎。 再一次&#xff0c;我们准备了一个关于LIN总线的简要…

SurfaceFliger绘制流程

前景提要&#xff1a; 当HWComposer接收到Vsync信号时&#xff0c;唤醒DisSync线程&#xff0c;在其中唤醒EventThread线程&#xff0c;调用DisplayEventReceiver的sendObjects像BitTub发送消息&#xff0c;由于在SurfaceFlinger的init过程中创建了EventThread线程&#xff0c…

【Proteus仿真】【STM32单片机】便携式恒温箱设计

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真STM32单片机控制器&#xff0c;使用报警模块、LCD1602显示模块、DS18B20温度模块、加热制冷模块、按键模块、HC05蓝牙模块等。 主要功能&#xff1a; 系统运行后&#xff0c;LCD1…

Leetcode刷题详解——不同路径 II

1. 题目链接&#xff1a;63. 不同路径 II 2. 题目描述&#xff1a; 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finis…

连铸生产线液压系统比例伺服阀放大器

连铸生产线液压系统是连铸机的关键组成部分&#xff0c;它由液压站组成&#xff0c;包括高压泵站、剪切机泵站、滑动水口站、塞棒液压站、中间罐车液压站和倾翻台液压站。这些站点通过管道连接&#xff0c;共同实现连铸机的各类动作&#xff0c;如升降、横移、定位、锁紧及辊缝…

TCP/IP(十七)实战抓包分析(一)ICMP

一 TCP实战抓包分析 网络排查案例 ① 抓包分析涉及的内容 关于&#xff1a; TCP理论知识和tcpdump命令的知识,前面已经铺垫过了,这里不再赘述下面罗列了TCP的重点知识 客户端工具&#xff1a; curl、wget、postman、telnet、浏览器、ncwget --bind-addressADDRESS 指定…

酷开科技,让家庭更有温度!

生活中总有一些瞬间&#xff0c;会让我们感到无比温暖和幸福。一个拥抱、一句问候、一杯热茶&#xff0c;都能让我们感受到家庭的温馨和关爱。酷开科技也用自己的方式为我们带来了独属于科技的温暖&#xff0c;通过全新的体验将消费者带进一个充满惊喜的世界&#xff0c;让消费…

如何在Android设备上检查应用程序使用情况,包括使用时间

你可能不知道自己花了多少时间在手机上。很可能你一天中有一半的时间都在盯着手机屏幕。如果你怀疑这一事实,你会很快核实的。在这篇文章中,我们将向你介绍如何在Android设备上检查应用程序的使用情况。 如何在Android上检查应用程序电池使用情况 你使用时间最长的应用程序…

Python爬虫实战(六)——使用代理IP批量下载高清小姐姐图片(附上完整源码)

文章目录 一、爬取目标二、实现效果三、准备工作四、代理IP4.1 代理IP是什么&#xff1f;4.2 代理IP的好处&#xff1f;4.3 获取代理IP4.4 Python获取代理IP 五、代理实战5.1 导入模块5.2 设置翻页5.3 获取图片链接5.4 下载图片5.5 调用主函数5.6 完整源码5.7 免费代理不够用怎…

第18章_MySQL8其它新特性

第18章_MySQL8其它新特性 讲师&#xff1a;尚硅谷-宋红康&#xff08;江湖人称&#xff1a;康师傅&#xff09; 官网&#xff1a;http://www.atguigu.com 1. MySQL8新特性概述 MySQL从5.7版本直接跳跃发布了8.0版本&#xff0c;可见这是一个令人兴奋的里程碑版本。MySQL 8版…