网站页面建议,怎样安装免费的wordpress,wordpress侧边栏导航代码,清远网站seo本文目录 一、BIO模型二、非阻塞NIO忙轮询三、IO多路复用四、Select()多路复用实现 明确一下IO多路复用的概念#xff1a;IO多路复用能够使得程序同时监听多个文件描述符#xff08;文件描述符fd对应的是内核读写缓冲区#xff09;#xff0c;能够提升程序的性能。
Linux下… 本文目录 一、BIO模型二、非阻塞NIO忙轮询三、IO多路复用四、Select()多路复用实现 明确一下IO多路复用的概念IO多路复用能够使得程序同时监听多个文件描述符文件描述符fd对应的是内核读写缓冲区能够提升程序的性能。
Linux下实现的I/O多路复用的系统调用主要有select、poll、epoll。
一、BIO模型
多进程服务器的缺点就是线程或者进程会消耗资源创建一个子进程会复制虚拟地址空间占用的资源也就多了。线程来说相对来说比较好因为共享了虚拟地址空间然后线程或者进程调度消耗CPU资源。
没有引入多线程/多进程的时候多个客户端来了会在accept或者read/recv部分阻塞导致其他的客户端不能进来。所以通过多线程/进程进行改进在accept地方加入while循环然后创建对应的线程这样可以在线程内部进行读写。不会造成阻塞。
究其根本就是因为accept、read/recv是阻塞的所以导致了要引入多线程进程解决阻塞的问题。并且在线程或者进程当中read和recv也会阻塞。
二、非阻塞NIO忙轮询
非阻塞忙轮询的 这个模型就是设置accept/read不阻塞但是需要一直轮询。缺点就是需要占用更多的CPU和系统资源。
非阻塞的模型如下图所示所以需要某些数据结构来存储现有的client那么每次进行read或者recv的时候就都得遍历每次循环都得调用很多次的系统调用那就是On的复杂度。
为了解决这个问题所以需要使用IO多路复用技术select/poll/epoll.
三、IO多路复用
下图是select、poll的模式就是设置一个代理来帮我们进行管理。委托内核来帮我们管理检测对应的数据。就是假设有100个fd那么需要内核需要帮我们管理这100个fd内核其实检测fd中的读缓冲区是否有数据。有数据就说明我们需要获取数据了。底层是用二进制位的形式来检查就是设置标志位是否为1
缺点就是只会通知有多少个fd有动静但是具体是哪个fd需要我们挨个遍历一遍。 epoll相对于上面的优点就是能够通知有多少个fd有动静然后还会说明具体是哪些fd。 四、Select()多路复用实现
select的主要思想就是
首先需要构造一个包含文件描述符的列表并将需要监听的文件描述符加入其中。
接着调用一个系统函数该函数会阻塞地监听列表中的文件描述符。这个监听过程是由内核完成的只有当列表中的一个或多个文件描述符准备好进行I操作/O时函数才会返回。
当函数返回时它会告知进程有多少个以及是哪些文件描述符已经准备好进行I/O操作。
相关的头文件如下。
#include sys/time.h
#include sys/types.h
#include unistd.h
#include sys/select.hint select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);参数说明如下
nfds委托内核检测的最大文件描述符的值1。
readfds要检测的文件描述符中的读的集合委托内核检测哪些文件描述符的读属性读缓冲区是否有数据。一般只检测读操作读是被动的接收数据检测的就是读缓冲区。只有当对方发送来数据才能检测到。fd_set数据类型是整数如果对其进行sizeof那么会获得一个整数。比如sizeof(fd_set)128也就是128个字节对应1024位可以保存1024的标志位每个位对应一个文件描述符这是一个传入传出参数。就是我们先置为哪些为1然后把这个作为参数传给内核内核只会对这个1进行检测。
writefds是要检测的文件描述符的写的集合委托内核检测哪些文件描述符有写的属性。委托内核检测缓冲区是不是还可以写数据不满的就可以写。
exceptfds检测发生异常的文件描述符的集合。
timeout设置的超时时间。timeval是一个结构体有long tv_sec和long tv_usec两个属性一个对应秒一个对应微秒设置超时时间。设置NULL是永久阻塞直到检测到了对应的文件描述符有变化。tv_sec 0 ,tv_usec 0表示不阻塞。tv_sec 0 ,tv_usec 0表示阻塞对应的时间。
select函数返回-1表示失败返回n表示集合中检测到了有n个文件描述发生了变化。
// 将参数文件描述符fd对应的标志位设置为0
void FD_CLR(int fd, fd_set *set);// 判断fd对应的标志位是0还是1 返回值 fd对应的标志位的值0返回0 1返回1
int FD_ISSET(int fd, fd_set *set);// 将参数文件描述符fd 对应的标志位设置为1
void FD_SET(int fd, fd_set *set);// fd_set一共有1024 bit, 全部初始化为0
void FD_ZERO(fd_set *set);通过下面的示意图我们能够很清晰的看到select的一个作用过程。 fd_set是一个结构体用于 select 和 pselect 函数的数据结构用于表示一组文件描述符file descriptors。它的实现基于位掩码bitmask通过将文件描述符的编号映射到位掩码中的特定位来管理文件描述符集合。
long int表示8个字节。typedef long int __fd_mask; 定义了 __fd_mask 为 long int 类型用于表示位掩码。每个 __fd_mask 可以存储多个文件描述符的状态。
__FD_SETSIZE 和 __NFDBITS__FD_SETSIZE 是 fd_set 能够管理的最大文件描述符数量默认值通常是 1024。 __NFDBITS 是每个 __fd_mask 可以表示的文件描述符数量。由于 __fd_mask 是 long int 类型通常为 64 位在 64 位系统上因此 __NFDBITS 通常是 64。
fds_bits 或 __fds_bitsfd_set 结构体中包含一个数组数组的类型是 __fd_mask数组的大小是 __FD_SETSIZE / __NFDBITS。这个数组用于存储文件描述符的状态。每个 __fd_mask 元素可以表示 __NFDBITS 个文件描述符。
这个数组的大小是 __FD_SETSIZE / __NFDBITS例如如果 __FD_SETSIZE 1024__NFDBITS 64则数组大小为 1024 / 64 16。每个 __fd_mask 元素可以表示 64 个文件描述符因此整个数组可以表示 1024 个文件描述符。 我们来看一个简单的select的服务端代码。
#include stdio.h
#include arpa/inet.h
#include unistd.h
#include stdlib.h
#include string.h
#include sys/select.hint main() {// 创建socketint lfd socket(PF_INET, SOCK_STREAM, 0);struct sockaddr_in saddr;saddr.sin_port htons(9999);saddr.sin_family AF_INET;saddr.sin_addr.s_addr INADDR_ANY;// 绑定bind(lfd, (struct sockaddr *)saddr, sizeof(saddr));// 监听listen(lfd, 8);// 创建一个fd_set的集合存放的是需要检测的文件描述符fd_set rdset, tmp;FD_ZERO(rdset);FD_SET(lfd, rdset);int maxfd lfd;while(1) {tmp rdset;// 调用select系统函数让内核帮检测哪些文件描述符有数据int ret select(maxfd 1, tmp, NULL, NULL, NULL);if(ret -1) {perror(select);exit(-1);} else if(ret 0) {continue;} else if(ret 0) {// 说明检测到了有文件描述符的对应的缓冲区的数据发生了改变if(FD_ISSET(lfd, tmp)) {// 表示有新的客户端连接进来了struct sockaddr_in cliaddr;int len sizeof(cliaddr);int cfd accept(lfd, (struct sockaddr *)cliaddr, len);// 将新的文件描述符加入到集合中FD_SET(cfd, rdset);// 更新最大的文件描述符maxfd maxfd cfd ? maxfd : cfd;}for(int i lfd 1; i maxfd; i) {if(FD_ISSET(i, tmp)) {// 说明这个文件描述符对应的客户端发来了数据char buf[1024] {0};int len read(i, buf, sizeof(buf));if(len -1) {perror(read);exit(-1);} else if(len 0) {printf(client closed...\n);close(i);FD_CLR(i, rdset);} else if(len 0) {printf(read buf %s\n, buf);write(i, buf, strlen(buf) 1);}}}}}close(lfd);return 0;
}client端对应代码如下。
#include stdio.h
#include arpa/inet.h
#include stdlib.h
#include unistd.h
#include string.hint main() {// 创建socketint fd socket(PF_INET, SOCK_STREAM, 0);if(fd -1) {perror(socket);return -1;}struct sockaddr_in seraddr;inet_pton(AF_INET, 127.0.0.1, seraddr.sin_addr.s_addr);seraddr.sin_family AF_INET;seraddr.sin_port htons(9999);// 连接服务器int ret connect(fd, (struct sockaddr *)seraddr, sizeof(seraddr));if(ret -1){perror(connect);return -1;}int num 0;while(1) {char sendBuf[1024] {0};sprintf(sendBuf, send data %d, num);write(fd, sendBuf, strlen(sendBuf) 1);// 接收int len read(fd, sendBuf, sizeof(sendBuf));if(len -1) {perror(read);return -1;}else if(len 0) {printf(read buf %s\n, sendBuf);} else {printf(服务器已经断开连接...\n);break;}// sleep(1);usleep(1000);}close(fd);return 0;
}