SOCKET编程(5):IO复用

IO复用

多进程/线程并发模型,为每个sockets分配一个进程/线程

I/O(多路)复用,采用单个进/线程就可以管理多个socket

I/O复用有3种方案:

  • select
  • poll
  • epoll

select

I/O多路复用详解

27、fd_set与FD_SETSIZE详解

详解fd_set结构体

fd_set结构体

#include <sys/select.h>#define FD_SETSIZE 1024
#define NFDBITS (8 * sizeof(unsigned long))
#define __FDSET_LONGS (FD_SETSIZE/NFDBITS)typedef struct {unsigned long fds_bits[__FDSET_LONGS];
} fd_set;或者typedef struct{long int fds_bits[32];
}fd_set;

fd_set 是文件描述符 fd 的集合,由于每个进程可打开的文件描述符默认值为1024,fd_set可记录的 fd 个数上限也是1024个

fd_set 采用位图 bitmap 结构,是一个大小为32的 long 型数组,每一个 bit 代表一个描述符是否被监视(类似于一个32x32的矩阵)

操作函数

#include <sys/select.h>
#include <sys/time.h>
void FD_SET(int fd, fd_set *fdset);
void FD_CLR(int fd, fd_set *fdset);
void FD_ISSET(int fd, fd_set *fdset);
void FD_ZERO(fd_set *fdset);

FD_ZERO(&fdset); /将set清零使集合中不含任何fd,清空fdset与所有文件句柄的联系/
FD_SET(fd, &fdset); /将fd加入set集合,建立文件句柄fd与fdset的联系/
FD_CLR(fd, &fdset); /将fd从set集合中清除,清除文件句柄fd与fdset的联系/
FD_ISSET(fd, &fdset); /在调用select()函数后,用FD_ISSET来检测fd是否在set集合中,当检测到fd在set中则返回真,否则,返回假(0)/

select()函数

// nfds:fds中最大fd的值加1
// readfds: 读数据文件描述符集合
// writefds: 写数据文件描述符集合
// exceptfds: 异常情况的文件描述符集合
// timeout: 该方法阻塞的超时时间
int select (int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);struct timeval {long tv_sec;  //秒long tv_usec; //毫秒
}
  • 用户进程通过 select 系统调用把 fd_set 结构的数据拷贝到内核,由内核来监视并判断哪些连接有数据到来,如果有连接准备好数据,select 系统调用就返回
  • select 返回后,用户进程只知道某个或某几个连接有数据,但并不知道是哪个连接。所以需要遍历 fds 中的每个 fd, 当该 fd 被置位时,代表该 fd 表示的连接有数据需要被读取。然后我们读取该 fd 的数据并进行业务操作
  • select 第一个参数需要传入最大fd值加1的数值,目的是为了用户能自定义监视的 fd 范围,防止不必要资源消耗
  • 操作系统会复用用户进程传入的 fd_set 变量,来作为出参,所以我们传入的 fd_set 返回时已经被内核修改过了
  • select 的方式选择让内核来帮我们监视这些 fd,当有数据可读时就通知我们,避免listenfd在accept()时阻塞,提升了效率

返回值:

  • **>0:**有事件发生
  • **=0:**timeout,超时
  • **<0:**出错

示例程序

  • tcpseletc.cpp

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <sys/fcntl.h>// 初始化服务端的监听端口。
    int initserver(int port);int main(int argc,char *argv[])
    {if (argc != 2){printf("usage: ./tcpselect port\n"); return -1;}// 初始化服务端用于监听的socket。int listensock = initserver(atoi(argv[1]));printf("listensock=%d\n",listensock);if (listensock < 0){printf("initserver() failed.\n"); return -1;}fd_set readfdset;  // 读事件的集合,包括监听socket和客户端连接上来的socket。int maxfd;  // readfdset中socket的最大值。// 初始化结构体,把listensock添加到集合中。FD_ZERO(&readfdset);FD_SET(listensock,&readfdset);maxfd = listensock;while (1){// 调用select函数时,会改变socket集合的内容,所以要把socket集合保存下来,传一个临时的给select。fd_set tmpfdset = readfdset;int infds = select(maxfd+1,&tmpfdset,NULL,NULL,NULL);// printf("select infds=%d\n",infds);// 返回失败。if (infds < 0){printf("select() failed.\n"); perror("select()"); break;}// 超时,在本程序中,select函数最后一个参数为空,不存在超时的情况,但以下代码还是留着。if (infds == 0){printf("select() timeout.\n"); continue;}// 检查有事情发生的socket,包括监听和客户端连接的socket。// 这里是客户端的socket事件,每次都要遍历整个集合,因为可能有多个socket有事件。for (int eventfd=0; eventfd <= maxfd; eventfd++){if (FD_ISSET(eventfd,&tmpfdset)<=0) continue;     //判断时用tmpfdset集合if (eventfd==listensock){ // 如果发生事件的是listensock,表示有新的客户端连上来。struct sockaddr_in client;socklen_t len = sizeof(client);int clientsock = accept(listensock,(struct sockaddr*)&client,&len);if (clientsock < 0){printf("accept() failed.\n"); continue;}printf ("client(socket=%d) connected ok.\n",clientsock);// 把新的客户端socket加入集合,readfdset集合,注意区别何时用tmpfdset何时用readfdsetFD_SET(clientsock,&readfdset);if (maxfd < clientsock) maxfd = clientsock;continue;}else{// 客户端有数据过来或客户端的socket连接被断开。char buffer[1024];memset(buffer,0,sizeof(buffer));// 读取客户端的数据。ssize_t isize=read(eventfd,buffer,sizeof(buffer));// 发生了错误或socket被对方关闭。if (isize <=0){printf("client(eventfd=%d) disconnected.\n",eventfd);close(eventfd);  // 关闭客户端的socket。FD_CLR(eventfd,&readfdset);  // 从readfdset集合中移去客户端的socket。// 重新计算maxfd的值,注意,只有当eventfd==maxfd时才需要计算。if (eventfd == maxfd){for (int ii=maxfd;ii>0;ii--){if (FD_ISSET(ii,&readfdset)){maxfd = ii; break;}}printf("maxfd=%d\n",maxfd);}continue;}printf("recv(eventfd=%d,size=%d):%s\n",eventfd,isize,buffer);// 把收到的报文发回给客户端。write(eventfd,buffer,strlen(buffer));}}}return 0;
    }// 初始化服务端的监听端口。
    int initserver(int port)
    {int sock = socket(AF_INET,SOCK_STREAM,0);if (sock < 0){printf("socket() failed.\n"); return -1;}// Linux如下int opt = 1; unsigned int len = sizeof(opt);setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,len);setsockopt(sock,SOL_SOCKET,SO_KEEPALIVE,&opt,len);struct sockaddr_in servaddr;servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(port);if (bind(sock,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0 ){printf("bind() failed.\n"); close(sock); return -1;}if (listen(sock,5) != 0 ){printf("listen() failed.\n"); close(sock); return -1;}return sock;
    }
    

select缺陷与不足

  1. 可监控的文件描述符数量最大为 1024 个,就代表最大能支持的并发为1024,这个是操作系统内核决定的
  2. 用户进程的文件描述符集合 fd_set 每次都需要从用户进程拷贝到内核,有一定的性能开销
  3. select 函数返回,我们只知道有文件描述符满足要求,但不知道是哪个,所以需要遍历所有文件描述符,复杂度为O(n)
  4. select 机制的这些特性在高并发网络服务器动辄几万几十万并发连接的场景下是低效的

poll

poll 是另一种I/O多路复用的实现方式,它解决了 select 1024个文件描述符的限制问题

poll 是使用 pollfd 结构来替代了 selectfd_set 位图,以解决 1024 的文件描述符个数限制

struct pollfd 
{int   fd;         /* file descriptor */short events;     /* requested events */short revents;    /* returned events */
};
  • fd 表示要监视的文件描述符
  • events 表示要监视的事件,比如输入、输出或异常
  • revents 表示返回的标志位,标识哪个事件有信息到来,处理完成后记得重置标志位

poll 函数的定义

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
  • poll 函数的第一个参数传入了一个自定义的 pollfd 的数组,原则上已经没有了个数的限制
  • poll 除了解决了 select 存在的文件描述符个数的限制,并没有解决 select 存在的其他问题(拷贝轮询)
  • selectpoll 都会随着监控的文件描述符数量增加而性能下降,因此也不太适合高并发场景

epoll

epoll 使用一个文件描述符管理多个描述符,省去了大量文件描述符频繁在用户态和内核态之间拷贝的资源消耗

epoll 操作过程有三个非常重要的接口

epoll_create()函数

/* Creates an epoll instance.  Returns an fd for the new instance.The "size" parameter is a hint specifying the number of filedescriptors to be associated with the new instance.  The fdreturned by epoll_create() should be closed with close().  */
extern int epoll_create (int __size) __THROW;/* Same as epoll_create but with an FLAGS parameter.  The unused SIZEparameter has been dropped.  */
extern int epoll_create1 (int __flags) __THROW;

epoll_create() 方法生成一个 epoll 专用的文件描述符(创建一个 epoll 的句柄)

参数 size 在新版本中没有具体意义,填一个大于0的任意值即可

epoll_ctl()函数

/* Manipulate an epoll instance "epfd". Returns 0 in case of success,-1 in case of error ( the "errno" variable will contain thespecific error code ) The "op" parameter is one of the EPOLL_CTL_*constants defined above. The "fd" parameter is the target of theoperation. The "event" parameter describes which events the calleris interested in and any associated user data.  */
extern int epoll_ctl (int __epfd, int __op, int __fd,struct epoll_event *__event) __THROW;
  • epfdepoll 专用的文件描述符,epoll_create() 的返回值
  • op:表示添加、修改、删除的动作,用三个宏来表示:
/* Valid opcodes ( "op" parameter ) to issue to epoll_ctl().  */
#define EPOLL_CTL_ADD 1	/* Add a file descriptor to the interface.  */
#define EPOLL_CTL_DEL 2	/* Remove a file descriptor from the interface.  */
#define EPOLL_CTL_MOD 3	/* Change file descriptor epoll_event structure.  */
  • fd:需要监听的文件描述符
  • event:告诉内核要监听的事件

epoll_event结构体

前端 详解epoll_events结构体

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_PACKED;

定义了枚举类型的events

enum EPOLL_EVENTS{EPOLLIN = 0x001,
#define EPOLLIN EPOLLINEPOLLPRI = 0x002,
#define EPOLLPRI EPOLLPRIEPOLLOUT = 0x004,
#define EPOLLOUT EPOLLOUTEPOLLRDNORM = 0x040,
#define EPOLLRDNORM EPOLLRDNORMEPOLLRDBAND = 0x080,
#define EPOLLRDBAND EPOLLRDBANDEPOLLWRNORM = 0x100,
#define EPOLLWRNORM EPOLLWRNORMEPOLLWRBAND = 0x200,
#define EPOLLWRBAND EPOLLWRBANDEPOLLMSG = 0x400,
#define EPOLLMSG EPOLLMSGEPOLLERR = 0x008,
#define EPOLLERR EPOLLERREPOLLHUP = 0x010,
#define EPOLLHUP EPOLLHUPEPOLLRDHUP = 0x2000,
#define EPOLLRDHUP EPOLLRDHUPEPOLLEXCLUSIVE = 1u << 28,
#define EPOLLEXCLUSIVE EPOLLEXCLUSIVEEPOLLWAKEUP = 1u << 29,
#define EPOLLWAKEUP EPOLLWAKEUPEPOLLONESHOT = 1u << 30,
#define EPOLLONESHOT EPOLLONESHOTEPOLLET = 1u << 31
#define EPOLLET EPOLLET};

epoll_wait()函数

/* Wait for events on an epoll instance "epfd". Returns the number oftriggered events returned in "events" buffer. Or -1 in case oferror with the "errno" variable set to the specific error code. The"events" parameter is a buffer that will contain triggeredevents. The "maxevents" is the maximum number of events to bereturned ( usually size of "events" ). The "timeout" parameterspecifies the maximum wait time in milliseconds (-1 == infinite).This function is a cancellation point and therefore not marked with__THROW.  */
extern int epoll_wait (int __epfd, struct epoll_event *__events,int __maxevents, int __timeout);

epoll_wait() 方法等待事件的产生,类似 select 调用

  • epfdepoll 专用的文件描述符,epoll_create() 的返回值
  • events:分配好的 epoll_event 结构体数组,epoll 将会把发生的事件赋值到 events 数组中
  • maxevents:告诉内核 events 数组的大小
  • timeout:超时时间,单位毫秒,为 -1 时,方法为阻塞

值得注意的是epoll_wait()函数只能获取是否有注册事件发生,至于这个事件到底是什么、从哪个 socket 来、发送的时间、包的大小等等信息,统统不知道。这就好比一个人在黑黢黢的山洞里,只能听到声响,至于这个声音是谁发出的根本不知道。因此我们就需要struct epoll_event来帮助我们读取信息

实例

  • tcpepoll.cpp

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <arpa/inet.h>
    #include <netinet/in.h>
    #include <sys/epoll.h>
    #include <sys/socket.h>
    #include <sys/types.h>#define MAXEVENTS 100// 把socket设置为非阻塞的方式。
    int setnonblocking(int sockfd);// 初始化服务端的监听端口。
    int initserver(int port);int main(int argc, char *argv[])
    {if (argc != 2){printf("usage:./tcpepoll port\n");return -1;}// 初始化服务端用于监听的socket。int listensock = initserver(atoi(argv[1]));printf("listensock=%d\n", listensock);if (listensock < 0){printf("initserver() failed.\n");return -1;}int epollfd;char buffer[1024];memset(buffer, 0, sizeof(buffer));// 创建一个描述符epollfd = epoll_create(1);// 添加监听描述符事件struct epoll_event ev;ev.data.fd = listensock;ev.events = EPOLLIN;epoll_ctl(epollfd, EPOLL_CTL_ADD, listensock, &ev);while (1){struct epoll_event events[MAXEVENTS]; // 存放有事件发生的结构数组。// 等待监视的socket有事件发生。int infds = epoll_wait(epollfd, events, MAXEVENTS, -1);// printf("epoll_wait infds=%d\n",infds);// 返回失败。if (infds < 0){printf("epoll_wait() failed.\n");perror("epoll_wait()");break;}// 超时。if (infds == 0){printf("epoll_wait() timeout.\n");continue;}// 遍历有事件发生的结构数组。for (int ii = 0; ii < infds; ii++){if ((events[ii].data.fd == listensock) && (events[ii].events & EPOLLIN)){// 如果发生事件的是listensock,表示有新的客户端连上来。struct sockaddr_in client;socklen_t len = sizeof(client);int clientsock = accept(listensock, (struct sockaddr *)&client, &len);if (clientsock < 0){printf("accept() failed.\n");continue;}// 把新的客户端添加到epoll中。memset(&ev, 0, sizeof(struct epoll_event));ev.data.fd = clientsock;ev.events = EPOLLIN;epoll_ctl(epollfd, EPOLL_CTL_ADD, clientsock, &ev);printf("client(socket=%d) connected ok.\n", clientsock);continue;}else if (events[ii].events & EPOLLIN){// 客户端有数据过来或客户端的socket连接被断开。char buffer[1024];memset(buffer, 0, sizeof(buffer));// 读取客户端的数据。ssize_t isize = read(events[ii].data.fd, buffer, sizeof(buffer));// 发生了错误或socket被对方关闭。if (isize <= 0){printf("client(eventfd=%d) disconnected.\n", events[ii].data.fd);// 把已断开的客户端从epoll中删除。memset(&ev, 0, sizeof(struct epoll_event));ev.events = EPOLLIN;ev.data.fd = events[ii].data.fd;epoll_ctl(epollfd, EPOLL_CTL_DEL, events[ii].data.fd, &ev);close(events[ii].data.fd); //或者一行关闭命令即可continue;}printf("recv(eventfd=%d,size=%d):%s\n", events[ii].data.fd, isize, buffer);// 把收到的报文发回给客户端。write(events[ii].data.fd, buffer, strlen(buffer));}}}close(epollfd);return 0;
    }// 初始化服务端的监听端口。
    int initserver(int port)
    {int sock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0){printf("socket() failed.\n");return -1;}// Linux如下int opt = 1;unsigned int len = sizeof(opt);setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, len);setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &opt, len);struct sockaddr_in servaddr;servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(port);if (bind(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0){printf("bind() failed.\n");close(sock);return -1;}if (listen(sock, 5) != 0){printf("listen() failed.\n");close(sock);return -1;}return sock;
    }// 把socket设置为非阻塞的方式。
    int setnonblocking(int sockfd)
    {if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0) | O_NONBLOCK) == -1)return -1;return 0;
    }
    

小结

epoll 底层使用了 RB-Tree 红黑树和 list 链表实现。内核创建了红黑树用于存储 epoll_ctl 传来的 socket,另外创建了一个 list 链表,用于存储准备就绪的事件

epoll_wait 调用时,仅仅观察这个 list 链表里有没有数据即可。有数据就返回,没有数据就阻塞。所以,epoll_wait 非常高效,通常情况下即使我们要监控百万计的连接,大多一次也只返回很少量准备就绪的文件描述符而已,所以,epoll_wait 仅需要从内核态拷贝很少的文件描述符到用户态

epoll 相比于 selectpoll,它更高效的本质在于:

  1. 减少了用户态和内核态文件描述符状态的拷贝,epoll 只需要一个专用的文件句柄即可
  2. 减少了文件描述符的遍历,selectpoll 每次都要遍历所有的文件描述符,用来判断哪个连接准备就绪;epoll 返回的是准备就绪的文件描述符,效率大大提高
  3. 没有并发数量的限制,性能不会随文件描述符数量的增加而下降

IO复用总结

select 是较早实现的一种I/O多路复用技术,但它最明显的缺点就是有 1024 个文件描述符数量的限制,也就导致它无法满足高并发的需求

poll 一定程度上解决了 select 文件描述符数量的限制,但和 select 一样,仍然存在文件描述符状态在用户态和内核态的频繁拷贝,和遍历所有文件描述符的问题,这导致了在面对高并发的实现需求时,它的性能不会很高

epoll 高效地解决了以上问题,首先使用一个特殊的文件描述符,解决了用户态和内核态频繁拷贝的问题;其次 epoll_wait 返回的是准备就绪的文件描述符,省去了无效的遍历;再次,底层使用红黑树和链表的数据结构,更加高效地实现连接的监视

工作中常用的 redis、nginx 都是使用了 epoll 这种I/O复用模型,通过单线程就实现了10万以上的并发访问

epoll 不一定任何情况下都比 select 高效,需要根据具体场景。比如并发不是很高,且大部分都是活跃的 socket,那么也许 select 会比 epoll 更加高效,因为 epoll 会有更多次的系统调用,用户态和内核态会有更加频繁的切换

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

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

相关文章

新闻资讯微信小程序开发后端+php【附源码,文档说明】

博主介绍&#xff1a;✌IT徐师兄、7年大厂程序员经历。全网粉丝15W、csdn博客专家、掘金/华为云//InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3…

管理科学SCI、SSCI双检索,自引率低,无预警风险,对国人相当友好!

一、期刊名称 Journal of Organizational and End User Computing 二、期刊简介概况 期刊类型&#xff1a;SSCI 学科领域&#xff1a;管理学 影响因子&#xff1a;6.5 中科院分区&#xff1a;2区 出版方式&#xff1a;开放出版 版面费&#xff1a;$3300 三、期刊征稿范围…

探秘钱塘高中,筑梦未来之旅————杭州市钱塘高级中学

晨曦微露&#xff0c;书生琅琅&#xff0c;古韵今风交织在这方学府&#xff0c;滔滔的钱塘江畔&#xff0c;杭州市钱塘高级中学屹立于此。这所学校自1958年建校伊始&#xff0c;走过几十年的光辉岁月&#xff0c;一直致力于提供优质的教育资源。 近年来&#xff0c;学校获得多项…

Learning C# Programming with Unity 3D

作者&#xff1a;Alex Okita 源码地址&#xff1a;GitHub - badkangaroo/UnityProjects: A repo for all of the projects found in the book. 全书 686 页。

Java常见数据结构---八大结构

前言&#xff1a; 数据结构是计算机底层存储、组织数据的方式。是指数据相互之间是以什么方式排列在一起的。 通常情况下&#xff0c;精心选择的数据结构可以带来更高的运行或者存储效率 常见的八大数据结构&#xff1a; 栈&#xff1a; 思想&#xff1a; 栈是一种数据结构&…

大数据项目中的拉链表(hadoop,hive)

缓慢渐变维 拉链表 拉链表&#xff0c;可实现数据快照&#xff0c;可以将历史和最新数据保存在一起 如何实现: 在原始数据增加两个新字段 起始时间&#xff08;有效时间&#xff1a;什么时候导入的数据的时间&#xff09;&#xff0c;结束时间&#xff08;默认的结束时间为99…

运筹系列92:vrp算法包VROOM

1. 介绍 VROOM is an open-source optimization engine written in C20 that aim at providing good solutions to various real-life vehicle routing problems (VRP) within a small computing time. 可以解决如下问题&#xff1a; TSP (travelling salesman problem) CVRP …

九、 个人信息出境标准合同的签署及备案流程是怎样的?

为指导和帮助个人信息处理者规范有序备案个人信息出境标准合同&#xff0c;国家网信办结合此前备案实践经验发布了《标准合同备案指南&#xff08;第二版&#xff09;》&#xff0c;并就个人信息出境标准合同备案的适用范围、备案方式、备案流程和材料以及咨询、举报联系方式等…

F5 BIG-IP Next Central Manager SQL注入漏洞(CVE-2024-26026、CVE-2024-21793)

0x01 产品简介 BIG-IP Next Central Manager是BIG-IP Next的原生默认用户界面,它可跨平台管理BIG-IP Next实例。BIG-IP Next是F5 Networks公司推出的一款下一代BIG-IP软件,提供了多云应用安全和应用交付服务。 0x02 漏洞概述 CVE-2024-26026:BIG-IP Next Central Manager…

产品推荐 | 基于AMD Virtex 7 FPGA VC709 的高速连接功能开发板

01 产品概述 Virtex™ 7 FPGA VC709 连接功能套件是一款速率为 40Gb/s 的高速平台&#xff0c;您可以通过评估和开发连接功能&#xff0c;迅速为包含所有必要软硬件和 IP 核的高带宽和高性能应用提供强大的支持。它包括一个含有 PCI Express Gen 3、Northwest Logic 公司推出的…

4.1 文本相似度(二)

目录 1 文本相似度评估 2 代码 2.1 load_dataset 方法 2.2 AutoTokenizer、AutoModelForSequenceClassification 1 文本相似度评估 对两个文本拼接起来&#xff0c;然后作为一个样本喂给模型&#xff0c;作为一个二分类的任务&#xff1b; 数据处理的方式以及训练的基本流程…

c 指针基础

/* 指针练习*/ #include <stdio.h> #include <stdlib.h> void printAll(int n1, int n2, int *p1, int *p2); int main(){ //赋值操作语法演示 int num1 1111; int num2 2222; int *prt1 &num1; int *prt2 &num2; printAll(num1, num2, prt1…

maven .lastUpdated文件作用

现象 有时候我在用maven管理项目时会发现有些依赖报错&#xff0c;这时你可以看一下本地仓库中是否有.lastUpdated文件&#xff0c;也许与它有关。 原因 有这个文件就表示依赖下载过程中发生了错误导致依赖没成功下载&#xff0c;可能是网络原因&#xff0c;也有可能是远程…

平面设计基础指南:从零开始的学习之旅!

平面设计师主要做什么&#xff1f; 平面设计师通过创建视觉概念来传达信息。他们创造了从海报和广告牌到包装、标志和营销材料的所有内容&#xff0c;并通过使用形状、颜色、排版、图像和其他元素向观众传达了他们的想法。平面设计师可以在内部工作&#xff0c;专门为品牌创建…

Mac安装jadx

1、使用命令brew安装 : brew install jadx 输入完命令,等待安装完毕 备注&#xff08;关于Homebrew &#xff09;&#xff1a; Homebrew 是 MacOS 下的包管理工具&#xff0c;类似 apt-get/apt 之于 Linux&#xff0c;yum 之于 CentOS。如果一款软件发布时支持了 homebrew 安…

mac定时任务、自启动任务

https://quail.ink/mynotes/p/mac-startup-configuration-detailed-explanation <?xml version"1.0" encoding"UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.d…

【2024年5月备考新增】】 考前篇(2)《官方平台 - 考生模拟练习平台常用操作(一)》

软考考生常用操作说明 说明:模拟作答系统是旨在让考生熟悉计算机化考试环境和作答方式,模拟作答不保存考生作答 历史记录。考试题型、题量、分值、界面及文字内容以正式考试答题系统为准。 1 如何标记试题、切换试题 2 简答题如何查看历史记录、切换输入法 3 选做题,已作答…

游戏找不到steam_api64.dll如何解决,介绍5种简单有效的方法

面对“找不到steam_api64.dll&#xff0c;无法继续执行代码”的问题&#xff0c;许多游戏玩家或软件使用者可能会感到手足无措。这个错误提示意味着你的计算机系统在尝试运行某个游戏或应用程序时&#xff0c;无法定位到一个至关重要的动态链接库文件——steam_api64.dll&#…

《深入Linux内核架构》第3章 内存管理(6)

目录 3.5.7 内核中不连续页的分配 3.5.8 内核映射 本专栏文章将有70篇左右&#xff0c;欢迎关注&#xff0c;订阅后续文章。 本节讲解vmalloc, vmap&#xff0c;kmap原理。 3.5.7 内核中不连续页的分配 kmalloc函数&#xff1a;分配物理地址和虚拟地址都连续的内存。 kmall…

MongoDB聚合运算符:$type

MongoDB聚合运算符&#xff1a;$type 文章目录 MongoDB聚合运算符&#xff1a;$type语法使用可用的类型 举例 $type聚合运算符用来返回指定参数的BSON类型的字符串。。 语法 { $type: <expression> }<expression>可以是任何合法的表达式。 使用 不像查询操作符$…