当前位置:   article > 正文

TCP网络编程模型从入门到实战中等篇,单服务器多个用户的简单并发版本, 从多进程 到多线程 到 线程池 版本服务器实现...直到最终解决面试经典C10k高并发服务器设计_网络编程多用户连接

网络编程多用户连接

目录

一. 继续解决上一篇留下的疑惑

二. 多进程模型实现服务器支持多用户连接

三. 多线程模型实现服务器支持多用户连接

四. 线程池实现服务器支持多用户连接

五. 总结本章


一. 继续解决上一篇留下的疑惑

  • 如果上一篇没看过的, 附上链接一份, 看完可以帮助后序学习https://blog.csdn.net/weixin_53695360/article/details/122754482?spm=1001.2014.3001.5502
  • 问题抛出 :     为何在一个时间段中, 不可以支持多个用户的同时访问服务器,  只能够支持一个用户访问服务器结束, 断开连接下一个用户才可以进行连接?
  • 原因 :  因为我们前文中的TCP socket  是最简单的, 基本的一对一的通信, 是同步阻塞的方式, 也就是说 当服务器 还没有处理完一个客户的网络 I/O的时候, 或者 读写操作发生阻塞时候 其他客户是没有办法与服务端进行连接操作的
  • 官方解释一下这个同步阻塞原因 : 就是主线程, 建立连接的线程被阻塞处理 网络 IO 了(占用,不空, 处理完这个IO之前没法建立新的连接) 
  • 其实简单的解释一下 同步阻塞含义 :  其他客户端 需要和 正在被服务的客户端一起同步阻塞等待服务器服务结束 哪一个正在服务的客户端 才能够建立新的连接
  • 解决方法综述:    多进程     多线程      线程池       IO 多路复用技术   (本文介绍前三种)

二. 多进程模型实现服务器支持多用户连接

首先是理论支撑

  • 服务器端主进程 (父进程) 仅仅只是负责监听客户端的连接, 每一次accept接受一次连接之后, 我们就 fork 出来一个子进程来处理这个连接所需的服务....
  • 简单回顾fork() :  我不喜欢理解成创建一个子进程, 我喜欢理解成复制一个进程出来, 这个进程中和原来的进程相比, 需要处理的后序代码逻辑是一摸一样的, 内存地址空间, 程序计数器等等都是完成摹刻出来的. 仅有的区别, 就是 pid 不同, 还有 如何区分主进程逻辑还是子进程逻辑, 通过fork 返回值来看, 返回值 为 0 代表是子进程处理逻辑,返回值 > 0, 也就是返回子进程的pid 代表父进程处理逻辑
  • 父子分工 :父进程直接关闭 自己所属的一份 connfd socket文件描述符, 然后只负责监听, 同时负责回收子进程资源, 防止僵尸, 此处为避免阻塞收尸, 可以采取轮询式, 或者我一般直接设置信号处理SIGCHLD 信号. 然后子进程来实现网络IO传输和服务操作....
  • signal(SIGCHLD, SIG_IGN);        //避免子进程僵尸, 设置SIGCHLD信号为SIG_IGN 自动收尸, 不会僵尸..... 丢给系统去处理..
  • 主题fork 多进程实现并发服务逻辑伪代码:
    1. pid = fork();
    2. if (pid < 0) {
    3. ERR_EXIT("fork");
    4. }
    5. if (pid) {
    6. close(connfd); //父进程只进行监听
    7. } else {
    8. close(listenfd); //子进程不需要监听
    9. ..... //服务逻辑代码
    10. }

还是上文连接所实现的过程, 服务端将客户端发来的字符转大写写回.

  1. #include <sys/types.h>
  2. #include <unistd.h>
  3. #include <sys/socket.h>
  4. #include <arpa/inet.h>
  5. #include <stdio.h>
  6. #include <stdlib.h>
  7. #include <ctype.h>
  8. #include <strings.h>
  9. #include <signal.h>
  10. #define SERVE_PORT 12345
  11. #define ERR_EXIT(m) \
  12. do { perror(m); exit(EXIT_FAILURE); } while (0)
  13. typedef struct sockaddr SA;
  14. int listenfd; //设置全局监听套接字, 方便关闭
  15. void handle(int signo) {
  16. fprintf(stdout, "ByeBye!\n");
  17. close(listenfd);
  18. exit(EXIT_SUCCESS);
  19. }
  20. int main() {
  21. signal(SIGCHLD, SIG_IGN);
  22. signal(SIGINT, handle);
  23. int listenfd, connfd, pid;
  24. struct sockaddr_in serveAdd, clientAdd;
  25. socklen_t clientAdd_len;
  26. char ipbuff[256];
  27. //创建套接字
  28. if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
  29. ERR_EXIT("socket"); //协议家族 服务类型(套接字类型), 协议弃用(0)
  30. }
  31. //确定服务端地址簇
  32. bzero(&serveAdd, sizeof(serveAdd)); //0
  33. serveAdd.sin_family = AF_INET;
  34. serveAdd.sin_port = htons(SERVE_PORT);
  35. serveAdd.sin_addr.s_addr = htonl(INADDR_ANY); //注意转网络字节序
  36. //bind 端口 地址信息
  37. if (bind(listenfd, (SA*)&serveAdd, sizeof(serveAdd)) == -1) {
  38. ERR_EXIT("bind");
  39. }
  40. //开始监听..
  41. if (listen(listenfd, 3) == -1) {
  42. ERR_EXIT("listen");
  43. }
  44. printf("Accepting connections..\n");
  45. //循环不断的接收客户的连接请求进行服务
  46. while (1) {
  47. clientAdd_len = sizeof(clientAdd);
  48. if ((connfd = accept(listenfd, (SA*)&clientAdd, &clientAdd_len)) == -1) {
  49. ERR_EXIT("accept");
  50. }
  51. printf("recieve connection from ip is %s and port is %d\n",
  52. inet_ntop(AF_INET, &clientAdd.sin_addr, ipbuff, sizeof(ipbuff)),
  53. ntohs(clientAdd.sin_port));
  54. pid = fork();
  55. if (pid < 0) {
  56. ERR_EXIT("fork");
  57. }
  58. if (pid) {
  59. close(connfd); //父进程只进行监听
  60. } else {
  61. //服务
  62. close(listenfd); //子进程不需要监听
  63. while (1) {
  64. char buff[1024] = {0};
  65. int i;
  66. int n = read(connfd, buff, sizeof(buff)); //读数据
  67. if (n == -1) {
  68. ERR_EXIT("read");
  69. }
  70. if (n == 0) { //说明客户端主动断开连接
  71. break;
  72. }
  73. //处理数据, 简单的小写字符转大写
  74. for (i = 0; i < n; ++i) {
  75. buff[i] = toupper(buff[i]);
  76. }
  77. //写回
  78. write(connfd, buff, n);
  79. }
  80. fprintf(stdout, "ip %s and port is %d interrupt connfd\n",
  81. ipbuff, ntohs(clientAdd.sin_port));
  82. close(connfd);
  83. exit(EXIT_SUCCESS);//子进程完成通信(服务)断开
  84. }
  85. }
  86. return 0;
  87. }
  • 使用多进程来应付多客户端的弊端  : 进程的创建需要消耗大量的系统资源,   又特别是内存资源这些都是有限的, 所以使用多进程的方式, 处理 <= 100 这种 少量客户端还行, 当C10k问题来临是, 根本无能为力, 毕竟进程的产生, 进程间切换的包袱是很重的...... 

三. 多线程模型实现服务器支持多用户连接

  •    竟然 进程间切换, 以及进程创建 所耗系统资源太大了, 那我们就使用轻量级进程, 多个线程共用一个进程地址空间, 来减轻负重  -----  多线程模型

首先还是理论支撑      

  • 多线程共享所在进程数据 :  文件描述符列表,进程空间,代码,全局数据,堆,共享库
  • 线程是运行在进程中的一个 "执行流' 单个进程中可以运行多个线程, 同一进程里面多个线程共享进程的部分资源,          这样线程间切换的时候仅仅只是切换线程私有数据, 寄存器等不共享数据, 相比进程间切换开销大大减少....
  • 线程创建函数的图解分析 : 
  • 然后是线程回收资源的设置, 此处, 我们不设置主线程等待回收, 介绍一个pthread_detach函数
  •  过程描述  (线程功能分析) :   每建立一个新的  connect  自然我们就  获取到一个新的connfd, 此时, 我们   就创建一个子线程, 且传入connfd, 子线程的功能, 就是两台主机网络通信的整个服务器的处理逻辑流程 (简单总结, 产生新的客户连接就创建新的子进程服务)....
  • 主体线程部分代码逻辑:
    1. //线程逻辑代码:
    2. void* Routine(void* arg) {
    3. pthread_detach(pthread_self());
    4. int connfd = (int)arg;
    5. //网络通信服务端服务逻辑。。。
    6. return (void*)0;
    7. }
    8. //循环不断的接收客户的连接请求进行服务
    9. while (1) {
    10. clientAdd_len = sizeof(clientAdd);
    11. if ((connfd = accept(listenfd, (SA*)&clientAdd, &clientAdd_len)) == -1) {
    12. ERR_EXIT("accept");
    13. }
    14. printf("recieve connection from ip is %s and port is %d\n",
    15. inet_ntop(AF_INET, &clientAdd.sin_addr, ipbuff, sizeof(ipbuff)),
    16. ntohs(clientAdd.sin_port));
    17. //创建一个子线程, 来一个新的连接就创建一个
    18. pthread_create(&tid, NULL, Routine, (void*)connfd);
    19. }
           ​​​​​​
  • 整个服务端代码实现
    1. [tangyujie@VM-4-9-centos Serve]$ cat server.c
    2. #include <sys/types.h>
    3. #include <unistd.h>
    4. #include <sys/socket.h>
    5. #include <arpa/inet.h>
    6. #include <stdio.h>
    7. #include <stdlib.h>
    8. #include <ctype.h>
    9. #include <strings.h>
    10. #include <signal.h>
    11. #include <pthread.h>
    12. #define SERVE_PORT 12345
    13. #define ERR_EXIT(m) \
    14. do { perror(m); exit(EXIT_FAILURE); } while (0)
    15. typedef struct sockaddr SA;
    16. int listenfd; //设置全局监听套接字, 方便关闭
    17. void handle(int signo) {
    18. fprintf(stdout, "ByeBye!\n");
    19. close(listenfd);
    20. exit(EXIT_SUCCESS);
    21. }
    22. void* Routine(void* arg) {
    23. pthread_detach(pthread_self()); //线程结束自动回收资源
    24. int connfd = (int)arg; //先将参数强转回去
    25. //服务
    26. while (1) {
    27. char buff[1024] = {0};
    28. int i;
    29. int n = read(connfd, buff, sizeof(buff));//读数据
    30. if (n == -1) {
    31. ERR_EXIT("read");
    32. }
    33. if (n == 0) { //说明客户端断开连接了
    34. break;
    35. }
    36. //处理数据, 简单的小写字符转大写
    37. for (i = 0; i < n; ++i) {
    38. buff[i] = toupper(buff[i]);
    39. }
    40. write(connfd, buff, n); //写回
    41. }
    42. fprintf(stdout, "interrupt connfd end\n");
    43. close(connfd); //服务结束断开连接
    44. return (void*)0;
    45. }
    46. int main() {
    47. signal(SIGCHLD, SIG_IGN);
    48. signal(SIGINT, handle);
    49. int listenfd, connfd;
    50. pthread_t tid;
    51. struct sockaddr_in serveAdd, clientAdd;
    52. socklen_t clientAdd_len;
    53. char ipbuff[256];
    54. //创建套接字
    55. if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
    56. ERR_EXIT("socket"); //协议家族 服务类型(套接字类型), 协议弃用(0)
    57. }
    58. //确定服务端地址簇
    59. bzero(&serveAdd, sizeof(serveAdd)); //0
    60. serveAdd.sin_family = AF_INET;
    61. serveAdd.sin_port = htons(SERVE_PORT);
    62. serveAdd.sin_addr.s_addr = htonl(INADDR_ANY); //注意转网络字节序
    63. //bind 端口 地址信息
    64. if (bind(listenfd, (SA*)&serveAdd, sizeof(serveAdd)) == -1) {
    65. ERR_EXIT("bind");
    66. }
    67. //开始监听..
    68. if (listen(listenfd, 3) == -1) {
    69. ERR_EXIT("listen");
    70. }
    71. printf("Accepting connections..\n");
    72. //循环不断的接收客户的连接请求进行服务
    73. while (1) {
    74. clientAdd_len = sizeof(clientAdd);
    75. if ((connfd = accept(listenfd, (SA*)&clientAdd, &clientAdd_len)) == -1) {
    76. ERR_EXIT("accept");
    77. }
    78. printf("recieve connection from ip is %s and port is %d\n",
    79. inet_ntop(AF_INET, &clientAdd.sin_addr, ipbuff, sizeof(ipbuff)),
    80. ntohs(clientAdd.sin_port));
    81. //创建一个子线程, 来一个新的连接就创建一个
    82. pthread_create(&tid, NULL, Routine, (void*)connfd);
    83. }
    84. return 0;
    85. }
        ​​​​​​
  • 多线程模型  ---  相较多进程模型 缺失 减少了资源消耗, 但是...
  • 每来一个连接就创建一个线程, 线程运行结束之后操作系统还要销毁线程, 这个平凡的创建销毁线程的系统资源销毁(开销)  也是压力相当大的  此时应该可以支持  《= 1000 Client。 所以可以使用     (   线程池避免线程的频繁创建销毁  )

四. 线程池实现服务器支持多用户连接

线程池理论支撑   :附上连接一份, 学完应该足以支撑, 上一份代码是 C++的, 但是所用逻辑理论等是相通的.https://blog.csdn.net/weixin_53695360/article/details/122745816?spm=1001.2014.3001.5502

  • 过程描述, 在整个主线程最开始, 就可以开启,     (提前开启消费者工作线程等待任务来临)   我们的所有    工作线程 (饥饿的消费者, 等待客户端任务来临进行服务)    然后是线程逻辑,  我们在Routine 线程中 循环不断的接收 task_queue任务队列中的任务进行服务......   pop 任务  进行服务...   (因为线程池中是多线程, 任务队列中的任务就是临界资源)  所以为了整个过程的有序进行, 我们使用  锁 保护临界资源, 条件变量, 避免CPU的无端浪费....   
  • 上述语言看不明白, 说明线程池基础却有缺失, 可能需要阅读上文链接,或进一步查询资料

函数刨析

  • 然后废话不多说, 上代码, 还是服务端代码
    1. [tangyujie@VM-4-9-centos Serve]$ cat server.c
    2. #include <sys/types.h>
    3. #include <unistd.h>
    4. #include <sys/socket.h>
    5. #include <arpa/inet.h>
    6. #include <stdio.h>
    7. #include <stdlib.h>
    8. #include <ctype.h>
    9. #include <strings.h>
    10. #include <signal.h>
    11. #include <pthread.h>
    12. #define SERVE_PORT 12345
    13. #define ERR_EXIT(m) \
    14. do { perror(m); exit(EXIT_FAILURE); } while (0)
    15. typedef struct sockaddr SA;
    16. int listenfd; //设置全局监听套接字, 方便关闭
    17. //设置任务结构体.....
    18. typedef struct Task {
    19. int connfd; //任务需要晓得哈是哪个做
    20. struct Task* next;
    21. } Task;
    22. typedef struct TaskQueue {
    23. Task* front;
    24. Task* tail;
    25. pthread_mutex_t lock;
    26. pthread_cond_t cond;
    27. } TaskQueue;
    28. TaskQueue* tp;
    29. void ClearTask(Task* head) {
    30. Task* p = head, *q;
    31. while (p) {
    32. q = p->next;
    33. free(p);
    34. p = q;
    35. }
    36. }
    37. void DestroyTaskQueue(TaskQueue* tp) {
    38. ClearTask(tp->front);
    39. pthread_mutex_destroy(&tp->lock);
    40. pthread_cond_destroy(&tp->cond);
    41. }
    42. void handle(int signo) {
    43. fprintf(stdout, "ByeBye!\n");
    44. DestroyTaskQueue(tp);
    45. close(listenfd);
    46. exit(EXIT_SUCCESS);
    47. }
    48. TaskQueue* InitTaskQueue() {
    49. TaskQueue* tp = (TaskQueue*)malloc(sizeof(TaskQueue));
    50. tp->front = tp->tail = NULL;
    51. pthread_mutex_init(&tp->lock, NULL);
    52. pthread_cond_init(&tp->cond, NULL);
    53. return tp;
    54. }
    55. void Lock(TaskQueue* tp) {
    56. pthread_mutex_lock(&tp->lock);
    57. }
    58. void Unlock(TaskQueue* tp) {
    59. pthread_mutex_unlock(&tp->lock);
    60. }
    61. void WakeUp(TaskQueue* tp) {
    62. pthread_cond_signal(&tp->cond);
    63. }
    64. void Wait(TaskQueue* tp) {
    65. pthread_cond_wait(&tp->cond, &tp->lock);
    66. }
    67. Task* GetNewTask(int connfd) {
    68. Task* newTask = (Task*)malloc(sizeof(Task));
    69. newTask->connfd = connfd;
    70. newTask->next = NULL;
    71. return newTask;
    72. }
    73. int IsEmpty(TaskQueue* tp) {
    74. return tp->front == NULL;
    75. }
    76. void Push(TaskQueue* tp, Task* task) {
    77. Lock(tp); //临界资源操作需要原子操作, 锁之间
    78. if (IsEmpty(tp)) {
    79. tp->front = task;
    80. tp->tail = task;
    81. WakeUp(tp); //唤醒通知有任务了
    82. Unlock(tp);
    83. return ;
    84. }
    85. tp->tail->next = task;
    86. tp->tail = task;
    87. WakeUp(tp); //唤醒通知有任务了
    88. Unlock(tp);
    89. }
    90. Task* Pop(TaskQueue* tp) {
    91. Lock(tp);
    92. Task* poptask;
    93. while (IsEmpty(tp)) { //没有任务, 就一直等待生产
    94. Wait(tp); //循环是为了避免伪唤醒. 唤醒多个线程
    95. } //但是有些线程 还要继续Wait, 所以循环
    96. poptask = tp->front;
    97. tp->front = tp->front->next;
    98. if (tp->front == NULL) tp->tail = NULL;
    99. Unlock(tp);
    100. return poptask;
    101. }
    102. void* Routine(void* arg) {
    103. TaskQueue* tp = (TaskQueue*)arg;
    104. while (1) { //不断的尝试Pop获取任务进行处理
    105. int connfd = Pop(tp)->connfd; //Pop任务后获取connfd
    106. //服务
    107. while (1) {
    108. char buff[1024] = {0};
    109. int i;
    110. int n = read(connfd, buff, sizeof(buff));//读数据
    111. if (n == -1) {
    112. ERR_EXIT("read");
    113. }
    114. if (n == 0) { //说明客户端断开连接了
    115. break;
    116. }
    117. //处理数据, 简单的小写字符转大写
    118. for (i = 0; i < n; ++i) {
    119. buff[i] = toupper(buff[i]);
    120. }
    121. write(connfd, buff, n); //写回
    122. }
    123. fprintf(stdout, "interrupt connfd end\n");
    124. close(connfd); //服务结束断开连接
    125. }
    126. return (void*)0;
    127. }
    128. void InitPool(TaskQueue* tp, int n) {
    129. pthread_t tid;
    130. int i;
    131. for (i = 0; i < n; ++i) {
    132. pthread_create(&tid, NULL, Routine, (void*)tp); //此处传入tp 需要拿取任务
    133. pthread_detach(tid); //线程结束自动回收线程资源
    134. }
    135. }
    136. int main() {
    137. signal(SIGCHLD, SIG_IGN);
    138. signal(SIGINT, handle);
    139. tp = InitTaskQueue();//初始化任务队列
    140. InitPool(tp, 3); //一开始就开启消费者多线程
    141. int listenfd, connfd;
    142. pthread_t tid;
    143. struct sockaddr_in serveAdd, clientAdd;
    144. socklen_t clientAdd_len;
    145. char ipbuff[256];
    146. //创建套接字
    147. if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
    148. ERR_EXIT("socket"); //协议家族 服务类型(套接字类型), 协议弃用(0)
    149. }
    150. //确定服务端地址簇
    151. bzero(&serveAdd, sizeof(serveAdd)); //0
    152. serveAdd.sin_family = AF_INET;
    153. serveAdd.sin_port = htons(SERVE_PORT);
    154. serveAdd.sin_addr.s_addr = htonl(INADDR_ANY); //注意转网络字节序
    155. //bind 端口 地址信息
    156. if (bind(listenfd, (SA*)&serveAdd, sizeof(serveAdd)) == -1) {
    157. ERR_EXIT("bind");
    158. }
    159. //开始监听..
    160. if (listen(listenfd, 3) == -1) {
    161. ERR_EXIT("listen");
    162. }
    163. printf("Accepting connections..\n");
    164. //循环不断的接收客户的连接请求进行服务
    165. while (1) {
    166. clientAdd_len = sizeof(clientAdd);
    167. if ((connfd = accept(listenfd, (SA*)&clientAdd, &clientAdd_len)) == -1) {
    168. ERR_EXIT("accept");
    169. }
    170. printf("recieve connection from ip is %s and port is %d\n",
    171. inet_ntop(AF_INET, &clientAdd.sin_addr, ipbuff, sizeof(ipbuff)),
    172. ntohs(clientAdd.sin_port));
    173. Task* newtask = GetNewTask(connfd);
    174. Push(tp, newtask);
    175. }
    176. return 0;
    177. }

然后是上述所有服务端均可通用的客户端代码:

  1. #include <sys/types.h>
  2. #include <unistd.h>
  3. #include <sys/socket.h>
  4. #include <arpa/inet.h>
  5. #include <stdio.h>
  6. #include <stdlib.h>
  7. #include <strings.h>
  8. #include <signal.h>
  9. #include <string.h>
  10. #define SERVE_PORT 12345 //端口号
  11. #define ERR_EXIT(m)\
  12. do { perror(m); exit(EXIT_FAILURE); } while (0) //错误处理
  13. typedef struct sockaddr SA;
  14. int sockfd; //设置全局, 方便关闭
  15. void handle(int signo) {
  16. fprintf(stdout, "ByeBye!\n");
  17. close(sockfd);
  18. exit(EXIT_SUCCESS);
  19. }
  20. int main(int argc, char* argv[]) {
  21. if (argc != 2) {
  22. fprintf(stderr, "%s <ip>", argv[0]);
  23. close(EXIT_FAILURE);
  24. }
  25. signal(SIGINT, handle);
  26. struct sockaddr_in serveAdd;
  27. //创建套接字
  28. if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
  29. ERR_EXIT("socket"); //协议家族 服务类型(套接字类型), 协议弃用(0)
  30. }
  31. //确定服务端地址簇
  32. bzero(&serveAdd, sizeof(serveAdd)); //0
  33. serveAdd.sin_family = AF_INET;
  34. serveAdd.sin_port = htons(SERVE_PORT);
  35. //将传入的ip字符串转换为 sin_addr
  36. if (inet_pton(AF_INET, argv[1], &serveAdd.sin_addr) == -1) {
  37. ERR_EXIT("inet_ntop");
  38. }
  39. //不需要绑定端口号 系统随机分配一个临时端口号, 直接连接
  40. if (connect(sockfd, (SA*)&serveAdd, sizeof(serveAdd)) == -1) {
  41. ERR_EXIT("connect");
  42. }
  43. while (1) { //死循环, 使用ctrl c信号关闭连接
  44. char buff[1024];
  45. printf("请说>>");
  46. scanf("%s", buff);
  47. write(sockfd, buff, strlen(buff));
  48. int n = read(sockfd, buff, sizeof(buff));
  49. if (n == -1) {
  50. ERR_EXIT("read");
  51. }
  52. buff[n] = 0;
  53. fprintf(stdout, ">>%s\n", buff);
  54. }
  55. }

五. 总结本章

  • 本文首先通过 同步阻塞的原因 引出来 主线程被阻塞处理 网络 IO 服务了, 这样当他服务一个客户的时候, 其他客户都无法与服务器建立连接. 
  • 然后为了解决这个问题  提出来了  多进程 多线程模型 线程池模型 多路IO复用(遗留)
  • 目的最终是为了解决 C10k 问题, 多进程 弊端分析(进程创建销毁, 切换) 系统资源耗费巨大, 最多支持 100 左右用户
  • 为了减少系统资源消耗  + 减少切换压力, ----  引出来 多线程模型, 多个线程共享所在进程中的进程资源,  线程间切换  仅仅只是线程私有数据和寄存器的切换(切换压力小)但是不停的创建销毁线程压力大
  • 于是又引出来 线程池来实现线程的复用, 减去线程不停创建和销毁的压力
  • C10k 问题就成为本章留疑了, 大家可以先自行讨论, 评论区给与简易,以及多路复用的含义解释呀等。。。。 总结时候如果存在不清晰读者可回溯 

本文仅为自主学习复习使用, 借鉴了部分网络资源, 如果有不同意见,都可留言讨论,共同学习进步

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/683799
推荐阅读
相关标签
  

闽ICP备14008679号