TCP预先创建线程服务器程序,主线程统一accept

最后一个使用线程的服务器程序设计示范是在程序启动阶段创建一个线程池之后只让主线程调用accept并把每个客户连接传递给池中某个可用线程。
            本设计示范的问题在于主线程如何把一个已连接套接字传递给线程池中某个可用线程。这里有多个实现手段。我们原本可以如前使用描述符传递,不过既然所有线程和所有描述符都在同一个进程之内,我们没有必要把一个描述符从一个线程传递到另一个线程。接收线程只需知道这个已连接套接字描述符的值,而描述符传递实际传递的并非这个值,而是对这个套接字的一个引用,因而将返回一个不同于原值的描述符(该套接字的引用计数也被递增)。

  1. typedef struct {
  2. pthread_t thread_tid; /* thread ID */
  3. long thread_count; /* # connections handled */
  4. } Thread;
  5. Thread *tptr; /* array of Thread structures; calloc'ed */
  6.  
  7. #define MAXNCLI 32
  8. int clifd[MAXNCLI], iget, iput;
  9. pthread_mutex_t clifd_mutex;
  10. pthread_cond_t clifd_cond;

定义存放已连接套接字描述符的共享数组
07-10        我们还定义一个clifd数组,由主线程往中存入已接受的已连接套接字描述符,并由线程池中的可用线程从中取出一个以服务相应的客户。iput是主线程将往该数组中存入的下一个元素的下标,iget是线程池中某个线程将从该数组中取出的下一个元素的下标。这个有所有线程共享的数据结构自然必须得到保护,我们使用互斥锁和条件变量做到这一点。

  1. #include "unpthread.h"
  2. #include "pthread08.h"
  3.  
  4. static int nthreads;
  5. pthread_mutex_t clifd_mutex = PTHREAD_MUTEX_INITIALIZER;
  6. pthread_cond_t clifd_cond = PTHREAD_COND_INITIALIZER;
  7.  
  8. int
  9. main(int argc, char **argv)
  10. {
  11. int i, listenfd, connfd;
  12. void sig_int(int), thread_make(int);
  13. socklen_t addrlen, clilen;
  14. struct sockaddr *cliaddr;
  15.  
  16. if (argc == 3)
  17. listenfd = Tcp_listen(NULL, argv[1], &addrlen);
  18. else if (argc == 4)
  19. listenfd = Tcp_listen(argv[1], argv[2], &addrlen);
  20. else
  21. err_quit("usage: serv08 [ <host> ] <port#> <#threads>");
  22. cliaddr = Malloc(addrlen);
  23.  
  24. nthreads = atoi(argv[argc-1]);
  25. tptr = Calloc(nthreads, sizeof(Thread));
  26. iget = iput = 0;
  27.  
  28. /* 4create all the threads */
  29. for (i = 0; i < nthreads; i++)
  30. thread_make(i); /* only main thread returns */
  31.  
  32. Signal(SIGINT, sig_int);
  33.  
  34. for ( ; ; ) {
  35. clilen = addrlen;
  36. connfd = Accept(listenfd, cliaddr, &clilen);
  37.  
  38. Pthread_mutex_lock(&clifd_mutex);
  39. clifd[iput] = connfd;
  40. if (++iput == MAXNCLI)
  41. iput = 0;
  42. if (iput == iget)
  43. err_quit("iput = iget = %d", iput);
  44. Pthread_cond_signal(&clifd_cond);
  45. Pthread_mutex_unlock(&clifd_mutex);
  46. }
  47. }
  48. void
  49. sig_int(int signo)
  50. {
  51. int i;
  52. void pr_cpu_time(void);
  53.  
  54. pr_cpu_time();
  55.  
  56. for (i = 0; i < nthreads; i++)
  57. printf("thread %d, %ld connections\n", i, tptr[i].thread_count);
  58.  
  59. exit(0);
  60. }

创建线程池
29-30    使用thread_make创建池中的每个线程。

等待客户连接
34-46    主线程大部分时间阻塞在accept调用中,等待各个客户连接的到达。一旦某个客户连接到达,主线程就把它的已连接套接字描述符存入clifd数组的下一个元素,不过需事先获取保护该数组的互斥锁。主线程还检查iput下标没有赶上iget下标(若赶上则说明该数组不够大),并发送信号到条件变量信号,然后释放互斥锁,以允许线程池中某个线程为这个客户服务。

  1. #include "unpthread.h"
  2. #include "pthread08.h"
  3.  
  4. void
  5. thread_make(int i)
  6. {
  7. void *thread_main(void *);
  8.  
  9. Pthread_create(&tptr[i].thread_tid, NULL, &thread_main, (void *) i);
  10. return; /* main thread returns */
  11. }
  12.  
  13. void *
  14. thread_main(void *arg)
  15. {
  16. int connfd;
  17. void web_child(int);
  18.  
  19. printf("thread %d starting\n", (int) arg);
  20. for ( ; ; ) {
  21. Pthread_mutex_lock(&clifd_mutex);
  22. while (iget == iput)
  23. Pthread_cond_wait(&clifd_cond, &clifd_mutex);
  24. connfd = clifd[iget]; /* connected socket to service */
  25. if (++iget == MAXNCLI)
  26. iget = 0;
  27. Pthread_mutex_unlock(&clifd_mutex);
  28. tptr[(int) arg].thread_count++;
  29.  
  30. web_child(connfd); /* process request */
  31. Close(connfd);
  32. }
  33. }

等待未之服务的客户描述符
21-31      线程池中每个线程都试图获取保护clifd数组的互斥锁。获得之后就测试iput与iget,若两者相等则无事可做,于是通过调用pthread_cond_wait睡眠在条件变量上。主线程接受一个连接后将待用pthread_cond_signal向条件变量发送信号,以唤醒睡眠在其上的线程。若测得iput与iget不等则从clifd数组中取出下一个元素以获得一个连接,然后调用web_child。

UNIX网络编程——客户/服务器程序设计示范(八)的更多相关文章

  1. UNIX网络编程——客户/服务器程序设计示范(总结)

    (1)当系统负载较轻是,每来一个客户请求现场派生一个子进程为之服务的传统并发服务器程序模型就足够了.这个模型甚至可以与inetd结合使用,也就是inetd处理每个连接的接收.我们的其他意见是就重负荷运 ...

  2. UNIX网络编程——客户/服务器程序设计示范(七)

        TCP预先创建线程服务器程序,每个线程各自accept 前面讨论过预先派生一个子进程池快于为每个客户线程派生一个子进程.在支持线程的系统上,我们有理由预期在服务器启动阶段预先创建一个线程池以取 ...

  3. UNIX网络编程——客户/服务器程序设计示范(六)

    TCP并发服务器程序,每个客户一个线程 前面讲述了,每个客户一个进程的服务器,或为每个客户现场fork一个子进程,或者预先派生一定数目的子进程.如果服务器主机支持线程,我们就可以改用线程以取代子进程. ...

  4. UNIX网络编程——客户/服务器程序设计示范(五)

        TCP预先派生子进程服务器程序,传递描述符 对预先派生子进程服务器程序的最后一个修改版本是只让父进程调用accept,然后把所接受的已连接套接字"传递"给某个子进程.这么做 ...

  5. UNIX网络编程——客户/服务器程序设计示范(三)

    TCP预先派生子进程服务器程序,accept无上锁保护 我们的第一个"增强"型服务器程序使用称为预先派生子进程的技术.使用该技术的服务器不像传统意义的并发服务器那样为每个客户现场派 ...

  6. UNIX网络编程——客户/服务器程序设计示范(二)

        TCP并发服务器程序,每个客户一个子进程 传统上并发服务器调用fork派生一个子进程来处理每个客户.这使得服务器能够同时为多个客户服务,每个进程一个客户.客户数目的唯一限制是操作系统对以其名义 ...

  7. UNIX网络编程——客户/服务器程序设计示范(一)

    下面给出的是客户程序用于测试我们的服务器程序的各个变体. #include "unp.h" #define MAXN 16384 /* max # bytes to request ...

  8. UNIX网络编程——客户/服务器程序设计示范(四)

        TCP预先派生子进程服务器程序,accept使用线程上锁保护 我们使用线程上锁保护accept,因为这种方法不仅适用于同一进程内各线程之间的上锁,而且适用于不同进程之间的上锁.        ...

  9. UNIX网络编程——客户/服务器心搏函数

    阅读此博客时,可以参考以前的博客<<UNIX网络编程--socket的keep-alive>>和<<UNIX网络编程--套接字选项(心跳检测.绑定地址复用)> ...

随机推荐

  1. 中断API之setup_irq【转】

    转自:https://blog.csdn.net/tiantao2012/article/details/78957472 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blo ...

  2. IPQ4028开启I2C功能

    0 概述 IPQ4028是一款集约式4核心ARM7 SOC芯片,内嵌独立双频WiFi子系统,offload式,支持MU-MIMO,最高支持1.2Gbps.标准的官方Demo方案中,IPQ4019开启了 ...

  3. Python【第一课】 Python简介和基础

    本节内容 Python安装(windows) 第一个程序(windows中的python) 变量 字符编码 注释 用户输入 模块初步认识 数据类型 数据运算 表达式if...else 表达式for l ...

  4. python中int的功能简单介绍

    Int的功能介绍 1. 绝对值 x.__abs__()等同于abs(x) 2. 加法 x.__add__(y)等同于x+y 3. 与运算 x.__and__(y)等同于x&y 4. 布尔运算 ...

  5. C语言程序第一次作业

    (一)实验总结 1. 求圆面积和周长 (1)题目 输入圆的半径,计算圆的周长和面积. (2)流程图 (3)测试数据及运行结果 测试数据1:r=2 运行结果: (4)实验分析 没有问题 2.判断闰年 ( ...

  6. C语言成语设计第一次作业

    一 1.求圆面积和周长 输入圆的半径,计算圆的周长和面积. 2.流程图 3.测试数据及运行结果 测试数据:r=7 运行结果 4.实验分析 问题:第一次输入提示时未加双引号 解决办法:发现问题后加了双引 ...

  7. 基于FPGA的数字识别的实现

    欢迎大家关注我的微信公众号:FPGA开源工作室     基于FPGA的数字识别的实现二 作者:lee神 1 背景知识 1.1基于FPGA的数字识别的方法 通常,针对印刷体数字识别使用的算法有:基于模版 ...

  8. 解决nodejs中json序列化时Date类型默认为UTC格式

    在nodejs中,json序列化时Date类型时,默认转为UTC格式. 如下图 上面只是一个例子,下面我用一个更具体化的例子来展示一个这个情况,我们在开发WEB项目中,经常用到Express组件, 我 ...

  9. Servlet生命周期与工作原理(转载)

    Servlet生命周期分为三个阶段: 1,初始化阶段  调用init()方法 2,响应客户请求阶段 调用service()方法 3,终止阶段 调用destroy()方法 Servlet初始化阶段: 在 ...

  10. sql 复习练习

          一.基础1.说明:创建数据库CREATE DATABASE database-name2.说明:删除数据库drop database dbname3.说明:备份sql server--- ...