当前位置: 首页 > news >正文

学习记录——day34 IO多路复用 fcntl select poll select实现聊天室

目录

一、IO多路复用引入

二、非阻塞型IO

1、 fcntl

 三、多路文件IO

1、原理:内核监视对象套接字的缓冲区变化

2、select模型

select注意事项

 描述符操作函数

3、 select实现并发服务器

1)服务器端

2)客服端

4、 select实现聊天室(无姓名)

1)服务器端

2)客服端

5、poll模型

poll函数注意事项

 poll实现并发服务器


一、IO多路复用引入

对于用多进程/多线程实现的并发服务器而言:

        1)可连接的客服端数量搜最大进程/线程数限制

        2)进程阻塞占用时间片会影响效率

而对于IO多路复用实现的并发服务器:

        1)连接数量没有限制(select 受文件描述符最大数量限制-》1024)   

        2)由一个进程实现,不会出现时间片资源占用     

        3)事件触发才执行,执行效率和资源利用率较高

二、非阻塞型IO

1、 fcntl

原型:int fcntl(int fd, int cmd, ... /* arg */ );
调用:int flag = fcntl(描述符,F_GETFL)
     fcntl(描述符,F_SETFL,flag)
功能描述:设置或者获取文件的各项属性,到底如何操作由cmd决定,一般我们都会用来设置阻塞或者非阻塞IO
参数解析:
    参数 fd:准备设置属性的文件的描述符
    参数 cmd:文件到底设置什么属性又cmd决定
    参数 ...:
        F_SETFL:设置文件的flag属性
        F_GETFL:获取当前文件的flag属性 

 三、多路文件IO

        先发送输入事件,再调取阻塞型读取函数

1、原理:内核监视对象套接字的缓冲区变化

        1)边缘触发:缓冲区改变

        2)水平触发:缓冲区存在数据

        内核会通知监视者,有描述符可读

2、select模型

原型:int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);

功能描述:以阻塞的形式监视 readfds,writefds,exceptfds 这3个描述符集合中,所有描述符,如果有任何描述符激活,则select解除阻塞

参数解析:
    参数 nfds:readfds,writefds,exceptfds 这3个集合中的最大值
    参数 readfds:监视描述符集合中任意的描述符是否可读,一般我们只用这个
    参数 writefds:监视描述符集合中任意的描述符是否可写,一般写NULL
    参数 exceptfds:监视描述符集合中任意的描述符是否发生意外,一般写NULL
        注意:只要select监视到了有描述符激活,就会将激活的描述符,以覆盖的形式写入到上述3个fds里面去
    
    参数 timeout:是一个结构体,结构如下
        struct timeval {
            long    tv_sec;         /* seconds */
            long    tv_usec;        /* microseconds */
        };
        表示select函数只阻塞传入的时间长度的秒数,超过这个时间自动解除阻塞
        传NULL表示:一直阻塞,不受时间影响
返回值:返回激活的描述符的数量

select注意事项

        select 为水平触发 

        select fd_set 最大为 1024(文件描述符最大数量)

        select监视到激活的描述符成功后,会覆盖原队列

 描述符操作函数

 void FD_CLR(int fd, fd_set *set);
    功能描述: 从 set 中删除描述符 fd
int  FD_ISSET(int fd, fd_set *set);
    功能描述:判断 set 中是否存在描述符 fd
    返回值:如果存在返回1,不存在返回0
void FD_SET(int fd, fd_set *set);
    功能描述:将描述符 fd 添加到 set 里面去
void FD_ZERO(fd_set *set);
    功能描述:清空 set 所有描述符,相当于初始化的功能

3、 select实现并发服务器

1)服务器端

#include <myhead.h>
#define SER_PORT 6666
#define SER_IP "192.168.2.106"
int main(int argc, char const *argv[])
{// 1、创建套接字int sfd = socket(AF_INET, SOCK_STREAM, 0);if (sfd == -1){perror("socket error");return -1;}printf("socket success, sfd = %d\n", sfd); // 3// 2、为套接字绑定ip地址和端口号// 2.1 填充地址信息结构体struct sockaddr_in sin;sin.sin_family = AF_INET;                // 通信域sin.sin_port = htons(SER_PORT);          // 端口号sin.sin_addr.s_addr = inet_addr(SER_IP); // ip地址// 3、绑定if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) == -1){perror("bind error");return -1;}// 4、设置socket功能 -> 监听if (listen(sfd, 128) == -1){perror("listen error");return -1;}printf("listen on\n");// 5、创建接收客服端的结构体(后面没用,可无)//如果要实现有姓名的聊天室必须定义信息结构体数组或使用链表存储对端地址信息struct sockaddr_in cin;socklen_t addrlen;// 6、select监听套接字变化fd_set readfds;FD_ZERO(&readfds);FD_SET(sfd, &readfds);int newsdf_arr[100] = {0}; // 存放客服端套接字int nwesdf_count = 0;      // 记录客服端套接字数组下标while (1){//6.1、启动select监听套接字变化//定义中间变量避免 readfds 被覆盖fd_set temp = readfds;select(FD_SETSIZE, &temp, 0, 0, 0);printf("select on\n");if (FD_ISSET(sfd, &temp)){int newsdf = accept(sfd, (struct sockaddr *)&cin, &addrlen);//后两个参数后面用不上,可以填0//无客服端连接时,accept 会清空缓冲区if (newsdf == -1){perror("accept error");return -1;}printf("客服端连接成功\n");FD_SET(newsdf, &readfds); // 将新连接的套接字加入监听列表newsdf_arr[nwesdf_count] = newsdf;nwesdf_count++;}//6.2、循环监听客服端套接字变化for (int i = 0; i < nwesdf_count; i++){int newsfd_temp = newsdf_arr[i];//定义变量接收操作对象信息if (FD_ISSET(newsfd_temp, &temp)){char buf[128] = {0};int res = read(newsfd_temp, buf, 128);//read 函数在阻塞状态下 客服端断开连接 返回 0,非阻塞状态下,返回 -1if (res == 0){printf("客服端断开连接\n");// 客服端断开连接// 将断开的客服端从监视列表 readfds 删除FD_CLR(newsfd_temp, &readfds);// 将断开的客服端从客服端列表 newsfd_arr 删除for (int j = i; j < nwesdf_count - 1; j++){newsdf_arr[j] = newsdf_arr[j + 1];}nwesdf_count--;// 关闭套接字close(newsfd_temp);}else{printf("接收到:%s\n", buf);}}}}return 0;
}

2)客服端

#include <myhead.h>
#define SER_PORT 6666
#define SER_IP "192.168.2.106"
int main(int argc, char const *argv[])
{int cfd = socket(AF_INET, SOCK_STREAM, 0);if (cfd == -1){perror("socket error");return -1;}printf("socket success, cfd = %d\n", cfd);struct sockaddr_in sin;sin.sin_family = AF_INET;                // 通信域sin.sin_port = htons(SER_PORT);          // 端口号sin.sin_addr.s_addr = inet_addr(SER_IP); // ip地址connect(cfd,(struct sockaddr*)&sin,sizeof(sin));while (1){char buf[128] = {0};fgets(buf,sizeof(buf),stdin);buf[strlen(buf)-1] = 0;if (strcmp(buf,"quit") == 0){break;}sendto(cfd,buf,sizeof(buf),0,(struct sockaddr*)&sin,sizeof(sin));        }close(cfd);return 0;
}

4、 select实现聊天室(无姓名)

1)服务器端

#include <myhead.h>
#define SER_PORT 6666
#define SER_IP "192.168.2.106"
int main(int argc, char const *argv[])
{// 1、创建套接字int sfd = socket(AF_INET, SOCK_STREAM, 0);if (sfd == -1){perror("socket error");return -1;}printf("socket success, sfd = %d\n", sfd); // 3// 2、为套接字绑定ip地址和端口号// 2.1 填充地址信息结构体struct sockaddr_in sin;sin.sin_family = AF_INET;                // 通信域sin.sin_port = htons(SER_PORT);          // 端口号sin.sin_addr.s_addr = inet_addr(SER_IP); // ip地址// 3、绑定if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) == -1){perror("bind error");return -1;}// 4、设置socket功能 -> 监听if (listen(sfd, 128) == -1){perror("listen error");return -1;}printf("listen on\n");// 5、创建接收客服端的结构体(用不上,可以没有)struct sockaddr_in cin;socklen_t addrlen;// 6、select监听套接字变化fd_set readfds;FD_ZERO(&readfds);FD_SET(sfd, &readfds); // 监听连接用的套接字FD_SET(0, &readfds);   // 监听标准输入流int newsdf_arr[100] = {0}; // 存放客服端套接字int nwesdf_count = 0;      // 记录客服端套接字数组下标while (1){// 6.1、启动select监听套接字变化// 定义中间变量避免 readfds 被覆盖fd_set temp = readfds;select(FD_SETSIZE, &temp, 0, 0, 0);//printf("select on\n");// 调试用// 监听客服端连接if (FD_ISSET(sfd, &temp)){int newsdf = accept(sfd, (struct sockaddr *)&cin, &addrlen); // 后两个参数后面用不上,可以填0if (newsdf == -1){perror("accept error");return -1;}printf("客服端连接成功\n");FD_SET(newsdf, &readfds); // 将新连接的套接字加入监听列表newsdf_arr[nwesdf_count] = newsdf;nwesdf_count++;}// 6.2、循环监听客服端套接字变化for (int i = 0; i < nwesdf_count; i++){int newsfd_temp = newsdf_arr[i]; // 定义变量接收操作对象信息if (FD_ISSET(newsfd_temp, &temp)){char buf[128] = {0};int res = read(newsfd_temp, buf, 128);// read 函数在阻塞状态下 客服端断开连接 返回 0,非阻塞状态下,返回 -1if (res == 0){printf("客服端断开连接\n");// 客服端断开连接// 将断开的客服端从监视列表 readfds 删除FD_CLR(newsfd_temp, &readfds);// 将断开的客服端从客服端列表 newsfd_arr 删除for (int j = i; j < nwesdf_count - 1; j++){newsdf_arr[j] = newsdf_arr[j + 1];}nwesdf_count--;i--;// 关闭套接字close(newsfd_temp);}else{printf("接收到:%s\n", buf);// 将从客服端接收到信息发个其他客服端for (int k = 0; k < nwesdf_count; k++){if (newsfd_temp != newsdf_arr[k])//避免将信息发回发送者{send(newsdf_arr[k], buf, sizeof(buf), 0);}}}}}// 监听服务器标准输入流if (FD_ISSET(0, &temp)){// 服务器输入char ser_buf[128];fgets(ser_buf, 128, stdin);ser_buf[strlen(ser_buf) - 1] = 0;for (int i = 0; i < nwesdf_count; i++){send(newsdf_arr[i], ser_buf, sizeof(ser_buf), 0);}}}return 0;
}

2)客服端

#include <myhead.h>
#define SER_PORT 6666
#define SER_IP "192.168.2.106"
int main(int argc, char const *argv[])
{int cfd = socket(AF_INET, SOCK_STREAM, 0);if (cfd == -1){perror("socket error");return -1;}printf("socket success, cfd = %d\n", cfd);struct sockaddr_in sin;sin.sin_family = AF_INET;                // 通信域sin.sin_port = htons(SER_PORT);          // 端口号sin.sin_addr.s_addr = inet_addr(SER_IP); // ip地址socklen_t addrlen = sizeof(sin);connect(cfd, (struct sockaddr *)&sin, sizeof(sin));// 创建select监听套接字,stdin// 创建监听对象信息结构体fd_set readfds;FD_ZERO(&readfds);FD_SET(cfd, &readfds);FD_SET(0, &readfds);while (1){fd_set temp = readfds;select(FD_SETSIZE, &temp, 0, 0, 0); // 最后一个0表示阻塞printf("select on\n");if (FD_ISSET(cfd, &temp)){char cli_buf[128];recvfrom(cfd, cli_buf, sizeof(cli_buf), 0, (struct sockaddr *)&sin, &addrlen);printf("接收到:%s\n", cli_buf);}if (FD_ISSET(0, &temp)){char buf[128] = {0};fgets(buf, sizeof(buf), stdin);buf[strlen(buf) - 1] = 0;if (strcmp(buf, "quit") == 0){break;}sendto(cfd, buf, strlen(buf), 0, (struct sockaddr *)&sin, sizeof(sin));}}close(cfd);return 0;
}

5、poll模型

poll解决了上述两个问题

原型:int poll(struct pollfd *fds, nfds_t nfds, int timeout);
调用:
功能描述:监视 fds所指向的描述符数组中所有描述符的情况,最多监视nfds个,一般就是数组的容量
参数解析:
    参数 fds:结构体数组,数组中的每一个结构体元素都是一个描述符搭配一些其他数据,结构如下
        struct pollfd {
            int   fd;        监视对象
            short events;     监视对象激活条件:可读、可写、意外
                因为可读的原因激活:POLLIN,常用               

                因为可写的原因激活:POLLOUT
            short revents;   监视对象激活后 events 会覆盖到 revents                                  
        };
    参数 nfds:想要监视的描述符的数量,一般就是fds这个数组的实际长度
    参数 timeout:poll函数阻塞时长,单位为毫秒
         0 表示不阻塞
         -1 表示阻塞,直到有描述符激活
        
返回值:成功返回激活的描述符的数量                        

注意:

poll函数注意事项

        poll函数监视的直接是一个结构体数组,这个数组我们是可以直接操作的,不需要额外的函数去操作

        poll函数的激活方式为水平激活

 poll实现并发服务器

#include <myhead.h>
#define SER_PORT 6666
#define SER_IP "192.168.2.106"int main(int argc, char const *argv[])
{// 1、创建套接字int sfd = socket(AF_INET, SOCK_STREAM, 0);if (sfd == -1){perror("socket error");return -1;}printf("socket success, sfd = %d\n", sfd); // 3// 2、为套接字绑定ip地址和端口号// 2.1 填充地址信息结构体struct sockaddr_in sin;sin.sin_family = AF_INET;                // 通信域sin.sin_port = htons(SER_PORT);          // 端口号sin.sin_addr.s_addr = inet_addr(SER_IP); // ip地址// 3、绑定if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) == -1){perror("bind error");return -1;}// 4、设置socket功能 -> 监听if (listen(sfd, 128) == -1){perror("listen error");return -1;}printf("listen on\n");// 5、创建接收客服端的结构体(后面用不到可以没有)struct sockaddr_in cin;socklen_t addrlen;// 6、poll监听// 6.1、将服务器加入监视列表struct pollfd fds[50] = {0};fds[0].fd = sfd;         // 监视对象 用于连接的套接字fds[0].events = POLL_IN; // 对象激活条件 可读-》客服端申请连接int fd_count = 1; // 记录监视列表下标while (1){// 设置监听对象,实际监听数量,阻塞时间poll(fds, fd_count, -1); //-1 表示阻塞for (int i = 0; i < fd_count; i++){// 提出数据,以减少后续需要输入的内容int fd = fds[i].fd;short revents = fds[i].revents;// 判断 监视对象:服务器 是否激活if (fd == sfd && revents == fds[i].events){// 接收客服端连接int cfd = accept(sfd, 0, 0);if (cfd == -1){perror("accept error");return -1;}printf("accpet success\n");// 将客服端加入监视列表fds[fd_count].fd = cfd;fds[fd_count].events = POLL_IN;fd_count++;}// 判断其他监视对象是否激活if (fd != sfd && revents == fds[i].events){char buf[128] = {0};int res = read(fd, buf, 128);if (res == 0){printf("客服端断开连接\n");// 将断开连接的客服端从监视列表删除for (int j = i; j < fd_count; j++){fds[j] = fds[j + 1];}fd_count--;i--; // 防止跳过监视列表成员close(fd);break;}else{printf("接收到:%s\n", buf);}}}}return 0;
}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • C#工具库-NPOI
  • 案例分享—优秀国外界面设计配色舒适的原因
  • Kubernetes--深入Pod
  • MySQL索引失效的场景
  • Linux~系统基础学习
  • 深入探讨SD NAND的SD模式与SPI模式初始化
  • [数据集][目标检测]agvs仓储机器人检测数据集VOC+YOLO格式967张3类别
  • 数组去重的12重方法
  • 运维大规模K8S集群注意事项
  • java 函数接口Consumer简介与示例【函数式编程】【Stream】
  • 大学生实用工具!分享5款靠谱AI一键生成毕业论文的网站
  • 5个免费在线 AI 绘画网站推荐,附100+提示词!
  • 什么是上网行为管理呢?【上网行为管理系统功能介绍 】
  • 【C++ 面试 - 面向对象】每日 3 题(六)
  • LeetCode17 电话号码的字母组合
  • SegmentFault for Android 3.0 发布
  • [译] 怎样写一个基础的编译器
  • “大数据应用场景”之隔壁老王(连载四)
  • 《Java8实战》-第四章读书笔记(引入流Stream)
  • 【跃迁之路】【585天】程序员高效学习方法论探索系列(实验阶段342-2018.09.13)...
  • conda常用的命令
  • css的样式优先级
  • es6(二):字符串的扩展
  • HTTP 简介
  • iOS小技巧之UIImagePickerController实现头像选择
  • JavaScript实现分页效果
  • Java反射-动态类加载和重新加载
  • MyEclipse 8.0 GA 搭建 Struts2 + Spring2 + Hibernate3 (测试)
  • springboot_database项目介绍
  • Vim Clutch | 面向脚踏板编程……
  • XForms - 更强大的Form
  • 百度小程序遇到的问题
  • 多线程事务回滚
  • 构建二叉树进行数值数组的去重及优化
  • 记录一下第一次使用npm
  • 聊一聊前端的监控
  • 在electron中实现跨域请求,无需更改服务器端设置
  • [Shell 脚本] 备份网站文件至OSS服务(纯shell脚本无sdk) ...
  • NLPIR智能语义技术让大数据挖掘更简单
  • ​ ​Redis(五)主从复制:主从模式介绍、配置、拓扑(一主一从结构、一主多从结构、树形主从结构)、原理(复制过程、​​​​​​​数据同步psync)、总结
  • ​一文看懂数据清洗:缺失值、异常值和重复值的处理
  • #QT项目实战(天气预报)
  • (06)Hive——正则表达式
  • (16)Reactor的测试——响应式Spring的道法术器
  • (C#)一个最简单的链表类
  • (C语言)求出1,2,5三个数不同个数组合为100的组合个数
  • (js)循环条件满足时终止循环
  • (ZT)薛涌:谈贫说富
  • (黑马C++)L06 重载与继承
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理第3章 信息系统治理(一)
  • (一)ClickHouse 中的 `MaterializedMySQL` 数据库引擎的使用方法、设置、特性和限制。
  • (转)ObjectiveC 深浅拷贝学习
  • (转)Windows2003安全设置/维护
  • (转)大型网站的系统架构
  • (转)甲方乙方——赵民谈找工作