0


【高性能服务器】select模型

🔥博客主页: 我要成为C++领域大神
🎥****系列专栏:【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】
❤️感谢大家点赞👍收藏⭐评论✍️

本博客致力于知识分享,与更多的人进行学习交流

IO多路复用就是复用一个线程,从原先一个客户端需要一个线程去调用recv询问内核数据是否已经就绪,那么多个客户端就需要多个线程,转变成现在多个客户端都用一个线程使用select/poll去统一管理,主动通知用户哪些数据已经就绪(read,write,accept等事件),所以复用了这个线程,减少了系统开销。

在客户端增加时,线程不会呈O(n)增加

关于recv和accept工作流程

  1. accpet

通过服务端文件描述符监听socket事件,当监听到

  1. READ_EVENT

事件时,说明有其他网络端向此socket发送数据,触发socket读事件(三次握手中客户端会发送数据),建立TCP连接。

  1. recv

通过客户端文件描述符监听socket事件,当监听到READ_EVENT事件,处理事件,将数据读取到用户缓冲区buffer

通过IO复用,实现监听到socket事件就绪后,直接调用accpet或recv即可,直接完成TCP连接或者数据读取,两个函数不会阻塞。

可以实现单进程一对多效果,但是没有使用并发技术

处理的业务复杂度不能过高,要在极短的时间内处理若干任务,投入二次监听

IO多路复用第一版select


实现原理

select 实现多路复用的方式是,将已连接的 Socket 都放到一个监听集合,然后调用 select 函数将文件描述符集合拷贝到内核里,让内核来检查是否有网络事件产生,就是通过遍历监听集合的方式进行检查。

当检查到有事件产生后,将此 Socket 标记为可读或可写, 接着再把整个文件描述符集合拷贝回用户态里,然后用户态还需要再通过遍历的方法找到可读或可写的 Socket,然后再对其处理。

所以,对于 select 这种方式,需要进行 2 次「遍历」文件描述符集合,一次是在内核态里,一个次是在用户态里 ,而且还会发生 2 次「拷贝」文件描述符集合,先从用户空间传入内核空间,由内核修改后,再传出到用户空间中。

select 使用固定长度的 BitsMap,表示文监听集合,而且所支持的文件描述符的个数是有限制的,在 Linux 系统中,由内核中的 FD_SETSIZE 限制, 默认最大值为 1024,最多只能监听1021个用户socket,因为0、1、2是标准文件描述符。

监听集合中对应的socket位码是1,表示监听次socket,为0表示不监听

在select这种I/O多路复用机制下,我们需要把想监控的文件描述集合通过函数参数的形式告诉select,然后select会将这些文件描述符集合拷贝到内核中,我们知道数据拷贝是有性能损耗的,因此为了减少这种数据拷贝带来的性能损耗,Linux内核对集合的大小做了限制,并规定用户监控的文件描述集合不能超过1024个,同时当select返回后我们仅仅能知道有些文件描述符可以读写了,但是我们不知道是哪一个,因此必须再遍历一边找到具体是哪个文件描述符可以读写了。


实现流程

核心接口

  1. void FD_ZERO(fd_set **fdset*)

初始化监听集合为0

  1. void FD_SET(int *fd*,fd_set **fdset*)

对set集合中fd对应位码设置为1

  1. void FD_CLR(int *fd*,fd_set **fdset*)

对set集合中fd对应位码设置为0

  1. int bitcode=void FD_ISSET(int *fd*,fd_set **fdset*)

查看fd在监听集合中是1还是0,并直接返回


  1. int ready=select(int nfds, fd_set* readset, fd_set* writeset, fd_set* exeptset,struct timeval* timeout);
  1. nfds

表示被select管理的描述符个数。值为最大描述符+1.不是描述符最大值

  1. readset

  1. writeset

  1. exeptset

可读事件集合、可写事件集合、异常事件集合。这三者都可以填null

  1. timeout

超时时间有三种含义: 阻塞(null)、正常超时、非阻塞(0)


使用服务器测试业务:

客户端向标准输入发送小写字符串,服务端响应回复对应大写字符,"abcAS"->"ABCAS"

客户端向服务端发送关键字localtime,服务端响应回复系统时间、

代码实现

MySock.h

  1. #ifndef _MYSOCK_H_
  2. #define _MYSOCK_H_
  3. #include <arpa/inet.h>
  4. #include <ctype.h>
  5. #include <errno.h>
  6. #include <netinet/in.h>
  7. #include <stdio.h>
  8. #include <stdlib.h>
  9. #include <string.h>
  10. #include <sys/select.h>
  11. #include <sys/socket.h>
  12. #include <sys/types.h>
  13. #include <time.h>
  14. #include <unistd.h>
  15. int SOCKET(int domain, int type, int protocol);
  16. int BIND(int sockfd, struct sockaddr* addr, socklen_t addrlen);
  17. ssize_t RECV(int sockfd, void* buf, size_t len, int flags);
  18. ssize_t SEND(int sockfd, void* buf, size_t len, int flags);
  19. int CONNECT(int sockfd, struct sockaddr* addr, socklen_t addrlen);
  20. int ACCEPT(int sockfd, struct sockaddr* addr, socklen_t* addrlen);
  21. int LISTEN(int sockfd, int backlog);
  22. char* FGETS(char* s, int size, FILE* stream);
  23. int SELECT(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds,
  24. struct timeval* timeout);
  25. int socket_init();
  26. int return_response(int clientfd, const char* clientip);
  27. //void strDeal(int *client_fd);
  28. // 全局变量声明
  29. char recv_buf[1024];
  30. char time_buf[64];
  31. int serverFd, clientFd;
  32. struct sockaddr_in clientAddr;
  33. fd_set set, oset;
  34. int client_array[1020];
  35. int maxfd, ready;
  36. socklen_t addrlen;
  37. char clientip[16];
  38. time_t tp;
  39. ssize_t recvlen;
  40. int toupper_flag;
  41. #define SHUTDOWN 1
  42. #endif

MySock.c

  1. #include "MySock.h"
  2. int SOCKET(int domain, int type, int protocol) {
  3. int reval = socket(domain, type, protocol);
  4. if (reval == -1) {
  5. perror("socket call failed");
  6. exit(0);
  7. }
  8. return reval;
  9. }
  10. int BIND(int sockfd, struct sockaddr* addr, socklen_t addrlen) {
  11. int reval = bind(sockfd, addr, addrlen);
  12. if (reval == -1) {
  13. perror("bind call failed");
  14. exit(0);
  15. }
  16. return reval;
  17. }
  18. ssize_t RECV(int sockfd, void* buf, size_t len, int flags) {
  19. ssize_t reval;
  20. reval = recv(sockfd, buf, len, flags);
  21. return reval;
  22. }
  23. ssize_t SEND(int sockfd, void* buf, size_t len, int flags) {
  24. ssize_t reval;
  25. reval = send(sockfd, buf, len, flags);
  26. if (reval == -1)
  27. perror("send call failed");
  28. return reval;
  29. }
  30. int CONNECT(int sockfd, struct sockaddr* addr, socklen_t addrlen) {
  31. int reval = connect(sockfd, addr, addrlen);
  32. if (reval == -1) {
  33. perror("connect call failed");
  34. exit(0);
  35. }
  36. return reval;
  37. }
  38. int ACCEPT(int sockfd, struct sockaddr* addr, socklen_t* addrlen) {
  39. int reval = accept(sockfd, addr, addrlen);
  40. if (reval == -1) {
  41. perror("accept call failed");
  42. exit(0);
  43. }
  44. return reval;
  45. }
  46. int LISTEN(int sockfd, int backlog) {
  47. int reval = listen(sockfd, backlog);
  48. if (reval == -1) {
  49. perror("listen call failed");
  50. exit(0);
  51. }
  52. return reval;
  53. }
  54. char* FGETS(char* s, int size, FILE* stream) {
  55. char* str;
  56. if ((str = fgets(s, size, stream)) != NULL) {
  57. return str;
  58. } else {
  59. perror("fgets call failed");
  60. exit(0);
  61. }
  62. }
  63. int SELECT(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds,
  64. struct timeval* timeout) {
  65. int reval = select(nfds, readfds, writefds, exceptfds, timeout);
  66. if (reval == -1) {
  67. perror("select call failed");
  68. exit(0);
  69. }
  70. return reval;
  71. }
  72. int socket_init() {
  73. struct sockaddr_in sockAddr;
  74. sockAddr.sin_family = AF_INET;
  75. sockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
  76. sockAddr.sin_port = htons(8080);
  77. int sock_fd = SOCKET(AF_INET, SOCK_STREAM, 0);
  78. BIND(sock_fd, (struct sockaddr*)&sockAddr, sizeof(sockAddr));
  79. LISTEN(sock_fd, 5);
  80. return sock_fd;
  81. }
  82. int return_response(int clientfd, const char* clientip) {
  83. char response[1024];
  84. bzero(response, sizeof(response));
  85. sprintf(response, "Hi [%s],This is TCP Server Working...\n", clientip);
  86. SEND(clientfd, response, sizeof(response), 0);
  87. }

SelectServer.c

  1. #include "MySock.h"
  2. int main() {
  3. bzero(recv_buf, sizeof(recv_buf));
  4. bzero(time_buf, sizeof(time_buf));
  5. bzero(clientip,sizeof(clientip));
  6. serverFd = socket_init();
  7. FD_SET(serverFd, &set); // 设置监听
  8. int i;
  9. for (i = 0; i < 1020; ++i) {
  10. client_array[i] = -1;
  11. }
  12. maxfd = serverFd;
  13. printf("Test Select Server is Running...\n");
  14. while (SHUTDOWN) {
  15. oset = set;
  16. ready = SELECT(maxfd+1, &oset, NULL, NULL, NULL);
  17. while (ready) { // 辨别就绪
  18. if (FD_ISSET(serverFd, &oset)) {
  19. addrlen = sizeof(clientAddr);
  20. clientFd =ACCEPT(serverFd, (struct sockaddr*)&clientAddr, &addrlen);
  21. inet_ntop(AF_INET, &clientAddr.sin_addr.s_addr, clientip, 16);
  22. printf("Listen Server Socket Successfully Call Accept, Client IP [%s], PORT[%d]\n",clientip, ntohs(clientAddr.sin_port));
  23. return_response(clientFd, clientip);
  24. if (maxfd < clientFd)
  25. maxfd = clientFd;
  26. for (i = 0; i < 1020; ++i)
  27. if (client_array[i] == -1) {
  28. client_array[i] = clientFd;
  29. break;
  30. }
  31. FD_SET(clientFd, &set);//新socket设置监听
  32. FD_CLR(serverFd,&oset);//处理完毕,清理就绪集合
  33. } else {
  34. // 仅处理一次客户端请求,单进程不允许客户端持续占用
  35. for (i = 0; i < 1020; ++i)
  36. {if (client_array[i] != -1)
  37. if (FD_ISSET(client_array[i], &oset))
  38. {
  39. if ((recvlen = RECV(client_array[i], recv_buf, sizeof(recv_buf), 0)) >0)
  40. { // 处理客户端业务
  41. printf("Client Say:%s\n", recv_buf);
  42. if (strcmp(recv_buf, "localtime") == 0) {
  43. tp = time(NULL); // 获取时间种子
  44. ctime_r(&tp, time_buf);
  45. time_buf[strcspn(time_buf, "\n")] = '\0';
  46. printf("[%s]Response SysTime Successfully!\n", clientip);
  47. SEND(client_array[i], time_buf, strlen(time_buf) + 1, 0);
  48. bzero(time_buf, sizeof(time_buf));
  49. } else {
  50. toupper_flag = 0;
  51. while (recvlen > toupper_flag) {
  52. recv_buf[toupper_flag] = toupper(recv_buf[toupper_flag]);
  53. ++toupper_flag;
  54. }
  55. printf("[%s]Response Toupper Successfully!\n", clientip);
  56. SEND(client_array[i], recv_buf, recvlen, 0);
  57. bzero(recv_buf, sizeof(recv_buf));
  58. }
  59. } else if (recvlen == 0) {
  60. FD_CLR(client_array[i], &set); // 删除监听
  61. close(client_array[i]);
  62. client_array[i] = -1;
  63. printf("Client is Exiting, Delete Listen Item.\n");
  64. }
  65. FD_CLR(client_array[i],&oset);//处理完毕,清理就绪集合
  66. break;
  67. }
  68. }
  69. }
  70. ready--;
  71. }
  72. }
  73. printf("Server is Over\n");
  74. close(serverFd);
  75. return 0;
  76. }

Client.c

  1. #include "MySock.c"
  2. //客户端源码编写,连接服务器成功,服务器反馈信息
  3. #define _IP "xxx.xxx.xxx.xxx"
  4. #define _PORT 8080
  5. int main()
  6. {
  7. struct sockaddr_in ServerAddr;
  8. bzero(&ServerAddr,sizeof(ServerAddr));
  9. ServerAddr.sin_family=AF_INET;
  10. ServerAddr.sin_port=htons(_PORT);
  11. inet_pton(AF_INET,_IP,&ServerAddr.sin_addr.s_addr);
  12. int Myfd=SOCKET(AF_INET,SOCK_STREAM,0);
  13. //看需求决定是否要绑定
  14. char Response[1024];//存放服务端反馈信息
  15. ssize_t recvlen;
  16. bzero(Response,sizeof(Response));
  17. char sendbuf[1024];
  18. if((CONNECT(Myfd,(struct sockaddr *)&ServerAddr,sizeof(ServerAddr)))==0)
  19. {
  20. while(1)
  21. {
  22. if((recvlen=RECV(Myfd,Response,sizeof(Response),0))>0)
  23. {
  24. printf("%s\n",Response);
  25. }
  26. printf("Please Type Some text:");//读取标准输入发送给服务端
  27. FGETS(sendbuf,sizeof(sendbuf),stdin);
  28. sendbuf[strcspn(sendbuf,"\n")]='\0';
  29. SEND(Myfd,sendbuf,sizeof(sendbuf),0);
  30. }
  31. }
  32. close(Myfd);
  33. printf("Client is Over\n");
  34. return 0;
  35. }

运行结果

select模型使用一个进程实现对多个客户端的统一管理

select模式优缺点

优点

1.可以通过简单的代码实现一对多效果, 比较轻量

2.select模型拥有较强的兼容性,各个平台和语言都有实现

3.支持微秒级别的定时阻塞监听,如果对时间精度有需求,select可以满足

4.较为适合监听数量较小(局域网)等场景

缺点

1.监听数量较小,最大只能监听1024,无法满足 高并发需求

2.轮询问题, 随着轮询数量的增长,IO处理性能呈线性下降

3.用户需要对传出传出监听集合进行分离设置

4.select只返回就绪的数量,没有反馈就绪的socket,需要用户自行遍历查找,开销较大

5.select可以监听的事件数量较少,select设置监听是批处理以集合为单位的无法对不同的socket 设置不同的事件监听

6.select多轮使用会出现大量重复的拷贝开销和挂载监听开销

标签: 网络 linux c语言

本文转载自: https://blog.csdn.net/Coldreams/article/details/140210817
版权归原作者 我要成为C++领域大神 所有, 如有侵权,请联系我们删除。

“【高性能服务器】select模型”的评论:

还没有评论