首页
最新活动
服务器租用
香港服务器租用
台湾服务器租用
美国服务器租用
日本服务器租用
新加坡服务器租用
高防服务器
香港高防服务器
台湾高防服务器
美国高防服务器
裸金属
香港裸金属服务器
台湾裸金属服务器
美国裸金属服务器
日本裸金属服务器
新加坡裸金属服务器
云服务器
香港云服务器
台湾云服务器
美国云服务器
日本云服务器
CDN
CDN节点
CDN带宽
CDN防御
CDN定制
行业新闻
官方公告
香港服务器资讯
帮助文档
wp博客
zb博客
服务器资讯
联系我们
关于我们
机房介绍
机房托管
登入
注册
帮助文档
专业提供香港服务器、香港云服务器、香港高防服务器租用、香港云主机、台湾服务器、美国服务器、美国云服务器vps租用、韩国高防服务器租用、新加坡服务器、日本服务器租用 一站式全球网络解决方案提供商!专业运营维护IDC数据中心,提供高质量的服务器托管,服务器机房租用,服务器机柜租用,IDC机房机柜租用等服务,稳定、安全、高性能的云端计算服务,实时满足您的多样性业务需求。 香港大带宽稳定可靠,高级工程师提供基于服务器硬件、操作系统、网络、应用环境、安全的免费技术支持。
联系客服
服务器资讯
/
香港服务器租用
/
香港VPS租用
/
香港云服务器
/
美国服务器租用
/
台湾服务器租用
/
日本服务器租用
/
官方公告
/
帮助文档
高并发服务器--多路IO转接(多路IO复用)
发布时间:2024-03-09 18:26:49 分类:帮助文档
高并发服务器--多路IO转接(多路IO复用) 目录 1、select实现 1.1 基本原理: 1.2 API: 1.3 代码: 1.4 优缺点 2、poll实现 2.1 工作流程 2.1 API 2.2 代码 3、epoll实现 3.1 API 3.1.1 epoll_create 3.1.2 epoll_ctl 3.1.3 epoll_wait 3.2 代码 高并发服务器的三种方式: 阻塞等待--消耗资源(如多线程多进程实现)非阻塞忙轮询--消耗cpu多路IO转接(内核监听多个文件描述符的属性(读写缓冲区)变化,如果某个文件描述符的读缓冲区变化了,这个时候就是可以读了,将这个事件告知应用层) 多路IO转接三种方式:select(windows, 跨平台)、poll(少用)、epoll(linux)。 多路IO转接服务器也叫做多任务IO服务器。该类服务器实现的主旨思想时,不再由应用程序自己监视客户端连接,取而代之由内核替应用程序监视文件。 1、select实现 select能监听文件描述符个数受限于FD_SETSIZE,一般为1024,单纯改变进程打开的文件描述符个数并不能改变select监听文件个数。 解决1024以下客户端时使用select是很合适的,但是如果客户端过多,select采用的是轮询模型,会大大降低服务器响应效率,不应在select上投入更多精力。 1.1 基本原理: select核心实现原理是位图,select总共有三种位图,分别为读,写,异常位图。用户程序预先将socket文件描述符注册至读,写,异常位图,然后通过select系统调用轮询位图中的socket的读,写,异常事件。内核通过轮询方式获取读,写,异常位图中注册的socket文件事件,如果检测到有socket文件处于就绪状态,则会将socket对应的事件设置到输出位图,等所有位图中的socket都被轮询完,会统一将输出位图通过copy_to_user函数复制到输入位图,并且覆盖掉输入位图注册信息(也就是用户初始化的位图被内核修改)。select轮询完所有位图,如果未检测到任何socket文件处于就绪状态,根据超时时间确定是否返回或者阻塞进程。socket检测到读,写,异常事件后,会通过注册到socket等待队列的回调函数poll_wake将进程唤醒,唤醒的进程将再次轮询所有位图。select返回时会将剩余的超时时间通过copy_to_user覆盖原来的超时时间。 1.2 API: #include
#include
#include
#include
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); 功能:监听多个文件描述符的属性变化(读、写、异常) void FD_CLR(int fd, fd_set *set); int FD_ISSET(int fd, fd_set *set); void FD_SET(int fd, fd_set *set); void FD_ZERO(fd_set *set); 参数: nfds : 最大文件描述符+1 readfds : 需要监听的读的文件描述符存放的集合 writefds : 需要监听的写的文件描述符存放的集合 NULL exceptfds : 需要监听的异常的文件描述符存放的集合 NULL timeout : 多长时间监听一次 固定的事件,限时等待 NULL永久监听 struct timeval{ long tv_sec; // seconds long tv_usec; // microseconds }; 返回值:返回的是变化的文件描述符的个数 注意:变化的文件描述符会存在监听的集合中,未变化的文件描述符会被删除 1.3 代码: #include
#include
#include
#include
#include
#include"wrap.h" #include
#define PORT 8800 int main() { // 创建监听套接字、绑定 int lfd = tcp4bind(PORT, NULL); // 监听 Listen(lfd, 128); int maxfd = lfd; fd_set oldset, rset; FD_ZERO(&oldset); FD_ZERO(&rset); FD_SET(lfd, &oldset); // 循环调用select,并处理发生变化的文件描述符 while(1) { rset = oldset; int n = select(maxfd+1, &rset, NULL, NULL, NULL); // n为发生变化文件描述符的数量 if(n < 0) { perror("select error:"); break; } else if(n == 0) // 超时 { continue; } else // 有文件描述符发生变化 { // lfd变化 if(FD_ISSET(lfd, &rset)) { struct sockaddr_in cliaddr; socklen_t len = sizeof(cliaddr); char ip[16] = ""; int cfd = Accept(lfd, (struct sockaddr*)&cliaddr, &len); printf("new client ip = %s; port = %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip ,16), ntohs(cliaddr.sin_port)); // 将cfd添加至oldset集合中,以下次监听 FD_SET(cfd, &oldset); // 更新maxfd if(cfd > maxfd) maxfd = cfd; // 只有lfd变化,continue if(--n == 0) continue; } } // cfd变化 for(int i = lfd + 1; i <= maxfd; i++) { // 如果i文件描述符在rset中 if(FD_ISSET(i, &rset)) { char buf[1500]= ""; int ret = Read(i, buf, sizeof(buf)); if(ret < 0) { perror("read error:"); close(i); FD_CLR(i, &oldset); continue; } else if(ret == 0) { printf("client close.\n"); close(i); FD_CLR(i, &oldset); } else { printf("%s\n", buf); write(i, buf, ret); } } } } return 0; } 1.4 优缺点 优点:跨平台 缺点: 文件描述符1024的限制 只是返回变化的文件描述符的个数,具体哪个变化需要遍历 每次都需要将需要监听的文件描述符集合由应用层拷贝到 内核 效率低: 假设现在4-1023个文件描述符需要监听,但是5-1000这些文件描述符关闭了? 假设现在4-1023个文件描述符需要监听,但是只有5,1002发来消息。 2、poll实现 2.1 工作流程 用户空间程序调用poll函数,并传入了一个pollfd结构数组,以及数组的大小和超时时间等参数。内核遍历该数组,检查每个文件描述符所对应的I/O事件是否发生如果有文件描述符的I/O事件发生,就在相应的pollfd结构中设置相应的标志位。poll函数返回给用户空间,并通知哪些文件描述符已经就绪。用户空间程序根据返回的结果进行相应的处理 2.1 API #include
int poll(struct pollfd *fds, nfds_t nfds, int timeout); 功能:监听多个文件描述符的属性变化 参数: fds: 监听的数组的首元素地址 nfds: 数组的有效元素的最大下标+1 timeout: 超时事件 -1为永久监听 >=0为限时等待 数组元素: struct pollfd { int fd; // 需要监听的文件描述符 short events; // 需要监听文件描述符什么事件 POLLIN读事件、POLLOUT写事件 short revents; // 返回监听到的事件 同上 } 2.2 代码 #include
#include
#include
#include
#include
#include
#include
#include
#include
#include"wrap.h" #define MAXLINE 80 #define SERV_PORT 8000 #define OPEN_MAX 1024 int main() { int i, j, maxi, lfd, cfd, sockfd; int nready; ssize_t n; char buf[MAXLINE], str[INET_ADDRSTRLEN]; socklen_t clilen; struct pollfd client[OPEN_MAX]; // 定义poll数组 struct sockaddr_in cliaddr, servaddr; lfd = Socket(AF_INET, SOCK_STREAM, 0); int opt = 1; setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // 设置端口复用 bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); Bind(lfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); Listen(lfd, 128); client[0].fd = lfd; // 要监听的第一个文件描述符,存入client[0] client[0].events = POLLIN; // lfd监听普通读事件 for(i = 1; i < OPEN_MAX; i++) client[i].fd = -1; // 用-1初始化client[]里剩下元素 0也是文件描述符,不能用来初始化 maxi = 0; while(1) { nready = poll(client, maxi+1, -1); // 阻塞监听是否有客户端链接请求 if(client[0].revents & POLLIN) { clilen = sizeof(cliaddr); cfd = Accept(lfd, (struct sockaddr *)&cliaddr, &clilen); // 接收客户端请求 printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port)); for(i = 1; i
maxi) maxi = i; if(--nready <= 0) continue; } for(i = 1; i<=maxi; i++) { if((sockfd = client[i].fd) < 0) continue; // 找到第一个大于0的 if(client[i].revents & POLLIN) { if((n = Read(sockfd, buf, MAXLINE)) < 0) { // connection reset by client if(errno == ECONNRESET) // 收到RST标志 { printf("client[%d] aborted connection\n", i); close(sockfd); } } else if(n == 0) { printf("client[%d] closed connection\n", i); close(sockfd); client[i].fd = -1; } else { for(j = 0; j < n; j++) buf[j] = toupper(buf[j]); Write(sockfd, buf, n); } if(--nready <= 0) break; } } } return 0; } 3、epoll实现 3.1 API 3.1.1 epoll_create int epoll_creat(int size); 功能:创建一个epoll对象 参数:size取大于0即可 返回值:成功返回epoll对象epfd。 小于0表示创建失败 3.1.2 epoll_ctl int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 功能:epoll_ctl函数用于增加、删除、修改epoll事件,epoll事件会存储于内核epoll结构体红黑树中。 参数: epfd:epoll文件描述符 op:操作码 EPOLL_CTL_ADD: 插入事件 EPOLL_CTL_DEL: 删除事件 EPOLL_CTL_MOD: 修改事件 fd:epoll事件绑定的套接字文件描述符 event:epoll事件结构体。 返回值: 成功:返回0。 失败:返回-1,并设置errno。 struct epoll_event { uint32_t events; // epoll事件 epoll_data_t data; }; typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; }epoll_data_t; epoll_ctl函数增加epoll事件时,系统默认注册EPOLLERR和EPOLLHUP事件 3.1.3 epoll_wait epoll就绪事件处理示例: 1、注册epoll事件 struct epoll_event ev; ev.data.fd = sock_fd; ev.event = EPOLLIN; // 注册EPOLLIN事件 epoll_ctl(efd, EPOLL_CTL_ADD, sock_fd, &ev); 2、就绪epoll事件 res = EPOLLIN | EPOLLRDNORM; 3、epoll_wait获取事件 events = (EPOLLIN | EPOLLERR | EPOLLHUP) & (EPOLLIN | EPOLLRDNORM) = EPOLLIN; 注意:只有注册的事件才能通过epoll_wait获取 int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); 功能:epoll_wait用于监听套接字事件。 参数: epfd: epoll文件描述符。 events: epoll事件数组。 maxevents:epoll事件数组长度 timeout:超时时间 小于0:一直等待。 等于0:立即返回。 大于0:等待超时时间返回,单位毫秒 返回值: 小于0:出错。 等于0:超时。 大于0:返回就绪事件个数。 3.2 代码 #include
#include
#include
#include
#include
#include"wrap.h" int main() { // 创建套接字并绑定 int lfd = tcp4bind(8000, NULL); // 监听 Listen(lfd, 128); // 创建树 int epfd = epoll_create(1); // 将lfd上树 struct epoll_event ev, evs[1024]; ev.data.fd = lfd; ev.events = EPOLLIN; epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev); // while监听套接字事件 while(1) { int nready = epoll_wait(epfd, evs, 1024, -1); // epoll_wait监听套接字事件, nready为就绪事件的数量,evs为epoll事件数组(用于接收) if(nready < 0) { perror(""); break; } else if(nready == 0) // 超时 continue; else // 有文件描述符变化 { for(int i = 0; i < nready; i++) { // 判断lfd变化,并且是读事件变化 if(evs[i].data.fd == lfd && evs[i].events & EPOLLIN) { struct sockaddr_in cliaddr; char ip[16] = ""; socklen_t len = sizeof(cliaddr); int cfd = Accept(lfd, (struct sockaddr *)&cliaddr, &len); // 提取新的连接 printf("new client ip = %s port = %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, 16), ntohs(cliaddr.sin_port)); // 将cfd上树 ev.data.fd = cfd; ev.events = EPOLLIN; epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev); } else if(evs[i].events & EPOLLIN) // cfd变化,而且是读事件变化 { char buf[1024] = ""; int n = read(evs[i].data.fd, buf, sizeof(buf)); if(n < 0) // 出错,cfd下树 { perror(""); epoll_ctl(epfd, EPOLL_CTL_DEL, evs[i].data.fd, &evs[i]); } else if(n == 0) // 客户端关闭 { printf("client close\n"); close(evs[i].data.fd);//将cfd关闭 epoll_ctl(epfd, EPOLL_CTL_DEL, evs[i].data.fd, &evs[i]); // 下树 } else { printf("%s\n", buf); write(evs[i].data.fd, buf, n); } } } } } return 0; } 3.3 epoll两种工作方式 1、监听读缓冲区的变化 水平(LT)触发:只要读缓冲区有数据就会触发epoll_wait。 边沿(ET)触发:数据来一次,epoll_wait只触发一次。 2、监听写缓冲区的变化 水平触发:只要可以写,就会触发。 边沿触发:数据从有到无,就会触发。 LT模式只不过比ET模式多执行了一个步骤,就是当epoll_wait获取完就绪队列epoll事件后,LT模式会再次将epoll事件节点再次添加到就绪队列。 默认设置都是水平触发,水平触发如果数据一次性都不干净,就需要多次系统调用,浪费资源,一般都会设置为边沿触发,改为边沿触发只需要将监听事件或上一个宏: ev.events = EPOLLIN | EPLLET; // 监听读事件并设置为ET模式 设置完之后,则需要在读数据的时候一次性将数据读完,不然会出现读不完的情况。此时只需要将read那里加一个while循环。如果read读完了缓冲区的数据之后会阻塞,所以需要将其设置为非阻塞: int flags = fcntl(cfd, F_GETFL); // 获取cfd的标志位 flags |= O_NONBLOCK; fcntl(cfd, F_SETFL, flags); // 将cfd设置为非阻塞
上一篇
10元1月的香港虚拟主机
下一篇
香港服务器租赁地址
相关文章
app怎么租用免费服务器吗
从国贸去木樨园怎么走
php网站空间怎么用
阿里云ECS服务器如何选择操作系统?
SSH怎么关闭服务器防火墙
比特球云盘怎么磁力下载ios
百度云内存怎么算的
新手小白如何在阿里云上快速部署幻兽帕鲁联机服务器?
服务器存在的问题怎么解决
香港云服务器租用推荐
服务器租用资讯
·广东云服务有限公司怎么样
·广东云服务器怎么样
·广东锐讯网络有限公司怎么样
·广东佛山的蜗牛怎么那么大
·广东单位电话主机号怎么填写
·管家婆 花生壳怎么用
·官网域名过期要怎么办
·官网邮箱一般怎么命名
·官网网站被篡改怎么办
服务器租用推荐
·美国服务器租用
·台湾服务器租用
·香港云服务器租用
·香港裸金属服务器
·香港高防服务器租用
·香港服务器租用特价
7*24H在线售后
高可用资源,安全稳定
1v1专属客服对接
无忧退款试用保障
德讯电讯股份有限公司
电话:00886-982-263-666
台湾总部:台北市中山区建国北路一段29号3楼
香港分公司:九龙弥敦道625号雅兰商业二期906室
服务器租用
香港服务器
日本服务器
台湾服务器
美国服务器
高防服务器购买
香港高防服务器出租
台湾高防服务器租赁
美国高防服务器DDos
云服务器
香港云服务器
台湾云服务器
美国云服务器
日本云服务器
行业新闻
香港服务器租用
服务器资讯
香港云服务器
台湾服务器租用
zblog博客
香港VPS
关于我们
机房介绍
联系我们
Copyright © 1997-2024 www.hkstack.com All rights reserved.