一、服务端特性概述
1、使用EPOLL模型在服务器中加入(网络IO监听,大量的监听能力)
EPOLL采用边缘触发模式(后话)
2、线程池模型进行并发处理业务(并发处理能力)
1)提高线程重用性(避免频繁创建销毁线程)。
2)线程管理能力较强(根据需求扩容缩减)。
3)预创建处理线程,可以更为及时有效地为客户端服务。
3、不要让处理线程与客户端绑定(影响并发),处理线程可以重用为不同的客户端服务。
4、不要让线程池与业务绑定,使用者自行定义实现业务并传递给线程池, 线程负责执行即可。
5、服务端任务传递采用生产者消费者模式(EPOLL生产者, 线程池消费者)。 生产者消费者模型采用条件变量完成线程控制(挂起与唤醒)。
6、管理者角色,负责对线程池进行监控, 完成线程控制(扩容与缩减)。
7、业务分类,可以根据不同网络IO就绪,制定不同的业务(当然也可以根据客户端协议制定业务)。
8、考虑线程安全,全局资源访问用互斥手段。
二、服务端处理流程分析
1、首先userA客户端去connect服务端,连接请求被epoll监听到,生产者是主控线程控制epoll,通过epoll模型监听到就绪并传递任务。serverfd绑定业务A(建立连接),clientfd绑定业务B(处理请求),serverfd就绪产生了业务A,生产者就会将业务A添加到投放到线程池的业务队列中,线程池一般会先预创建一些线程,这些线程都是消费者,一生产者,多消费者,消费者从业务队列里面取出业务,拿到业务以后去执行,比如某一消费者取到的是业务A,那么就会去建立连接(accept),然后将返回的新的clientfd添加到监听集合中。如果取到的是业务B,处理完以后反馈给客户端,如果某一时刻客户端异常退出,将其对应的网络IO从监听树中删除。
2、主控线程生产者,普通线程消费者,还有一个单独的线程是管理者,负责对线程池中的普通线程进行扩容和缩减,监管普通线程,线程池
线程的扩容和缩减条件,线程执行业务,线程被占用,一段时间内不能再执行其他业务,线程状态busy,没有任务线程状态就是idle,创建多少线程,就有多少线程存活,线程数量计数的时候一般不算生产者和管理者。扩容条件一,忙线程占存活线程数量的70%,扩容条件二,当前任务数量>=闲线程数量。缩减条件为闲线程是忙线程的倍数。
三、服务端模块分析
#include<SOCKET_API.h>
/* 服务端宏定义 */
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8000
#define BUFSIZE 1500
#define BACKLOG 128
#define TIMEOUT 2 //等待超时
#define IPSIZE 16
#define EPOLLSIZE 90000
#define TRUE 1
#define FALSE 0
#define THREAD_CODE 10 //扩容缩减量
int epfd; //监听树描述符
pthread_mutex_t busines_lock; //业务锁
/* 结构体定义 */
typedef struct{
void * (*BUSINES_ADDR)(void *); //业务地址 函数指针 哪个业务
void * BUSINES_ARG; //业务参数 socket
}busines_t; //业务类型
//业务传给线程处理
typedef struct{
/*线程池阈值*/
int thread_shutdown; //线程池开关0/1 , 根据开关决定是否结束线程池
int thread_max; //线程最大值
int thread_min; //线程最小值
int thread_busy;//繁忙线程数
int thread_alive; //有效线程数量
int thread_exitcode; //用于线程缩减
pthread_t * customer_tids; //消费者线程tid数组
pthread_t manager_tid; //保存管理者线程tid
busines_t * busines_queue; //保存业务的唤醒队列
int b_front; //业务队列头索引
int b_rear; //业务队列尾索引
int b_max; //业务队列最大数
int b_cur; //业务队列当前业务数
pthread_mutex_lock thread_lock; //互斥锁
pthread_cond_t not_full; //生产者条件变量
pthread_cond_t not_empty; //消费者条件变量
}thread_pool_t; //线程池类型
/* 服务端模型模块分析 */
void thread_pool_error(const char * strerror , int exitcode , int errno);
//如果errno==0则是标准错误处理,如果errno>0则是线程函数错误处理 将错误处理分成两类
int thread_pool_net_init(void); //成功返回serverfd , 失败返回-1 //服务端网络初始化
int thread_pool_epoll_init(int Sockfd); //成功返回监听树描述符,失败返回-1 //服务端epoll初始化 且监听serverfd
thread_pool_t * thread_pool_create(int Max , int Min , int QueMax); //线程池创建初始化,成功返回线程池地址
int thread_pool_destroy(thread_pool_t *); //释放销毁线程池资源
int thread_pool_epoll_listen(int epfd , thread_pool_t * p,int sockfd);//负责网络IO事件监听,辨别就绪并添加任务 producer
int thread_pool_add_busines(thread_pool_t * , busines_t); //生产者向线程池中添加业务
void * thread_pool_customer_job(void * arg); //消费者默认工作,创建时传递线程池参数 从任务队列中取任务
void * thread_pool_manager_job(void * arg); //管理者默认工作,创建时传递线程池参数 需要线程池中的阈值
int if_thread_alive(pthread_t); //测试一个线程是否存活 ? 返回0表示线程已经结束,1表示线程存活
void * BUSINES_ACCEPT(void * arg); //TCP连接业务
void * BUSINES_RESPONSE(void * arg); //业务处理