帮助文档
专业提供香港服务器、香港云服务器、香港高防服务器租用、香港云主机、台湾服务器、美国服务器、美国云服务器vps租用、韩国高防服务器租用、新加坡服务器、日本服务器租用 一站式全球网络解决方案提供商!专业运营维护IDC数据中心,提供高质量的服务器托管,服务器机房租用,服务器机柜租用,IDC机房机柜租用等服务,稳定、安全、高性能的云端计算服务,实时满足您的多样性业务需求。 香港大带宽稳定可靠,高级工程师提供基于服务器硬件、操作系统、网络、应用环境、安全的免费技术支持。
服务器资讯 / 香港服务器租用 / 香港VPS租用 / 香港云服务器 / 美国服务器租用 / 台湾服务器租用 / 日本服务器租用 / 官方公告 / 帮助文档
TCP高并发服务器简介(select、poll、epoll实现与区别)
发布时间:2024-03-10 20:46:49   分类:帮助文档
TCP高并发服务器简介(select、poll、epoll实现与区别) select、poll、epoll三者的实现: select实现TCP高并发服务器的流程: 一、创建套接字(socket函数):二、填充服务器的网络信息结构体:三、套接字和服务器的网络信息结构体进行绑定(bind函数):四、套接字设置成被动监听(listen函数):五、创建要监听的文件描述符集合:使用select函数后,会将没有就绪的文件描述符在集合中去除,所以需要创建两个文件描述符集合,一个是母本read_fds,类似于常量,保持不变,而另一个作为副本read_fds_t,类似于变量,可以改变; fd_set read_fds; FD_ZERO(&read_fds); fd_set read_fds_t; FD_ZERO(&read_fds_t); 六、把创建的套接字添加到要监视的集合中: FD_SET(sockfd,&read_fds); int fd_max = 0; fd_max = fd_max > sockfd ? fd_max : sockfd; 七、设置系统时间结构体变量,用来指定超时的时间: struct timeval tm_out; 八、等待文件描述符中的事件是否就绪,成功则返回就绪的文件描述符的个数(select函数):select函数: #include int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); /* 参数: nfds: 要监视的最大文件描述符+1 readfds: 要监视的读文件描述符集合 不关心可以传NULL writefds: 要监视的写文件描述符集合 不关心可以传NULL exceptfds: 要监视的异常文件描述符集合 不关心可以传NULL timeout: 超时时间 如果设置成NULL 会一直阻塞 直到有文件描述符就绪 返回值: 成功 就绪的文件描述符的个数 超时 0 失败 -1 重置错误码 */ //struct timeval 可以指定超时时间 //如果结构体的两个成员都为0 表示非阻塞 struct timeval { long tv_sec; //秒 long tv_usec; //微秒 }; void FD_CLR(int fd, fd_set *set); //将文件描述符在集合中删除 int FD_ISSET(int fd, fd_set *set); //判断文件描述符是否还在集合中 // 返回0 表示不在了 非0 表示在 void FD_SET(int fd, fd_set *set); //向集合中添加一个文件描述符 void FD_ZERO(fd_set *set); //清空集合 if(-1 == (ret = select(fd_max + 1,&read_fds_t,NULL,NULL,&tm_out))) { perror("select error"); exit(-1); } else if(0 == ret) { puts("timeout!!!!!"); putchar('\n'); continue; } 九、遍历文件描述符集合,判断哪些文件描述符已经准备就绪: for(int i = 3; i < fd_max + 1 && 0 != ret; i++) { ... } 十、判断文件描述符是否还在集合中,并且接收来自客户端的数据(recv函数)和给客户端发送应答消息(send函数): if(FD_ISSET(i,&read_fds_t)) { //说明有新的客户端连接服务器 if(i == sockfd) { if(-1 == (accept_fd = accept(sockfd,NULL,NULL))) { perror("accept error"); exit(-1); } printf("客户端[%d]连接到服务器\n",accept_fd); //将新连接的客户端的套接字添加到要监视的集合中 FD_SET(accept_fd,&read_fds); fd_max = fd_max > accept_fd ? fd_max : accept_fd; } else //之前连接的客户端在向服务器发送信息 { memset(buf,0,sizeof(buf)); if(-1 == (nbytes = recv(i,buf,sizeof(buf),0))) { perror("recv error"); exit(-1); } else if(0 == nbytes) { printf("客户端[%d]已断开连接\n",i); //将已断开连接客户端的套接字在文件描述符集合中剔除 FD_CLR(i,&read_fds); //关闭套接字 close(i); continue; } if(!strncmp(buf,"quit",4)) { printf("客户端[%d]已退出\n",i); //将已断开连接客户端的套接字在文件描述符集合中剔除 FD_CLR(i,&read_fds); //关闭套接字 close(i); continue; } printf("客户端[%d]发来信息[%s]\n",i,buf); //组装应答消息 strcat(buf,"----------k"); //给客户端发送应答消息 if(-1 == send(i,buf,sizeof(buf),0)) { perror("send error"); exit(-1); } } ret--; //减少遍历次数 } 十一、关闭套接字(close函数): poll实现TCP高并发服务器的流程: 一、创建套接字(socket函数):二、填充服务器的网络信息结构体:三、套接字和服务器的网络信息结构体进行绑定(bind函数):四、套接字设置成被动监听(listen函数):五、创建要监听的文件描述符集合并清空文件描述符集合: //创建要监听的文件描述符集合 struct pollfd new_fds[2048] = {0}; //清空文件描述符集合 for(int i = 0; i < 2048; ++i) { new_fds[i].fd = -1; } 六、把创建的套接字添加到要监视的集合中: FD_SET(sockfd,&read_fds); int fd_max = 0; fd_max = fd_max > sockfd ? fd_max : sockfd; 七、套接字添加到要监视的集合中,并且设置要监听的事件: //套接字添加到要监视的集合中 new_fds[0].fd = sockfd; //设置要监听的事件 new_fds[0].events |= POLLIN; 八、记录文件描述符集合中最大的文件描述符,并且设置超时的时间: //记录文件描述符集合中最大的文件描述符 int fd_max = 0; fd_max = fd_max > sockfd ? fd_max : sockfd; //设置超时的时间 int tm_out = 10000; 九、等待文件描述符中的事件是否就绪,成功则返回就绪的文件描述符的个数(poll函数):poll实现TCP中型并发服务器与select实现TCP小型并发服务器的区别就是无需每次重置集合,并且可以设置要监视的最大文件描述符的个数,而select至多监视1024个文件描述符;poll函数: #include int poll(struct pollfd *fds, nfds_t nfds, int timeout); /* 参数: fds:要监视的文件描述符的集合指向自定义的结构体数据 nfds:fds中已经使用的项目的个数 timeout:超时时间单位是毫秒 设置成10000 表示10s -1 永久阻塞 0 非阻塞 返回值: 0 超时 -1 出错 重置错误码 正数 成功 返回的就绪的文件描述符的个数 */ struct pollfd { int fd; /* 文件描述符 设置成-1 内核就不再监视这一位了*/ short events; /* 要监视的事件 */ short revents; /* 返回的事件 */ }; /* 要监视的事件是用位运算或起来的 要监视的事件放在events字段,而实际就绪的事件在revents字段返回 POLLIN 读事件 POLLOUT 写时间 POLLERR 异常事件 */ if(-1 == (ret = poll(new_fds,fd_max,tm_out))) { perror("poll error"); exit(-1); } else if(0 == ret) { puts("timeout!!!!!"); putchar('\n'); continue; } 十、遍历文件描述符集合,判断哪些文件描述符已经准备就绪: for(k = 0; k <= fd_max && ret != 0; ++k) { ... } 十一、找到实际就绪的事件的文件描述符,并且接收来自客户端的数据(recv函数)和给客户端发送应答消息(send函数): //找到实际就绪的事件的文件描述符 if(0 != (new_fds[k].revents & POLLIN)) { //说明有新的客户端连接服务器 if(new_fds[k].fd == sockfd) { if(-1 == (accept_fd = accept(sockfd,NULL,NULL))) { perror("accept error"); exit(-1); } printf("客户端[%d]连接到服务器\n",accept_fd); //将新连接的客户端的套接字添加到要监视的集合中 //遍历文件描述符集合,给新的文件描述符找一个位置 for(j = 0; j < 2048; j++) { if(-1 == new_fds[j].fd) { new_fds[j].fd = accept_fd; new_fds[j].events |= POLLIN; fd_max = fd_max > accept_fd ? fd_max : accept_fd; break; } } if(2048 == j) { //此时集合容量满了 close(accept_fd); } } else //之前连接的客户端在向服务器发送信息 { memset(buf,0,sizeof(buf)); if(-1 == (nbytes = recv(new_fds[k].fd,buf,sizeof(buf),0))) { perror("recv error"); exit(-1); } else if(0 == nbytes) { printf("客户端[%d]已断开连接\n",new_fds[k].fd); //将已断开连接客户端的套接字在文件描述符集合中剔除 close(new_fds[k].fd); new_fds[k].fd = -1; continue; } if(!strncmp(buf,"quit",4)) { printf("客户端[%d]已退出\n",new_fds[k].fd); //将已断开连接客户端的套接字在文件描述符集合中剔除 close(new_fds[k].fd); new_fds[k].fd = -1; continue; } printf("客户端[%d]发来信息[%s]\n",new_fds[k].fd,buf); //组装应答消息 strcat(buf,"----------k"); //给客户端发送应答消息 if(-1 == send(new_fds[k].fd,buf,sizeof(buf),0)) { perror("send error"); exit(-1); } } ret--; //减少遍历次数 } 十二、关闭套接字(close函数): epoll实现TCP高并发服务器的流程: 一、创建套接字(socket函数):通信域选择IPV4网络协议、套接字类型选择流式; int sock_fd = socket(AF_INET,SOCK_STREAM,0); //通信域选择IPV4、套接字类型选择流式 二、填充服务器和客户机的网络信息结构体:1.分别定义服务器网络信息结构体变量serveraddr和客户机网络信息结构体变量clientaddr;2.分别求出服务器和客户机的网络信息结构体变量的内存空间大小,以作备用;3.网络信息结构体清0;4.使用IPV4网络协议AF_INET;5.在终端预留服务器端主机的IP地址:inet_addr(argv[1]);6.在终端预留服务器端网络字节序的端口号:htons(atoi(argv[2])); struct sockaddr_in serveraddr; //定义服务器网络信息结构体变量 struct sockaddr_in clientaddr; socklen_t serveraddr_len = sizeof(serveraddr);//求出服务器结构体变量的内存空间大小 socklen_t clientaddr_len = sizeof(clientaddr);//求出客户机结构体变量的内存空间大小 memset(&serveraddr,0,serveraddr_len); //服务器结构体清零 memset(&clientaddr,0,clientaddr_len);//客户机结构体清零 serveraddr.sin_family = AF_INET; //使用IPV4网络协议 serveraddr.sin_addr.s_addr = inet_addr(argv[1]); //IP地址 serveraddr.sin_port = htons(atoi(argv[2]));//网络字节序的端口号 三、设置允许端口复用(setsockopt函数):setsockopt函数:功能:设置套接字属性; #include #include int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); /* 参数: sockfd:套接字 level: 选项的级别 套接字API级别 SOL_SOCKET TCP级别 IPPROTO_TCP IP级别 IPPROTO_IP optname:选项的名字 套接字API级别 SO_BROADCAST 是否允许发送广播 SO_RCVBUF 接收缓冲区的大小 SO_SNDBUF 发送缓冲区的大小 SO_RCVTIMEO 接收超时时间 参数使用的是 struct timeval 结构体 如果超时了 函数调用会立即返回-1 并将错误码置成 EAGAIN SO_SNDTIMEO 发送超时时间 SO_REUSEADDR 端口复用 TCP级别 TCP_NODELAY 使能/禁用Nagle算法 IP级别 IP_ADD_MEMBERSHIP 设置加入多播组 optval: 选项的值 没有特殊说明时 使用的都是int类型 optlen:optval的大小 返回值: 成功 0 失败 -1 重置错误码 */ 特别注意:使用setsockopt设置允许端口复用时,其在代码的位置在填充网络信息结构体和bind之间; int reuse = 1; if(-1 == (setsockopt(sock_fd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse)))) { perror("setsockopt error"); exit(-1); } 四、套接字和服务器的网络信息结构体进行绑定(bind函数): 五、套接字设置成被动监听(listen函数): 六、创建红黑树(epoll_create函数): #include int epoll_create(int size); /* 功能: 创建epoll/创建epoll实例的描述符 参数: size:参数已经被忽略了,只需要填写大于0的值即可 返回值: epoll_create 调用成功时会返回一个非负整数epfd, 表示新创建的 epoll 实例的文件描述符, 如果调用失败则返回 -1,并设置 errno 变量以指示具体错误原因 */ int epfd = epoll_create(1); if(-1 == epfd) { perror("epoll_create error"); exit(-1); } 七、定义事件结构体变量和存放就绪事件描述符的数组:事件结构体:epoll_event用于描述一个文件描述符上的事件; typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; struct epoll_event { uint32_t events; //EPOLLIN 读 / EPOLLOUT 写 epoll_data_t data; //存放用户的数据 }; struct epoll_event event; struct epoll_event events[N]; 八、将关心的文件描述符加入到红黑树(epoll_ctl函数):功能:epoll的控制操作或者用于向 epoll 实例中添加、修改、删除事件;epoll_ctl函数: int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); /* 参数: epfd:epoll的文件描述符 op:控制方式 EPOLL_CTL_ADD:添加 EPOLL_CTL_MOD:修改 EPOLL_CTL_DEL:删除 fd:被操作的文件描述符 event:(事件)结构体指针 返回值: 成功返回0, 失败返回-1 置位错误码 */ //添加要检测事件的描述符 event.events = EPOLLIN; event.data.fd = sock_fd; //将关心的文件描述符加入到红黑树 if(-1 == (epoll_ctl(epfd,EPOLL_CTL_ADD,sock_fd,&event))) { perror("epoll_ctl error"); exit(-1); } 九、等待文件描述符中的事件是否就绪,成功则返回就绪的文件描述符的个数(epoll_wait函数):epoll_wait函数: int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout); /* 参数: epfd:epoll的文件描述符 events:准备好的事件的结构体地址 maxevents:返回的最大的文件描述符的个数 timeout:超时 >0 :毫秒级别的超时时间 =0 :立即返回 =-1:不关心超时时间 返回值: 成功返回准备好的文件描述符的个数 返回0代表超时时间到了 失败返回-1置位错误码 */ if(-1 == (ret = epoll_wait(epfd,events,N,-1))) { perror("epoll_wait error"); exit(-1); } 十、遍历就绪的文件描述符集,判断哪些文件描述符已经准备就绪: for(int i = 0; i < ret; ++i) { ... } 十一、找到实际就绪的事件的文件描述符,并且接收来自客户端的数据(recv函数)和给客户端发送应答消息(send函数): if(events[i].data.fd == sock_fd) { //获取连接成功后新的客户端 new_fd = accept(sock_fd,(struct sockaddr *)&clientaddr,&clientaddr_len); if(-1 == new_fd) { perror("accept error"); exit(-1); } printf("文件描述符[%d]客户端[%s:%d]连接到了服务器\n",new_fd,inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port)); //添加要检测的文件描述符 event.events = EPOLLIN; event.data.fd = new_fd; if(-1 == (epoll_ctl(epfd,EPOLL_CTL_ADD,new_fd,&event))) { perror("epoll_ctl error"); exit(-1); } printf("文件描述符[%d]成功挂载在红黑树上\n",new_fd); } else { memset(buf,0,sizeof(buf)); int old_fd = events[i].data.fd; if(-1 == (nbytes = recv(old_fd,buf,sizeof(buf),0))) { perror("recv error"); exit(-1); } else if(0 == nbytes) { printf("文件描述符[%d]客户端断开了服务器\n",old_fd); //关闭对应的文件描述符 close(old_fd); //剔除挂在树上对应的文件描述符 epoll_ctl(epfd,EPOLL_CTL_DEL,old_fd,&event); } if(!strncmp(buf,"quit",4)) { printf("文件描述符[%d]客户端退出了服务器\n",old_fd); //关闭对应的文件描述符 close(old_fd); //剔除挂在树上对应的文件描述符 epoll_ctl(epfd,EPOLL_CTL_DEL,old_fd,&event); } printf("文件描述符[%d]客户端发来数据[%s]\n",old_fd,buf); //组装应答消息 strcat(buf,"-----k"); //给客户端发送应答消息 send(old_fd,buf,sizeof(buf),0); 十二、关闭套接字(close函数): select、poll、epoll三者的区别:
香港云服务器租用推荐
服务器租用资讯
·广东云服务有限公司怎么样
·广东云服务器怎么样
·广东锐讯网络有限公司怎么样
·广东佛山的蜗牛怎么那么大
·广东单位电话主机号怎么填写
·管家婆 花生壳怎么用
·官网域名过期要怎么办
·官网邮箱一般怎么命名
·官网网站被篡改怎么办
服务器租用推荐
·美国服务器租用
·台湾服务器租用
·香港云服务器租用
·香港裸金属服务器
·香港高防服务器租用
·香港服务器租用特价