【Linux】多路转接 -- select函数
文章目录
- 1. 认识select函数
- 2. select函数原型
- 3. socket就绪条件
- 4. select工作流程
- 5. select服务器
- 6. select的优缺点
首先我们要了解一下,什么是多路转接?
多路转接也叫多路复用,是一种用于管理多个IO通道的技术。它能实现同时监听和处理多个IO事件,而不是为每个IO通道创建单独的线程或者进程,多路转接允许在单个进程或线程中同时处理多个IO操作,从而提高程序的性能和效率。
本篇文章介绍的select函数,就用于select系统调用的多路转接技术。
1. 认识select函数
select函数是系统提供的一个多路转接接口。
IO = 等待就绪 + 数据拷贝,而select是只负责等。
- select系统调用可以让我们的程序同时监听多个文件描述符上的事件是否就绪。
- select的核心工作就是等,当监听的多个文件描述符中有一个或多个事件就绪时,select函数才会成功返回并将对应文件描述符的就绪事件告知调用者。
2. select函数原型
参数说明:
- nfds:需要监听的文件描述符中,最大的文件描述符值 + 1。
- readfs:输入输出型参数,调用时用户告知内核需要监听哪些文件描述符的读事件是否就绪,返回时内核告诉用户哪些文件描述符的读事件已经就绪。
- writefds:输入输出型参数,调用时用户告知内核需要监听哪些文件描述符的写事件是否就绪,返回时内核告知用户哪些文件描述符的写事件已经就绪。
- exceptfds:输入输出型参数,调用时告知内核需要监听哪些文件描述符的异常事件是否就绪,返回时内核告知用户哪些文件描述符的异常事件已经就绪。
- timeout:输入输出参数,调用时由用户设置select的等待时间,返回时表示timeout的剩余时间。
参数timeout的取值:
- NULL/nullptr:select调用后进行阻塞等待,直到被监视的某个文件描述符上的事件就绪。
- 0:select调用后进行非阻塞等待,无论被监视的文件描述符的事件是否就绪,select检测后都会立即返回。
- 特定的时间值:select调用后在指定的时间内进行阻塞等待,如果被监视的文件描述符上一直没有事件就绪,则在该时间后select进行超时返回。
返回值说明:
- 如果函数调用成功,则返回有事件就绪的文件描述符个数。
- 如果timeout时间耗尽,则返回0。
- 如果函数调用失败,则返回-1,同时错误码会被设置。
select调用失败,错误码可能被设置为:
- EBADF:文件描述符有无效的或者该文件已关闭
- EINTR:此调用被信号所中断
- EINVAL:参数nfds为负值
- ENOMEM:核心内存不足
fd_set 结构
fd_set 结构与 sigset_t 结构类似,fd_set 本质也是一个位图,用位图中对应的位来表示要监听的文件描述符。
调用select函数之前就需要用fd_set结构定义出对应的文件描述符集,然后将需要监视的文件描述符添加到文件描述符集当中,这个添加的过程本质就是在进行位操作,但是这个位操作不需要用户自己进行,系统专门提供了一组专门的接口,用户对fd_set位图进行各种操作。
timeval 结构
传入select函数的最后一个参数timeout,就是一个指向timeval结构的指针,timeval结构用于描述一段时间长度,该结构当中包含两个成员,其中tv_sec表示的是秒,tv_usec表示的是微妙。
3. socket就绪条件
读就绪
- socket内核中,接收缓冲区中的字节数,大于等于低水位标记物SO_RCVLOWAT,此时可以无阻塞地读取该文件描述符,并且返回值大于0。
- socket TCP通信中,对端连接关闭,此时对该socket读,则返回0。
- 监听的socket上有新的连接请求。
- socket上有未处理的错误。
写就绪
- socket内核中,发送缓冲区中的可用字节数,大于等于低水位标记SO_SNDLOWAT,此时可以无阻塞地写,并且返回值大于0。
- socket的写操作被关闭(close或者shutdown),对一个写操作被关闭的socket进行写操作,会触发SIGPIPE信号。
- socket使用非阻塞connect连接成功或失败之后。
- socket上有未读取的错误。
异常就绪
- socket上收到带外数据
4. select工作流程
这里我们只介绍select处理读取的操作。
如果我们要实现一个简单的select服务器,该服务器要做的就是读取客户端发来的数据并进行打印,那么这个select服务器的工作流程应该是这样的:
- 先初始化服务器,完成套接字的创建、绑定和监听。
- 定义一个fd_array数组用于保存监听套接字和已经与客户端建立连接的套接字,刚开始时就将监听套接字添加到fd_array数组当中。
- 然后服务器开始循环调用select函数,检测读事件是否就绪,如果就绪则执行对应的操作。
- 每次调用select函数之前,都需要定义一个读文件描述符集readfds,并将fd_array当中的文件描述符依此设置进readfds当中,表示让select帮我们监视这些文件描述符的读事件是否就绪。
- 当select检测到数据就绪时会读事件就绪的文件描述符设置进readfds当中,此时我们能够得知哪些文件描述符的读事件就绪了,并对这些文件描述符进行对应的操作。
- 如果读事件就绪的就是监听套接字,则调用accept函数从底层全连接队列获取已经建立好的连接,并将该连接对应的套接字添加到fd_array数组当中。
- 如果读事件就绪的是与客户端建议连接的套接字,则调用read函数读取客户端发来的数据并进行打印输出。
- 当然,服务器与客户端建立连接的套接字读事件就绪,也可能是因为客户端将连接关闭了,此时服务器应该调用close关闭套接字,并将该套接字从fd_array中清除,因为下一次不需要再监视该文件描述符的读事件了。
注意:
- 因为传入的select函数的readfds、writefds和exceptfds都是输入输出型参数,当select函数返回时这些参数当中的值已经被修改了,因此每次调用seletct函数时都需要对其进行重新设置,timeout也是类似的道理。
- 因为每次调用select函数之前都需要对readfds进行重新设置,所以需要定义一个fd_array数组保存与客户端已经建立的若干连接和监听套接字,实际fd_array数组当中的文件描述符就是需要让select监视读事件的文件描述符。
- 我们的select服务器只是读取客户端发来的数据,因此只需让select帮我们监视特定文件描述符的读事件,如果同时让select帮我们监视特定文件描述符的读事件和写事件,则需要分别定义readfds和writefds,并定义两个数组分别保存需要被监视读事件和写事件的文件描述符,便于每次调用select函数前对readfds和writefds进行重新设置。
- 服务器刚开始运行时,fd_array数组当中只有监听套接字,因此select第一次调用时只需要监视监听套接字的读事件是否就绪,但每次调用accept获取到新连接之后,都会将连接对应的套接字添加到fd_array当中,因此后续select调用时就需要监视监听套接字和若干连接套接字的读事件是否就绪。
- 由于调用select时还需要传入被监视的文件描述符中最大文件描述符值+1,因此每次在遍历fd_array对readfds进行重新设置时,还需要记录最大文件描述符的值。
5. select服务器
Socket类
我们编写一个Socket类,对套接字相关的接口进行一定程序的封装,为了让外部能够直接调用Socket类当中的函数,我们将这些成员函数定义成静态成员函数。
#pragma once#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <cstring>
#include <cstdlib>class Socket
{
public:// 创建套接字static int SocketCreate(){int sock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0){std::cerr << "socket error" << std::endl;exit(2);}// 设置端口复用int opt = 1;setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));return sock;}// 绑定static void SocketBind(int sock, int port){struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = INADDR_ANY;socklen_t len = sizeof(local);if (bind(sock, (struct sockaddr*)&local, len) < 0){std::cerr << "bind error" << std::endl;exit(3);}}// 监听static void SocketListen(int sock, int backlog){if (listen(sock, backlog) < 0){std::cerr << "listen error" << std::endl;exit(4);}}
};
SelectServer类
编写SelectServer类,因为我当前使用的是云服务器,所以编写的select服务器在绑定时只需将IP地址设置为INADDR_ANY即可,所以类中只包含监听套接字和端口号两个成员变量即可。
- 在构造SelectServer对象时,需要指明select服务器的端口号,当然也可以在初始化select服务器的时候指明。
- 在初始化select服务器的时候需要调用Socket类当中的函数,依此进行套接字的创建、绑定和监听即可。
- 在析构函数中可以选择调用close函数将监听套接字进行关闭,但实际也可以不进行该动作,因为服务器运行后一般是不退出的。
#pragma once#include "Socket.hpp"
#include <sys/select.h>#define BACK_LOG 5class SelectServer
{
public:SelectServer(int port): _port(port){}void InitSelectServer(){_listen_sock = Socket::SocketCreate();Socket::SocketBind(_listen_sock, _port);Socket::SocketListen(_listen_sock, BACK_LOG);}~SelectServer(){if (_listen_sock >= 0) close(_listen_sock);}private:int _listen_sock;int _port;
};
运行服务器
服务器初始化完毕之后就可以周期性地执行某种动作了,而select服务器要做的就是不断调用select函数,当事件就绪对应执行某种动作即可。
- 首先,在select服务器开始死循环调用select函数之前,需要先定义一个fd_array数组,先把数组中所有的位置初始化为无效,并将监听套接字添加到该数组当中,fd_array数组当中保存的就是需要被select监视读事件是否就绪的文件描述符。
- 此后,select服务器就不断调用select函数监视读事件是否就绪,每次调用select函数之前都需要重新设置readfds,具体设置过程就是遍历fd_array数组,将fd_array数组当中的文件描述符添加到readfds当中,并同时记录最大的文件描述符maxfd。
- 当select函数返回后,如果返回值为0,则说明timeout时间耗尽,此时直接准备下一次select调用即可。如果select的返回值为-1,则说明select调用失败,此时也让服务器准备下一次select调用,但实际应该进一步判断错误码,根据错误码来判断是否应该继续调用select函数。
- 如果select函数的返回值大于0,则说明select函数调用成功,此时已经有文件描述符的读事件就绪,接下来就应该对就绪事件进行处理。
#pragma once#include "Socket.hpp"
#include <sys/select.h>#define BACK_LOG 5
#define NUM 1024
#define DFL_FD - 1class SelectServer
{
public:SelectServer(int port): _port(port){}void InitSelectServer(){_listen_sock = Socket::SocketCreate();Socket::SocketBind(_listen_sock, _port);Socket::SocketListen(_listen_sock, BACK_LOG);}~SelectServer(){if (_listen_sock >= 0) close(_listen_sock);}void Run(){fd_set readfds; // 创建读文件描述符集int fd_array[NUM]; // 保存需要被监视读事件是否就绪的文件描述符ClearFdArray(fd_array, NUM, DFL_FD); // 将数组中的所有位置设置为无效fd_array[0] = _listen_sock; // 将监听套接字添加到fd_array数组中的第0个位置while (1){FD_ZERO(&readfds); // 清空readfds// 将fd_array数组当中的文件描述符添加到readfds中,并记录最大的文件描述符int maxfd = DFL_FD;for (int i = 0; i < NUM; ++i){if (fd_array[i] == DFL_FD) continue; // 跳过无效的位置FD_SET(fd_array[i], &readfds); // 将有效位置的文件描述符添加到readfds中if (fd_array[i] > maxfd) maxfd = fd_array[i]; // 更新最大文件描述符}switch (select(maxfd + 1, &readfds, nullptr, nullptr, nullptr)){case 0:std::cout << "timeout..." << std::endl;break;case -1:std::cerr << "select error" << std::endl;break;default:std::cout << "有事件发生..." << std::endl;break; }}}private:void ClearFdArray(int fd_array[], int num, int default_fd){for (int i = 0; i < num; ++i) fd_array[i] = default_fd;}int _listen_sock;int _port;
};
启动服务器
#include "SelectServer.hpp"
#include <string>int main(int argc, char* argv[])
{if (argc != 2){std::cerr << "Usage: " << "SelectServer" << " port" << std::endl;exit(1);}int port = atoi(argv[1]);SelectServer* svr = new SelectServer(port);svr->InitSelectServer();svr->Run();return 0;
}
由于当前服务器调用select函数时直接将timeout设置为了nullptr,因此select函数调用后会进行阻塞等待。而服务器在第一次调用select函数时只让select函数监视监听套接字的读事件,所以运行服务器之后如果没有客户端发来连接请求,那么读事件就不会就绪,而服务器会一直在第一次调用的select函数中进行阻塞等待。
当我们借助telnet工具向select服务器发起连接请求之后,select函数就会立马检测到监听套接字的读事件就绪,此时select函数便会返回成功,并将我们设置的提示语句进行打印输出,因为当前程序没有对就绪事件进行处理,此后每次select函数一调用就会检测到读事件就绪成功返回,因此屏幕不但打印输出提示语句。
如果服务器在调用select函数时将timeout的值设置为0,那么select函数调用后就会进行非阻塞等待,无论被监视的文件描述符上的事件是否就绪,select检测后都会立即返回。
此时如果select监视的文件描述符上有事件就绪,那么select函数的返回值就是大于0的,如果select函数监视的文件描述符上没有事件就绪,那么select的返回值就是小于0的,这里也就不进行演示了。
事件处理
当select检测到右文件描述符的读事件就绪并成功返回后,接下来就应该对就绪事件进行处理了,这里编写一个HandleEvent函数,当读事件就绪之后就调用该函数进行事件处理。
- 在进行事件处理时需要遍历fd_array数组当中的文件描述符,以此判断各个文件描述符对应的读事件是否就绪,如果就绪则需要进行事件处理。
- 当一个文件描述符的读事件就绪之后,还需要进一步判断该文件描述符是否是监听套接字,如果是监听套接字的读事件就绪,那么就应该调用accept函数将底层的连接获取上来。但是只调用accept函数将连接获取上来还不够,为了下一次调用select函数时能够让select帮我们监视新连接的事件是否就绪,在连接获取上来后还应该将连接对应的文件描述符添加到fd_array数组当中,这样在下一次调用select函数前对readfds重新设置时就能将该文件描述符添加进去了。
- 如果是客户端建立的连接对应的读事件就绪,那么就应该调用read函数读取客户端发来的连接,如果读取成功则将读到的数据在服务端进行打印。如果调用read函数读取失败或者客户端关闭了连接,那么select服务器也应该调用close函数关闭对应的连接,但此时只关闭连接也是不够的,还应该将该连接对应的文件描述符从fd_array数组当中清除,否则后续调用的select函数还会帮我们监视该连接的读事件是否就绪,但实际已经不需要了。
void HandleEvent(const fd_set& readfds, int fd_array[], int num){for (int i = 0; i < num; ++i){// 跳过无效位置if (fd_array[i] == DFL_FD) continue;// 连接事件就绪if (fd_array[i] == _listen_sock && FD_ISSET(fd_array[i], &readfds)){// 获取连接struct sockaddr_in peer;memset(&peer, 0, sizeof(peer));socklen_t len = sizeof(peer);int sock = accept(_listen_sock, (struct sockaddr*)&peer, &len);if (sock < 0){std::cerr << "accept error" << std::endl;continue;}std::string peer_ip = inet_ntoa(peer.sin_addr);int peer_port = ntohs(peer.sin_port);std::cout << "get a new link[" << peer_ip << ":" << peer_port << "]" << std::endl;// 将获取到的文件描述符添加到fd_array中if (!SetFdArray(fd_array, num, sock)){// 如果添加失败,关闭文件描述符close(sock);std::cout << "select server is full, close fd: " << sock << std::endl;}}}}private:bool SetFdArray(int fd_array[], int num, int fd){for (int i = 0; i < num; ++i){if (fd_array[i] == DFL_FD) {fd_array[i] = fd;return true;}}return false;}
添加文件描述符到fd_array数组中,本质就是遍历fd_array数组,找到一个没有被使用的位置将该文件描述符添加进去即可。但有可能fd_array数组中全部的位置都已经被占用了,那么文件描述符就会添加失败,此时就只能将刚刚获取上来的连接对应的套接字进行关闭,因为此时服务器是没有能力处理这个连接的。
该select服务器存在的一些问题。
- 服务器没有对客户端进行响应,select服务器如果要向客户端发送数据,不能直接调用write函数,因为调用write函数时实际也为了“等”和“拷贝”两步,我们也应该将“等”的这个过程交给select函数,因此在每次调用select函数之前,除了需要重新设置readfds之外还需要重新设置writefds,并且还需要一个数组来保存需要被监视事件是否就绪的文件描述符,当某一文件描述符的写事件就绪时我们才能够调用write函数向客户端发送数据。
- 没有定制协议,代码中读取数据时并没有按照某种规则进行读取,此时就可能造成粘包问题。比如HTTP协议规定在读取底层数据时读取到空行就表明读完了一个HTTP报头,此时再根据HTTP报头当中的Content-Length属性得知正文的长度,最终就能读取到一个完整的HTTP报文,HTTP协议通过这种方式就避免了粘包问题。
- 没有对应的输入输出缓冲区,代码中直接将读取的数据存储到了字符数组buffer中,这是不严谨的,因为本地数据读取可能并没有读取到一个完整的报文,此时服务器就不能进行数据的分析处理,一个将读取到的数据存储到一个输入缓冲区中,当读取到一个完整的报文之后再让服务器进行处理。此外,如果服务器能够对客户端进行响应,那么服务器的响应数据也不应该直接调用write函数发送给客户端,应该先存储到一个输出缓冲区当中,因为响应数据可能很庞大,无法一次发送完毕,可能需要进行分批发送。
6. select的优缺点
select的优点
- 可以同时等待多个文件描述符,并且只负责等待,实际的IO操作由accept、read、write等接口完成,这些接口在进行IO操作时不会被阻塞。
- select同时等待多个文件描述符,因此可以将“读”的时间重叠,提高了IO的效率。
当然,这也是所有多路转接接口的优点。
select的缺点
- 每次调用select,都需要手动设置fd集合,从接口使用角度来看并不方便。
- 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大。
- 同时每次调用select时都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大。
- select可监控的文件描述符数量太少。
select可监控的文件描述符有1024个,除去其中的一个监听套接字,那么它最多只能连接1023个客户端。
相关文章:

【Linux】多路转接 -- select函数
文章目录 1. 认识select函数2. select函数原型3. socket就绪条件4. select工作流程5. select服务器6. select的优缺点 首先我们要了解一下,什么是多路转接? 多路转接也叫多路复用,是一种用于管理多个IO通道的技术。它能实现同时监听和处理多个…...

ospf于mgre中应用(直连与星型拓扑)
题目 地址配置 R1: R2: R3: R4: R5: ISP: R1/2/3的星型拓扑结构 R1配置: interface Tunnel0/0/0 ip address 192.168.6.1 255.255.255.0 tunnel-protocol gre p2mp source 200.1.1.1 ospf …...

Web压测工具http_load原理分析
01、前言 http_load是一款测试web服务器性能的开源工具,从下面的网址可以下载到最新版本的http_load: http://www.acme.com/software/http_load/ 这个软件一直在保持着更新(不像webbench,已经是十年的老古董了。 webbench的源…...

flask------消息闪现 flash
1介绍 flask提供了一个非常有用的flash()函数,它可以用来“闪现”需要提示给用户的消息,比如当用户登录成功后显示“欢迎回来!”。在视图函数调用flash()函数,传入消息内容,flash()函数把消息存…...

【C++】数据结构与算法:常用查找算法
😏★,:.☆( ̄▽ ̄)/$:.★ 😏 这篇文章主要介绍常用查找算法。 学其所用,用其所学。——梁启超 欢迎来到我的博客,一起学习,共同进步。 喜欢的朋友可以关注一下,下次更新不迷路…...

【Spring Cloud 六】Hystrix熔断
这里写目录标题 系列文章目录背景一、Hystrix是什么服务雪崩服务容错的相关概念熔断器降级超时控制限流 二、会什么要有Hystrix三、如何使用Hystrix进行熔断处理整体项目代码服务提供者pom文件yml配置文件启动类controller 服务消费者pom文件yml配置文件启动类feignhystrixcont…...

FTP使用教程
FTP使用教程 目录 一.FTP简介二.FTP搭建三.FTP使用 一.FTP简介 FTP中文为文件传输协议,简称为文传协议。它也是一个应用程序,不同的操作系统有不同的FTP应用程序,这些应用程序都遵守同一种协议以…...

网络安全(黑客技术)自学
1.网络安全是什么 网络安全可以基于攻击和防御视角来分类,我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术,而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 2.网络安全市场 一、是市场需求量高; 二、则是发展相对成熟…...

使用公式与格式控制Excel快速实现计划甘特图
项目中都会遇到做任务计划的需求,有的客户要求需要有甘特图的形式本文介绍如何使用excel 单元格实现甘特图显示,调整任务时间自动填充单元格填色实现甘特图效果。废话不多说,先看效果。 准备工作先创建两列开始时间与完成时间,这…...

ChatGPT即将取代程序员
W...Y的主页 相信ChatGPT大家已经都不陌生,我们经常会在工作和学习中应用。但是ChatGPT的发展速度飞快。功能也越来越全面。ChatGPT的文章也是层次不穷的出现,ChatGPT即将取代程序员的消息也铺天盖地。那ChatGPT真的会取代程序员吗?我们是否…...

opencv-33 图像平滑处理-中值滤波cv2.medianBlur()
中值滤波是一种常见的图像处理滤波技术,用于去除图像中的噪声。它的原理是用一个滑动窗口(也称为卷积核)在图像上移动,对窗口中的像素值进行排序,然后用窗口中像素值的中值来替换中心像素的值。这样,中值滤…...

跟CZY一起深入理解C++(1)-一些基础知识
跟CZY一起深入理解C些基础知识 常量constconstexpr 初始化枚举与枚举类分离编译 常量 const 常量亦即不可改变的量(实际上可以暴力破解),那么常量在C中主要有以下几种应用场景 定义常量变量 //如果有以下情况,在GCC上能够破解,而在MSVC上不会改变 // int放在栈区,实际上是可…...
bash变量和参数介绍
bash变量和参数介绍 概述 变量可以让程序和脚本语言用来描述数据。一个变量仅仅是一个标签而已,被指定到计算机内存中存储着数据的某个位置或某些位置的标签。变量一般出现在算术运算操作和数量操纵及字符串解析中。 4.1. 变量替换(Variable Substitution) 变量的名…...

Qt 信号与槽
信号与槽(signal & slot)是Qt编程的基础,使Qt中处理界面各个组件的交互操作变得更加直观和简单。 信号(Signal)就是在特定情况下被发射的事件,如PushButton最常见的信号就是鼠标单击时发射的clicked()…...

目标检测与跟踪 (1)- 机器人视觉与YOLO V8
目录 1、研究背景 2. 算法原理及对比 2.1 点对特征(Point Pairs) 2.2 模板匹配 2.3 霍夫森林 2.4 深度学习 3、YOLO家族模型演变 4、YOLO V8 1、研究背景 机器人视觉识别技术是移动机器人平台十分关键的技术,代表着机器人智能化、自动化…...
mlr3verse vs KM曲线:谁能更精准地预测生存率?
一、引言 生存分析是统计学中一种重要的方法,用于分析个体在特定时间段内生存的概率或生存率。它在医学、流行病学、生物学等领域被广泛应用。通过生存分析,我们可以评估治疗方法的效果、预测疾病进展的风险以及评估特定因素对生存率的影响。 生存率的准…...

TechTool Pro for mac(硬件监测和系统维护工具)
TechTool Pro 是为 Mac OS X 重新设计的全新工具程序,不但保留旧版原有的硬件侦测功能,还可检查系统上其他重要功能,如:网络连接,区域网络等。 TechTool Pro for mac随时监控和保护您的电脑,并可预设定期检…...

排序算法(九大)- C++实现
目录 基数排序 快速排序 Hoare版本(单趟) 快速排序优化 三数取中 小区间优化 挖坑法(单趟) 前后指针法(单趟) 非递归实现(快排) 归并排序 非递归实现(归并&am…...
lettuce连接池的源代码(link)
springboot研究九:lettuce连接池很香,撸撸它的源代码_lettuce springboot_君哥聊技术的博客-CSDN博客...

小白到运维工程师自学之路 第六十二集 (docker持久化与数据卷容器)
一、概述 Docker持久化是指将容器中的数据持久保存在主机上,以便在容器重新启动或迁移时不丢失数据。由于Docker容器是临时和可变的,它们的文件系统默认是易失的,这意味着容器中的任何更改或创建的文件都只存在于此容器的生命周期内。但是&a…...

Linux应用开发之网络套接字编程(实例篇)
服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...

css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
引言 Bitmap(位图)是Android应用内存占用的“头号杀手”。一张1080P(1920x1080)的图片以ARGB_8888格式加载时,内存占用高达8MB(192010804字节)。据统计,超过60%的应用OOM崩溃与Bitm…...
Mysql8 忘记密码重置,以及问题解决
1.使用免密登录 找到配置MySQL文件,我的文件路径是/etc/mysql/my.cnf,有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...
【前端异常】JavaScript错误处理:分析 Uncaught (in promise) error
在前端开发中,JavaScript 异常是不可避免的。随着现代前端应用越来越多地使用异步操作(如 Promise、async/await 等),开发者常常会遇到 Uncaught (in promise) error 错误。这个错误是由于未正确处理 Promise 的拒绝(r…...
在golang中如何将已安装的依赖降级处理,比如:将 go-ansible/v2@v2.2.0 更换为 go-ansible/@v1.1.7
在 Go 项目中降级 go-ansible 从 v2.2.0 到 v1.1.7 具体步骤: 第一步: 修改 go.mod 文件 // 原 v2 版本声明 require github.com/apenella/go-ansible/v2 v2.2.0 替换为: // 改为 v…...
Python的__call__ 方法
在 Python 中,__call__ 是一个特殊的魔术方法(magic method),它允许一个类的实例像函数一样被调用。当你在一个对象后面加上 () 并执行时(例如 obj()),Python 会自动调用该对象的 __call__ 方法…...
Netty自定义协议解析
目录 自定义协议设计 实现消息解码器 实现消息编码器 自定义消息对象 配置ChannelPipeline Netty提供了强大的编解码器抽象基类,这些基类能够帮助开发者快速实现自定义协议的解析。 自定义协议设计 在实现自定义协议解析之前,需要明确协议的具体格式。例如,一个简单的…...