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

Linux 网络编程 day5 多路IO转接之改进select and poll

三种多路IO转接方法:select , poll  , epoll

改进select多路IO转接,使用数组来保存含有需要连接的套接字cfd,不用循环至1024,节约时间提高效率。 

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<ctype.h>
#define SER_PORT 9003
void sys_err(char* s)
{perror(s);exit(1);
}int main(int argc , char *argv[])
{int cfd , lfd , maxi;int client[FD_SETSIZE] ;char buf[BUFSIZ] , str[INET_ADDRSTRLEN];struct sockaddr_in serv_addr , clit_addr;socklen_t clit_addr_len;bzero(&serv_addr , sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(SER_PORT);serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);lfd = socket(AF_INET , SOCK_STREAM , 0);if(lfd == -1)sys_err("socket error");int opt = 1;setsockopt(lfd , SOL_SOCKET , SO_REUSEADDR , (void*)&opt , sizeof(opt));int ret = bind(lfd , (struct sockaddr*)&serv_addr , sizeof(serv_addr));if(ret == -1)sys_err("bind error");listen(lfd , 128);fd_set rset , allset;int maxfd , i , k , n ,j , m , sockfd;maxfd = lfd;maxi = -1;for(j = 0 ; j < 1024 ; j++)client[j] = -1;FD_ZERO(&allset);FD_SET(lfd , &allset);while(1){rset = allset;clit_addr_len = sizeof(clit_addr);ret = select(maxfd + 1 , &rset , NULL , NULL , NULL);if(ret < 0){sys_err("select error");}else if(ret > 0){if(FD_ISSET(lfd , &rset)){cfd = accept(lfd , (struct sockaddr*)&clit_addr , &clit_addr_len);if(cfd == -1)sys_err("accept error");printf("--received from %s at port %d\n" , inet_ntop(AF_INET , &clit_addr.sin_addr.s_addr , str , sizeof(str)) , ntohs(clit_addr.sin_port));for( m = 0 ; m < 1024 ;m++){if(client[m] < 0){client[m] = cfd;break;}}if( m == 1023){printf("too many clients\n");exit(1);}if(m > maxi)maxi = m;FD_SET(cfd , &allset);if(maxfd < cfd)maxfd = cfd ;if(ret == 1)continue;}for(i = lfd + 1 ; i <= maxi ; i++){if((sockfd = client[i]) < 0 )continue;if(FD_ISSET(sockfd , &rset)){n = read(sockfd , buf , sizeof(buf));if(n == 0){close(i);FD_CLR(sockfd , &allset);client[i] = -1;}else if( n > 0){for( k = 0 ; k < n; k++){buf[k] = toupper(buf[k]);}write(STDOUT_FILENO , buf , n);write(sockfd , buf , n);}else{sys_err("read error");}if(ret == 1)break;}}}}close(cfd);return 0 ;
}

poll

int poll(struct pollfd *fds, nfds_t nfds, int timeout);fds:监听的文件描述符数组struct pollfd {int   fd;         //监听的文件描述符short events;     //待监听的文件描述符对应的监听事件取值events : 读POLLIN , 写POLLOUT , 错误POLLERRshort revents;    //传入时,给0,满足对应事件会返回非0-->上述取值};nfds:监听数组的实际有效监听个数
timeout:超时时长,毫秒级等待
-1:阻塞等
0:立即返回,不阻塞进程
>0:等待指定毫秒数返回值:成功返回满足对应监听事件的文件描述符总个数初始化
struct pollfd pfds[1024];
pfds[0].fd = lfd;
pfds[0].events = POLLIN;
pfds[0].revents = 0;

read函数返回值

>0:实际读到字节数

=0:socket中,表示对端关闭。close()

<0:如果errno == EINTR  被异常中断,需要重启 。

     如果errno == EAGIN/EWOULDBLOCK 以非阻塞方式读数据,但是没有数据。需要再次读

     如果errno == ECONNRSET  说明连接被重置 需要close , 移除监听队列。

poll优缺点

优点:

1、自带数组结构,可以将 监听事件 和 返回事件 分离。

2、拓展监听上限。select无法修改,除非重新编译内核。

缺点:

1、不能跨平台。只有Linux和类Unix

2、无法直接定位满足监听事件的文件描述符,编码难度较大

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<ctype.h>
#include<poll.h>
#define SER_PORT 9003void sys_err(char* s)
{perror(s);exit(1);
}int main(int argc , char* argv[])
{int cfd , lfd , sockfd;char buf[BUFSIZ] , str[INET_ADDRSTRLEN];struct sockaddr_in serv_addr , clit_addr ;bzero(&serv_addr , sizeof(serv_addr));socklen_t clit_addr_len;lfd = socket(AF_INET , SOCK_STREAM , 0);if(lfd == -1)sys_err("socket error");int opt = 1 ;setsockopt(lfd , SOL_SOCKET , SO_REUSEADDR , (void*)&opt , sizeof(opt));int ret = bind(lfd , (struct sockaddr*)&serv_addr , sizeof(serv_addr));if(ret == -1)sys_err("bind error");listen(lfd , 128);struct pollfd pfds[1024];int maxi = lfd;pfds[0].fd = lfd;pfds[0].events = POLLIN;int i , n ,k;while(1){ret = poll(pfds , maxi + 1 , -1);if(ret < 0){sys_err("poll error");}else if(ret > 0){if(pfds[0].revents & POLLIN){clit_addr_len = sizeof(clit_addr);cfd = accept(lfd , (struct sockaddr*)&clit_addr , &clit_addr_len);if(cfd == -1)sys_err("accept error");printf("received from %s at PORT %d\n" ,inet_ntop(AF_INET , &clit_addr.sin_addr.s_addr , str ,                     sizeof(str)),ntohs(clit_addr.sin_port));for(i = 0 ; i < 1024 ; i++){if(pfds[i].fd<0){pfds[i].fd = cfd;break;}}if(i == 1024){printf("too many clients\n");exit(1);}if(maxi < i)maxi = i;if(ret == 1)continue;}for(i = 1 ; i <= maxi ; i++){sockfd = pfds[i].fd;if(sockfd < 0)continue;if(pfds[i].revents & POLLIN){n = read(sockfd , buf ,sizeof(buf));if(n == 0){close(sockfd);pfds[i].fd = -1;}else if(n > 0){for(k = 0 ; k < n ; k++){buf[k] = toupper(buf[k]);}write(sockfd , buf , n);write(STDOUT_FILENO , buf , n);}if(ret == 1)break;}}}}return 0 ;
}

突破1024文件描述符限制

cat /proc/sys/fs/file-max  -->当前计算机所能打开的最大文件个数,受硬件影响。

ulimit -a -->当前用户下的进程,默认打开文件描述符个数。1024

修改: sudo vi /etc/security/limits.conf  修改soft 基本不用

* soft nofile 65536 ---> 修改默认值,可直接借助命令修改:ulimit -n 数字(注销用户生效)

* hard nofile 100000 ---> 命令修改上限

epoll实现多路IO转接(重点)

本质是一个平衡二叉树,又是其特例红黑树。

epoll_create

创建一颗监听红黑树

int epoll_create(int size);
size:创建的红黑树的监听节点数量(仅供内核参考)。 返回值:成功返回指向新创建的红黑树的根节点的文件描述符fd
失败-1 ,errno

epoll_ctl(重点) 

操作监听红黑树

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epfd:epoll_create函数的返回值
op:对该监听红黑树所做的操作EPOLL_CTL_ADD:添加fd到监听红黑树EPOLL_CTL_MOD:修改fd在监听红黑树的监听事件EPOLL_CTL_DEL:将一个fd从监听红黑树上摘下(取消监听)
fd:待监听的fd
event:本质是struct epoll_event类型的结构体 地址events:EPOLLIN EPOLLOUT EPOLLERRdata:联合体共用体int fd; 对应监听事件的fdvoid *ptr;uint32_t u32;uint32_t u64;  返回值 成功0 , 失败-1 errno

epoll_wait

阻塞监听

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
epfd:epoll_create函数的返回值
events:传出参数【数组】,传出满足监听条件文件描述符结构体
maxevents:数组元素总个数 1024struct epoll_event events[1024];
timeout:超时时长,毫秒级等待
-1:阻塞等
0:立即返回,不阻塞进程
>0:等待指定毫秒数返回值:>0 满足监听的总个数,可以用作循环上限。
=0 , 没有fd满足监听事件
-1 , 失败 errno

流程

lfd = socket();
bind()
listen()int epfd = epoll_create(1024);                //epfd,监听红黑树的树根struct epoll_event tep , ep[1024];            //tep用来设置单个fd的属性,ep是epoll_wait函数传出的满足监听事件的数组
tep.events = EPOLLIN;
tep.data.fd = lfd;epoll_ctl(epfd , EPOLL_CTL_ADD , lfd , &tep); //将lfd添加到监听红黑树上while(1){int ret = epoll_wait(epfd , ep , 1024 , -1);  //实施监听for(i = 0 ; i < ret ; i++){             if(ep[i].data.fd == lfd){cfd = accept();tep.events = EPOLLIN;tep.data.fd = cfd;epoll_ctl(epfd , EPOLL_CTL_ADD , cfd , &tep);}else{                n = read(); if(n == 0){close();epoll_ctl(epfd , EPOLL_CTL_ADD , cfd , NULL); //摘除最后一个不需要初始化了,直接传NULL。}else if(n>0){小-->大write();}}}

使用epoll进行多路IO转接代码

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<sys/epoll.h>
#include<ctype.h>
#define SER_PORT 9004
void sys_err(char* s)
{perror(s);exit(1);
}int main(int argc , char *argv[])
{int lfd , cfd ,epfd , sockfd , i , n , kfd , k;char buf[BUFSIZ],str[INET_ADDRSTRLEN];struct sockaddr_in serv_addr , clit_addr;socklen_t clit_addr_len;bzero(&serv_addr , sizeof(serv_addr));serv_addr.sin_family = AF_INET ;serv_addr.sin_port = htons(SER_PORT);serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);lfd = socket(AF_INET , SOCK_STREAM , 0);if(lfd == -1)sys_err("socket error");int opt = 1;setsockopt(lfd , SOL_SOCKET , SO_REUSEADDR , (void*)&opt , sizeof(opt));int ret = bind(lfd , (struct sockaddr*)&serv_addr , sizeof(serv_addr));if(ret == -1)sys_err("bind error");listen(lfd , 128);epfd = epoll_create(1024);if(epfd == -1)sys_err("epoll_create error");struct epoll_event tep , ep[1024];tep.events = EPOLLIN;tep.data.fd = lfd;ret = epoll_ctl(epfd , EPOLL_CTL_ADD , lfd , &tep);if(ret == -1)sys_err("epoll_ctl error");while(1){kfd = epoll_wait(epfd , ep , 1024 , -1);if(kfd == -1 ){sys_err("epoll_wait error");}else if(kfd > 0){for(i = 0 ; i < kfd ; i++){sockfd = ep[i].data.fd;if(sockfd == lfd){clit_addr_len = sizeof(clit_addr);cfd = accept(lfd,(struct sockaddr*)&clit_addr, &clit_addr_len);if(cfd == -1)sys_err("accept error");printf("------received from %s at port %d------\n" ,inet_ntop(AF_INET, &clit_addr.sin_addr, str ,                 sizeof(str)),ntohs(clit_addr.sin_port));tep.events = EPOLLIN;tep.data.fd = cfd;ret = epoll_ctl(epfd , EPOLL_CTL_ADD , cfd , &tep);if(ret == -1)sys_err("epoll_ctl error");}else{n = read(sockfd , buf , sizeof(buf));if(n == 0){close(sockfd);epoll_ctl(epfd , EPOLL_CTL_DEL , sockfd , NULL);}else if(n > 0){for(k = 0 ; k < n ; k++){buf[k] = toupper(buf[k]);}write(sockfd , buf , n);write(STDOUT_FILENO , buf , n);}else{sys_err("read error");}}}}}return 0 ;
}

感觉epoll有些东西容易和poll弄混,黑马老师说可以只记epoll,在之后基本都是使用到epoll进行多路IO转接。

epoll实现多路IO转接主要是使用3个函数,第一个函数是epoll_create(1024),参数创建的红黑树的监听节点数量(仅供内核参考)。 第二个函数是epoll_ctl(epfd , EPOLL_CTL_ADD/MOD/DEL , lfd/cfd , &tep) , tep初始化是 struct epoll_event tep ; tep.events = EPOLLIN/OUT/ERR; tep.data.fd = lfd/cfd; 将lfd挂到红黑树上之后进行while循环,进行监听,kfd = epoll_wait(epfd , ep , 1024 , -1); ep是结构体数组,初始化为struct epoll_event ep[1024];

随后只需要根据下标取出epoll_wait函数的传出参数ep数组中的元素进行判断。是否是lfd或者是cfd,如果是lfd说明有要连接的客户端,进行accept连接,若是cfd,则说明有客户端进行写操作,服务器则进行相应的读操作以及小写转大写的操作即可。

相关文章:

  • 并发设计模式实战系列(16):屏障(Barrier)
  • Facebook如何运用AI实现元宇宙的无限可能?
  • RabbitMQ 添加新用户和配置权限
  • [监控看板]Grafana+Prometheus+Exporter监控疑难排查
  • 模型状态量
  • WPF之高级布局技术
  • 从设备交付到并网调试:CET中电技术分布式光伏全流程管控方案详解
  • 如何打造系统级低延迟RTSP/RTMP播放引擎?
  • 机器人系统设置
  • OpenJDK21源码编译指南(Linux环境)
  • 【[std::thread]与[qt类的对象自己的线程管理方法]】
  • cuda多维线程的实例
  • C++中指针使用详解(4)指针的高级应用汇总
  • 标题:基于自适应阈值与K-means聚类的图像行列排序与拼接处理
  • 一个关于fsaverage bem文件的说明
  • 五一感想:知识产权加速劳动价值!
  • window 显示驱动开发-线程和同步级别一级(二)
  • SecureCrt设置显示区域横列数
  • PDF扫描件交叉合并工具
  • 从PotPlayer到专业播放器—基于 RTSP|RTMP播放器功能、架构、工程能力的全面对比分析
  • 安徽六安原市长潘东旭,已任省市场监督管理局党组书记、局长
  • 农行原首席专家兼浙江省分行原行长冯建龙主动投案,正接受审查调查
  • 民生访谈|摆摊设点、公园搭帐篷、行道树飘絮,管理难题怎么解?
  • 汪海涛评《线索与痕迹》丨就虚而近实
  • 降准又降息!央行发布3类10项措施
  • 线下无理由退货怎样操作?线上线下监管有何不同?市场监管总局回应