IO 多路复用 来了(最详细版)

IO多路转接select

//为什么要写多进程/多线程的并发服务器?

在进行套接字通信的时候有一些阻塞函数:accept,read、recv,write、send

需要不停的检测客户端链接,需要不停的调用accept,需要占用一个线程或进程进行检测

发送数据:write 、send 如果写缓冲区被写满,阻塞→需要一个单独的线程去处理接收数据:read/recv,对方不给当前终端发送数据,当前终端阻塞->需要单独线程处理数据接收

总结套接字通信过程中有大量的阻塞操作,需要多个线程处理阻塞任务;

细节分析:

accept为啥会阻塞;

使用accept读了监听文件描述符的读缓冲区,检测过程是阻塞的

read,recv为什么会阻塞:

使用这两个检测了通信文件描述符的读缓冲区,阻塞检测

write send 为啥会

检测了通信的写缓冲,如果满了,就阻塞

iO多路转接:委托内核帮助我们检测文件描述符的状态,内核检测完毕之后会给用户一个回馈,用户通过内核反馈,就指定的哪些文件描述符有状态变化,有针对的对这些文件描述符的状态处理

在处理有状态变化的文件描述符的时候:

1.检测到有新链接,建立新链接,调用accept函数(不会阻塞)

2.内核检测到通信的文件描述符读缓冲区有数据==>对端给当前终端发送数据(需要接收read,recv,不会阻塞)

3.内核检测到通信的文件描述符写缓冲区可以写可以使用 write()/send()发送数据 ==> 不阻塞

io多路转接函数掌握两个

select重要

poll

epoll重要

select:

select是如何实现IO转接的?

select是一个跨平台的函数,linux和window平台都可以使用

我们调用这个函数,该函数会调用相对应的平台的系统api,委托操作系统执行某些操作

3.在调用select的时候需要我们通过参数的形式将要检测到文件描述符的集合传递内核中去,内核根据这个集合进行的文件描述符的状态检测

读集合:要检测的这一系列的文件描述符的读缓冲区(监听的加通信的)写集合:委托内核检测集合中的文件描述符对应的写缓冲区是否可写异常集合:检测集合中文件描述符进行读写操作的时候是否有异常  4.内核根据传递的集合中的数据,对文件描述符表进行线性检测,如果有满足条件的文件描述符,内核会通知调用者满足条件:内核如何通知调用者:内核会将用户传递给内核的读写异常集合进行修改,得到新的数据;

5.最终用户得到的信息:

1.知道委托内核检测的数据集合一共有多少文件描述符的变化2.通过检测内核传出的读写异常集合可以判断出是哪一个文件描述符发生了变化

检测是线性的

select函数:

nfds:

readfds:

writefds:

exceptfds:

timeout:

返回值:

0:

检测完成之后,满足条件的文件描述符总个数(三个集合的和

=0:

没有检测到满足条件的文件描述符,超时间到了,强制函数返回

-1:

函数调用失败了

fd_set类型操作函数

内核会把符合条件的读缓冲区的文件描述符返回,因为这是

传入传出出来,有数据的返回读集合,内核会修改集合

程序流程:

1.创监听的套接字

int lfd =socket();

2.绑定

bind();

3.设置监听

listen()

4.初始化要检测的文件描述

fd_set reads;。创建FD_ZERO(lfd,&reads);//清零FD_SET(lfd,&reads);//函数检测集合当中的文件描述符的状态

5.使用select函数检测集合中的文件描述符的状态

![image.png](https://flowus.cn/preview/2fd74c7d-f964-4f97-b19e-34c9ad344d63)![image.png](https://flowus.cn/preview/899fe710-6326-4d5b-808b-53b81e3e5357)![image.png](https://flowus.cn/preview/87165043-1aac-4c16-8a6f-b7a57d1d56a4)

结果集合并不一定和监视集合具有相同的长度。在调用 select 函数时,监视集合是用户空间传递给内核的,它包含了需要监视的所有套接字的文件描述符。

而当 select 函数返回时,返回的结果集合可能会比监视集合小,因为只会返回发生了事件的套接字的文件描述符。也就是说,结果集合中只包含了发生了事件的套接字的文件描述符,而不包含未发生事件的套接字。

因此,结果集合的长度可能比监视集合小,甚至为空。这取决于在监视集合中哪些套接字发生了事件,以及它们的数量。

#include<sys/select.h>
18 using namespace std;
19 int main(){
20
21     // 1.创建监听的套接字
22  int lfd=socket(AF_INET,SOCK_STREAM,0);
23  if(lfd==-1){
24  perror("socket");
25  exit(0);
26  }
27  //2.绑定
28  struct sockaddr_in addr;
29  addr.sin_family =AF_INET;  //IPV4
30  addr.sin_port=htons(8989);//端口,转换大端
31  addr.sin_addr.s_addr=INADDR_ANY;//ip地址为0
32  int ret =bind(lfd,(struct sockaddr*)&addr,sizeof(addr));//绑定
33 if(ret==-1){//如果绑定失败
34 perror("bind");
35 exit(0);
36 }// 3. 设置监听
38 ret =listen(lfd,128);
39 if(ret==-1){
40
41     perror("listen");
42     exit(0);
43 }
44 //4.初始化检测集合、
45  fd_set reads,temp;
46  FD_ZERO(&reads);
47  FD_SET(lfd,&reads);
48  int nfds=lfd;//5.委托内核不停检测集合中的文件描述符
50     while(1){
51         temp=reads;
52         int num=select(nfds+1,&temp,NULL,NULL,NULL);//最后一个是>    什么时候解除阻塞,NULL那就是不解除阻塞,知道检测出有状态变化;,内核传出的都是变化过的集合
53         cout<<num<<endl;
54         for(int i=lfd;i<=nfds;i++){
55             if(i==lfd&&FD_ISSET(lfd,&temp))、{`temp` 中只会保留发生了状态变化的文件描述符,而没有发生变化的文件描述符状态会被清除,不再包含在 `temp` 中。
56                 //建立新连接,这调用绝对不阻塞
57                 int cfd=accept(lfd,NULL,NULL);
58                 //cfd 添加到检测的原始集合
59                 FD_SET(cfd,&reads);
60                 nfds=nfds<cfd?cfd:nfds;//取最大的哪一个
61             }
62             else{
63                 if(FD_ISSET(i,&temp)){
64                     //接受数据
65                     char buf[1024];
66                     memset(buf,0,sizeof(buf));
67                     int len=recv(i,buf,sizeof(buf),0);
68                     if(len==0){
69                         cout<<"客户端断开连接"<<endl;
70                         //将i从原始集合中删除,下次不检查了
71                         FD_CLR(i,&reads);close(i);
73                     }
74                     else if(len>0){
75                     cout<<"recv data :"<<buf<<endl;
76                        send(i,buf,len,0);
77                     }
78                     else{
79                         perror("recv");
80                         break;
81                     }
82                 }
83             }
84         }
86     }
88 close(lfd);89     return 0;90 }

你提出了一个很好的观察。你是对的,将监听套接字 lfd 添加到 reads 集合中并不意味着一定会有连接请求到来。但是在这个特定的代码中,我们可以进行一些假设:

  1. 这段代码运行在一个循环中,并且 select 函数被用于阻塞等待可读事件。

  2. 在循环中,每次调用 select 函数都会检查 reads 集合中的所有文件描述符,包括监听套接字 lfd

  3. 当有连接请求到来时,监听套接字 lfd 会变为可读状态,select 函数会返回,并且 FD_ISSET(lfd, &temp) 条件将会为真,触发建立新连接的代码。

总的来说,在这个代码的设计中,监听套接字 lfd 被添加到了待检测的文件描述符集合中,因此每次调用 select 函数时都会检查其状态。虽然 lfd 被添加到了集合中并不保证一定有连接请求到来,但是在有连接请求到来时,通过检查 lfd 的状态,可以及时响应并处理连接请求。

poll(从select到epoll的过度),

不能跨平台,select有1024最大的并发上线,poll是没有的,select和poll都是线性的,而poll是动态数组,内部是链表,效率不高

epoll(重要)

如果内存是1g,epoll就可以支持10万连接

不能跨平台,只能在linux

支持大并发

IO多路转接函数:

select:

支持跨平台,第一个参数文件描述符在windows无意义为0,linux下的有意义集合最大文件描述符+1检测方式:线性数组,越多越低多次数据拷贝,用户态内核态,内核区拷贝回来传出信息的量:多少文件描述符发生变化了→返回值到底是谁发生变化,需要使用者检测

poll:

不支持跨平台,只支持linux,检测的链接数和内存正相关检测方式:线性链表,动态扩容多次数据拷贝,用户区到内核区,内核区拷贝回来

epoll:

也不支持跨平台,只支持linux检测方式:红黑树;不影响委托epoll检测的文件描述符集合用户区和内核区使用的是同一块内存;(共享内存)花销少传出的信息的量:有多少文件描述符的发送变化了→返回值可以精确到知道哪个文件描述符发生了变化
epoll使用步骤:

epoll:是一个模型,树状模型,需要调用3个函数

1.创建树状模型,没有节点

2。将要检测的节点添加到epoll树上

文件描述符的类型监听的通信的从检查的事件上说:读。写。异常

3.开始委托内核对树上的节点进行检测

4.处理的过程

监听的:建立新连接通信的:接受和发送数据

函数:

头文件#include

//创建一个epoll红黑模型树

int epoll_create(int size);//size值多少无所谓,>0即可,没有实际意义

返回值:

成功:有效的文件描述符,红黑树的根节点;通过这个文件描述符就可以访问创建的实例失败:-1;

对节点的操作函数

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

这是一个阻塞函数;

委托内核检测epoll 树上的文件描述符的状态,如果没有状态变化,该函数一直阻塞

有满足条件的文件描述符被检测到,函数返回

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

参数:

epoll_create()函数返回值,找到epoll的实例

events :传出参数,记录了发生状态变化的所有文件描述符信息

是结构体数组的地址

maxevents:events数组的容量

timeout :超时的时长,和poll一样,

如果为-1,委托内核检测epoll 树上的文件描述符的状态,如果没有状态变化,该函数一直阻塞有满足的就返回如果为0 epoll_wait 调用之后立马返回>0 :规定时间内没有检测到,函数强制解除阻塞

返回值:

成功:有多少文件描述发生的状态变化

基于epoll的tcp服务器的伪代码

int main(){
//创建监听的套接字int lfd =socket();//2. 绑定bind();//3.设置监听listen();//4.创建epoll模型int epfd=epoll_create(size);//5.将需要检测的文件描述符添加到epoll模型中struct epoll_event ev;ev.events=EPOLLIN;ev.data.fd=lfd;epoll_ctl(epfd,epoll_ctl_add,lfd,&ev);//将lfd挂在树上,事件为add添加//6.开始检测struct epoll_event  events[1024];while(1){//不停的检测,不停的调用epoll_waitint num=epoll_wait(epfd,events,1024,-1);//处理num个有状态变化的文件描述符,会放到events结构体数组的前num个for(int i=0;i<num;i++){int curfd=events[i].data.fd;if(curfd==lfd){int cfd =accept(lfd,NULL,NULL);//cfd 添加到epoll模型上ev.data.fd=cfd;ev.events=EPOLLIN;epoll_ctl(epfd,epoll_ctl_add,cfd, &ev);}else{//处理通信char buf[1024];int len =recv (curfd,buf,sizeof(buf),-1);if(len==0){cout<<"客户端断开链接"<<endl;//从epoll树上删除检测节点epoll_ctl(epfd,epoll_ctl_del,curfd,NULL);close(curfd);}else if(len>0){send();}else{perror("recv");}}}}}

执行代码:

18 #include<sys/epoll.h>19 using namespace std;                                             20 int main(){21 22     // 1.创建监听的套接字23  int lfd=socket(AF_INET,SOCK_STREAM,0);24  if(lfd==-1){25  perror("socket");26  exit(0);27  }28  //2.绑定29  struct sockaddr_in addr;30  addr.sin_family =AF_INET;  //IPV431  addr.sin_port=htons(8989);//端口,转换大端32  addr.sin_addr.s_addr=INADDR_ANY;//ip地址为033  int ret =bind(lfd,(struct sockaddr*)&addr,sizeof(addr));//绑定34 if(ret==-1){//如果绑定失败35 perror("bind");36 exit(0);37 }38 // 3. 设置监听 39 ret =listen(lfd,128);40 if(ret==-1){
42     perror("listen");43     exit(0);44 }45 46 //4.创建epoll模型47     int epfd=epoll_create(100);48     if(epfd==-1){49        perror("epoll_create");50        exit(0);51     }52 //5.将要检测的节点添加到epoll模型中53     struct epoll_event ev;54     ev.data.fd=lfd;//将lfd添加到结构体数组中初始化55     ev.events=EPOLLIN;//检测lfd的读缓冲区56      ret=epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);57     if(ret==-1){58         perror("epoll_ctl");59         exit(0);60     }
61  //6.不停的委托内核检测epoll模型中的文件描述符的状态             62   struct epoll_event evs[1024];//evs 是传出方63   int size=sizeof(evs)/sizeof(evs[0]);64     while(1){65       int num=epoll_wait(epfd,evs,size,-1);//-1 就是一直阻塞检测66       cout<<num<<endl;67       //遍历evs68       for(int i=0;i<num;i++){69           //取出数组元素的文件描述符70          int curfd=evs[i].data.fd;71          if(lfd==curfd){72             int cfd=accept(curfd,NULL,NULL);73             //添加结构体数组到检测的原始集合74             ev.events=EPOLLIN;75             ev.data.fd=cfd;76             epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);77          }78          else{79              //通信
80              char buf[1024];81              memset(buf,0,sizeof(buf));82              int len=recv(curfd,buf,sizeof(buf),0);83               if(len==0){84                   cout<<"客户端断开链接"<<endl;85                   epoll_ctl(epfd,EPOLL_CTL_DEL,curfd,NULL);//从ep    oll树删除此节点86                   close(curfd);87               }88               else if(len>0){89                   cout<<"recv data :"<<buf<<endl;90                   send(curfd,buf,len,0);91               }92               else{93                   perror("recv ");94                   exit(0);95               }96          }97 98       }99 
100   }
101     return 0;
102 }

epoll的工作模式(水平模式,边缘模式)

LT :水平工作模式,默认的工作模式,上面的就是,缺省就是默认的意思

![image.png](https://flowus.cn/preview/7d020cf0-3afe-417d-8cac-df1eafea2b86)阻塞非阻塞套接字都支持阻塞指定的接受和发送数据的状态:(缓冲区有关)read、recvwrite、send

场景:

客户端给服务器端发送数据,每次发送1k的数据,服务器使用epoll检测,检测到读缓冲区中有数据,每次接收500字节

发送的快,接收慢

水平模式特点:

读事件:接收端接收的数据少,接收一次数据包接收不完,还有500字节在读缓冲区这种场景下,只要是epoll_wait 检测函数到读缓冲有数据,就会通知用户一次不官有没有读完,只要有数据就通知通知就是epoll_wait ()函数返回,我们就可以处理传出参数中的文件描述符的状态写事件:检测写缓冲是否可用,只有是可写epoll_wait()就会返回

演示:

第一个num是监听的文件描述符发生变化的时候取的1

客户端发送helloworld,nihaoshijie的,服务器端用了好几次,是因为接收的读缓冲区很小,但是epoll检测就返回

客户端收到的是服务器多次返回的结果

ET :边沿触发模式,效率高,通知的次数少(只支持非阻塞模式)

边沿模式,需要我们手动模式,如何设置边沿模式

场景:

客户端给服务器发送数据,每次发送1k的数据,服务器使用epo11检测(边沿模式),检测到读缓冲区中有数据,每次接收500字节发送的快接收的慢

特点:检测的次数变少了,效率高,满足条件的新状态才会通知

读事件:接收端每次收到一条新的数据,epoll_wait()会通知一次通知的这一次内,数据没有接收完,没有将缓冲区的数据全部读出,epoll_wait()也不会通知只通知一次,不管你有没有读完一条数据来了,没读完,就通知一次,第二条新的才会通知写事件:检测写缓冲区是否可用(是否有容量)检测到写缓冲区的可用时候通知一次,再次检测到就不通知了写缓冲区原来是不可用(满了),后来缓冲区可用(不满),epo11_wait()检测到之后通知一次(唯一)

如何设置边沿模式?

在ev.events=EPOLLIN | EPOLLET4![image.png](https://flowus.cn/preview/980218a8-ffca-4fb6-bb88-ffd66a77fcbb)导致缓冲区的数据越积越多,直到新数据来处理**循环队列,先进先出,直到对列被写满才会发送处理,数据不能及时全部读出,无法处理客户端请求,如何解决这个问题?**既然只发生了一次,那么就得在epoll_wait的时候一次性读完方案一:接收端(服务器端)准备一个特别大的内存块,用来存储待接受的数据弊端:内存块多大是特别大,客户端的发送的数据多大不知道,内存块的上限不确定,系统不分配大内存给用户方案二:循环进行数据回收![image.png](https://flowus.cn/preview/6b2f60c9-1bb6-4c90-98fe-4ed36285953a)

如何设置文件描述符的非阻塞?

int flag =fcntl(cfd,F_GETFL)//获取

flag=flag | O_NONBLOCK; 按位或,给flag追加非阻塞

fcntl(cfd,F_SETFL,flag);//将新的属性设置文件描述符中

只需要将cfd通信文件描述符设置为非阻塞,开始通信的时候,直接while把数据通信完

在非阻塞模式下读数据遇到错误

由于是非阻塞,缓存区一直再读,如果没有数据了,非阻塞还是会继续读,然后会报错误read ,recv失败了,返回-1;

错误号 errno=EAGAIN or EWOULDBOLCK 一般情况使用EAGAIN判断就可以了

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

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

相关文章

VULNCON CTF 2021 -- IPS

前言 这个题目折磨了我接近一天&#xff0c;服气了&#xff0c;题目不算难&#xff0c;但是利用写得的疯掉了~~~ 然后这个题目跟之前的不同&#xff0c;之前的题目都是实现一个内核模块&#xff0c;而这个题目是直接实现了一个系统调用&#xff08;&#xff1a;所以这里不存在…

卷积通用模型的剪枝、蒸馏---蒸馏篇--RKD关系蒸馏(以deeplabv3+为例)

本文使用RKD实现对deeplabv3+模型的蒸馏;与上一篇KD蒸馏的方法有所不同,RKD是对展平层的特征做蒸馏,蒸馏的loss分为二阶的距离损失Distance-wise Loss和三阶的角度损失Angle-wise Loss。 一、RKD简介 RKD算法的核心是以教师模型的多个输出为结构单元,取代传统蒸馏学习中以教…

【通信】为什么用复形式表示信号

引入&#xff1a; 一个实例反映复信号和实信号对应关系&#xff08;幅度与相位&#xff09; 复信号的意义 在实际工程中&#xff0c;没有数学意义上的复数信号。再信号与系统中引入复数是为了&#xff1a; ①简化公式&#xff0c;特别是三角函数 ②复数的实部和虚部实际上代…

VBA技术资料MF152:列出工作表中所有单元格的注释

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套&#xff0c;分为初级、中级、高级三大部分&#xff0c;教程是对VBA的系统讲解&#…

FreeRTOS的任务详解、创建与删除

目录 1、任务详解 1.1 什么是任务&#xff1f; 1.2 任务的特点 1.3 任务的状态 1.4 任务的优先级 1.5 任务的堆和栈 2、任务的创建与删除 2.1 相关API 2.2 函数解析 2.2.1 xTaxkCreate() 2.2.2 xTaskCreateStatic() 2.2.3 vTaskDelete() 3、实战案例 3.1 创建两个…

JavaSwing课程设计-实现一个计算器程序

通过JavaSwing技术来实现计算器小程序&#xff0c;效果如下。 源码下载链接 源码下载 博主承诺真实有效&#xff0c;私信可提供支持

JavaEE 多线程详细讲解(2)

1.线程不安全分析 &#xff08;1&#xff09;线程不安全的主要原因就是&#xff0c;系统的抢占式执行&#xff0c;对于内核设计者来说&#xff0c;这是非常方便的一个执行方式&#xff0c;但是这却却导致线程不安全的问题&#xff0c;也有不抢占执行的系统&#xff0c;但是这种…

存储或读取时转换JSON数据

一、 数据库类型 二、使用Hutool工具 存储时将数据转换为JSON数据 获取时将JSON数据转换为对象 发现问题&#xff1a; 原本数据对象是Address 和 Firend但是转换完成后数据变成了JSONArray和JSONObject 三、自定义TypeHandler继承Mybatis的BaseTypeHandler处理器 package …

STL速查

容器 (Containers) 图解容器 支持随机访问 stringarrayvectordeque支持支持支持支持 string 类 构造函数 string(); ------创建一个空的字符串 例如: string str;string(const char* s); ------使用字符串s初始化string(const string& str); ------拷贝构造 赋值操作…

Android GPU渲染屏幕绘制显示基础概念(1)

Android GPU渲染屏幕绘制显示基础概念&#xff08;1&#xff09; Android中的图像生产者OpenGL&#xff0c;Skia&#xff0c;Vulkan将绘制的数据存放在图像缓冲区中&#xff0c;Android中的图像消费SurfaceFlinger从图像缓冲区将数据取出&#xff0c;进行加工及合成。 Surface…

OpenMVS学习笔记(一):WSL编译安装测试

1.CUDA和CUDNN安装 [1] WSL版本cuda安装&#xff1a; >> wget https://developer.download.nvidia.com/compute/cuda/repos/wsl-ubuntu/x86_64/cuda-wsl-ubuntu.pin >> sudo mv cuda-wsl-ubuntu.pin /etc/apt/preferences.d/cuda-repository-pin-600 >> wg…

7个AI驱动的3D模型生成器

老子云AI生成3D模型https://www.laozicloud.com/aiModel 在快速发展的技术世界中&#xff0c;人工智能 (AI) 已经改变了游戏规则&#xff0c;尤其是在 3D 对象生成领域。 AI 驱动的 3D 对象生成器彻底改变了我们创建和可视化 3D 模型的方式&#xff0c;使该过程更加高效、准确…

Star-CCM+通过将所有部件创建一个区域的方式分配至区域后子区域的分离,子区域材料属性的赋值,以及物理连续体的创建方法介绍

前言 上次介绍了将零部件分配至区域的方法与各个方法之间的区别&#xff0c;本文将继续上次的讲解&#xff0c;将其中的“将所有部件分配至一个区域”的应用进行补充。 如下图所示&#xff0c;按照将所有部件创建一个区域的方式分配至区域后&#xff0c;在区域下就会有一个区域…

若依集成mybatis-plus 超详细教程(亲测可用)

文章目录 简介步骤第一步第二步第三步第四步第五步第六步 使用QueryWrapperservice层impl 实现接口类层Mapper层 简介 话不多说 直接跟着下面的教程操作&#xff0c;如果有报错私信我&#xff0c;或者通过博文下面的微信名片加我微信&#xff0c;免费解答哦&#xff01; 步骤 …

opencv图片的旋转-------c++

图片的旋转 /// <summary> /// 图片的旋转 /// </summary> /// <param name"img"></param> /// <param name"angle">旋转角度:正数&#xff0c;则表示逆时针旋转;负数&#xff0c;则表示顺时针旋转</param> /// <…

【吊打面试官系列】Java高并发篇 - 什么是线程调度器(Thread Scheduler)和时间分片(TimeSlicing )?

大家好&#xff0c;我是锋哥。今天分享关于 【什么是线程调度器(Thread Scheduler)和时间分片(TimeSlicing )&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; 什么是线程调度器(Thread Scheduler)和时间分片(TimeSlicing )&#xff1f; 线程调度器是一个操作系统…

2024中国植物资源化妆品创新展在国家植物园成功举办

2024中国植物资源化妆品创新展&#xff08;简称国植美妆展&#xff09;于今年05月06日在北京国家植物园圆满落下帷幕。国植美妆展由中国广告协会化妆品工作委员会与中国抗衰老促进会化妆品产业分会指导&#xff0c;北京华晟德观文化科技发展有限公司主办&#xff0c;于03月30日…

安卓模拟器访问主机局域网

误打误撞能够访问主机局域网了 但是不太懂是因为哪一部分成功的 先记录一下 PC&#xff1a;mac系统 安卓编译器&#xff1a;Android Studio 步骤 只需要在PC上进行设置 1. 在【设置】中&#xff0c;打开已连接的Wi-Fi的【详细信息】 2. TCP/IP --> 配置IPv6&#xff0c;修…

前端组件库之ant-design-vue

在这里记录一个这个组件库我之前没有发现最近才发现的一个很好用的功能&#xff08;应该叫功能吧&#xff1f;&#xff09; 就是 这个flex弹性布局&#xff0c;之前在开发时&#xff0c;一直使用elementUI,是第一次使用这个组件库&#xff0c;所以没有发现这个功能这么好用 你…

WPF控件之StackPanel布局控件

StackPanel别名堆栈panel 使其子元素按照一定方式进行布局&#xff0c;子元素排布方式要么设置为水平排布&#xff0c;要么垂直排布。 属性 Orientation设置排列方式(默认的是垂直排布) : Horizontal水平排布 Vertical 垂直排布 实例 <StackPanel Orientation"Vert…