一、实验思想
线程池采用的还是上述内容所介绍的线程池知识,就是在简单线程池以及传统socket通信的基础上加入epoll机制。I/O复用机制有很多成熟的技术,比如说select、poll、epoll,epoll是最近才开发出来的用于实现I/O复用的一种机制,是目前最新的技术。
那么epoll和传统socket通信机制到底有什么不同。
在传统的I/O模型中,一个线程或进程通常需要阻塞等待某个操作完成,这可能导致资源的浪费,尤其在网络编程中,等待网络数据到达或发送可能会导致线程阻塞,浪费CPU时间。I/O复用通过允许一个线程同时监视多个文件描述符,当其中任何一个文件描述符准备好进行操作时,就通知应用程序进行处理,从而提高了程序的并发性和性能。
在Linux系统中,有三种I/O复用的机制:select、poll、和epoll。其中,epoll 是最近引入的,并且通常被认为是最为强大和高效的一种。相比于 select 和 poll,epoll 在大量连接时有更好的性能表现,因为它使用事件通知的方式,而不是轮询。
epoll通过以下系统调用来实现I/O复用:
epoll_create: 创建一个epoll实例,返回一个文件描述符。
epoll_ctl: 向epoll实例中添加、修改或删除文件描述符。
epoll_wait: 等待文件描述符上的事件发生,当有事件发生时,返回就绪的文件描述符列表。
通过使用epoll,程序可以更加高效地处理大量的并发连接,特别适用于服务器应用程序需要同时管理大量客户端连接的情况。
二、实验
和传统socket通信机制一样的地方就不在详细赘述。建立服务器socket套接字,绑定服务器socket套接字和协议族、ip地址、端口号,监听客户端连接请求都是一样的。
接下来需要创建epoll实例,并且将刚才定义的服务器添加到创建的epoll实例中。
接下来需要用epoll专用等待函数等待客户端准备就绪。
只要有客户端准备就绪,就响应对应的客户端,而不是像传统socket通信机制,只有当前请求的客户端得到响应之后才能响应其它准备就绪的客户端。epoll_wait()函数会将准备就绪的客户端加入events就绪事件数组中。
接下来,扫面events数组中的所有就绪请求事件,只要是访问服务器自身的,也就是说不是访问访问其它服务器,都响应其请求。
这样,就实现了epoll I/O复用。
三、运行结果
Server:
商品还有余量:
商品已被售空:
响应并完成客户端请求之后,线程池中的线程退出:
Client:
成功购买会恢复用户成功购买的信息:
商品余量不足会提示用户商品已被售空:
Epoll I/O复用的性能:
四、实验代码(Linux操作系统,C语言)
server:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <stdbool.h>
#define PORT 8080
#define MAX_CLIENTS 10000
#define MAX_EVENTS 100
#define MAX_BUFFER_SIZE 1024
struct ThreadPool* pool=NULL;
int product_quantity=500;
//自定义协议格式
struct Node{
int length;//数据长度
char data[MAX_BUFFER_SIZE];//数据
};
struct Task{
void (*function)(int arg);
int client_socket;
struct Task* next;
};
struct ThreadPool{
//任务队列
struct Task *queueHead;
struct Task *queueTail;
//线程的数量
int max_thread_num;
int counter;
int idle;
//互斥锁 条件变量
pthread_mutex_t mutex;
pthread_mutex_t task_mutex;
pthread_cond_t cond;
//销毁线程池标志
int quit;
};
//线程池处理函数
void* thread_routine(void* arg){
struct ThreadPool* pool=(struct ThreadPool*)arg;
//struct timespec *asctime_ptr = (struct timespec *)asctime;
//int status = pthread_cond_timedwait(&pool->cond, &pool->mutex, asctime_ptr);
struct timespec abstime;//用于存储时间
int timeout;//用于标记一个线程是否超时等待
while(true){
timeout = 0;//将超时标志置为0,表示线程还未超时
//准备访问任务队列,获取锁
pthread_mutex_lock(&pool->mutex);
//printf("线程 %ld 获得锁\n",pthread_self());
pool -> idle++;//增加线程池的空闲线程数
//如果任务队列为空并且线程池没有被销毁 线程睡眠
while(pool->queueHead==pool->queueTail&&pool->quit==0){
printf("线程 %ld 即将睡眠\n",pthread_self());
clock_gettime(CLOCK_REALTIME,&abstime);//获取当前时间
abstime.tv_sec += 2;//将超时时间设置为当前时间的后2秒
//获得条件变量 原子释放互斥锁
int status=pthread_cond_timedwait(&pool->cond,&pool->mutex,&abstime);
printf("线程 %ld 被唤醒,重新获得互斥锁\n",pthread_self());
if(status == ETIMEDOUT)//如果线程睡眠超时
{
//打印线程睡眠超时信息
printf("thread 0x%0x is wait timed out\n",(int)pthread_self());
timeout = 1;//将线程睡眠超时标志置为1
break;//结束线程睡眠任务循环
}
}
pool->idle--;
if(pool->queueHead->next != NULL)
{
//从任务队列中获取一个任务,并且执行
struct Task task;
struct Task* t=pool->queueHead->next;
task.function=t->function;
task.client_socket=t->client_socket;
pool->queueHead->next=t->next;
free(t);
if(pool->queueHead->next==NULL){
pool->queueTail=pool->queueHead;
}
//释放互斥锁
pthread_mutex_unlock(&pool->mutex);
//执行任务
printf("thread %ld start work ...\n",pthread_self());
task.function(task.client_socket);
printf("thread %ld end work ...\n",pthread_self());
pthread_mutex_lock(&pool->mutex);//重新获取锁
}
//如果线程池被销毁
if(pool->quit==1 && pool->queueHead->next == NULL)
{
pool -> counter--;//减少执行线程数
if(pool->counter == 0)//如果执行线程数降为0
{
pthread_cond_signal(&pool->cond);//唤醒线程
}
pthread_mutex_unlock(&pool->mutex);//释放锁
break;
}
if(timeout==1&&pool->queueHead->next==NULL)//如果线程超时且任务队列为空
{
pool -> counter--;//减少执行线程数
pthread_mutex_unlock(&pool->mutex);//释放锁
break;
}
pthread_mutex_unlock(&pool->mutex);
}
printf("线程%ld退出...\n",pthread_self());
return NULL;
}
//创建线程池 在线程池中创建线程
struct ThreadPool* create_thread_pool(int max_thread_num){
//申请线程池结构体
struct ThreadPool* pool=(struct ThreadPool*)malloc(sizeof(struct ThreadPool));
if(NULL==pool){
fprintf(stderr,"malloc ThreadPool failure\n");
return NULL;
}
//初始化任务队列
pool->queueHead=(struct Task*)malloc(sizeof(struct Task));
if(NULL==pool->queueHead){
fprintf(stderr,"malloc Tesk failure\n");
free(pool);
return NULL;
}
pool->queueTail=pool->queueHead;
pool->queueHead->next=NULL;
//初始化线程的数量
pool->max_thread_num=max_thread_num;
pool->counter = 0;//初始化线程池中当前运行的线程数量为0
pool->idle = 0;//初始化空闲线程数量
//初始化互斥锁 信号量
//互斥锁/信号量 互斥锁/信号量属性
pthread_mutex_init(&pool->mutex,NULL);
pthread_mutex_init(&pool->task_mutex,NULL);
pthread_cond_init(&pool->cond,NULL);
//初始化销毁线程池标志
pool->quit=0;
}
//任务执行函数
void task_func(int client_socket){
int product=0;
pthread_mutex_lock(&pool->task_mutex);
if(product_quantity>0){
printf("thread %ld is working product_quantity=%d ...\n",pthread_self(),product_quantity);
product_quantity--;
product=1;
}
else{
printf("thread %ld is working product_quantity=0 ...\n",pthread_self());
}
pthread_mutex_unlock(&pool->task_mutex);
if(product==1){
struct Node send_data;
char* data_string="server> hello! client. successfully buying.";
send_data.length=strlen(data_string);
strncpy(send_data.data,data_string,MAX_BUFFER_SIZE);
send(client_socket,&send_data,sizeof(send_data),0);
}
else{
struct Node send_data;
char* data_string="server> sorry! client. the product have been sold out.";
send_data.length=strlen(data_string);
strncpy(send_data.data,data_string,MAX_BUFFER_SIZE);
send(client_socket,&send_data,sizeof(send_data),0);
}
close(client_socket);
//sleep(0.01);
}
//往任务队列中添加任务
void thread_pool_add_task(struct ThreadPool*pool,void (*func)(int),int client_socket){
//进队操作
struct Task*t=(struct Task*)malloc(sizeof(struct Task));
if(NULL==t){//如果获取任务结构体内存失败,返回
fprintf(stderr,"malloc Task failure\n");
return;
}
t->function=func;
t->client_socket=client_socket;
t->next=NULL;
pthread_mutex_lock(&pool->mutex);
pool->queueTail->next=t;
pool->queueTail=t;
//如果有睡眠线程,则唤醒睡眠线程
if(pool->idle > 0)
{
pthread_cond_signal(&pool->cond);//唤醒线程
}
//如果没有睡眠线程,且线程池中正在运行线程数小于最大线程数,则创建一个新线程
else if(pool -> counter < pool -> max_thread_num)//可以多创建几个线程
{
pthread_t tid;
//pthread_create(线程号,线程属性,线程处理函数,线程池)
pthread_create(&tid,NULL,thread_routine,pool);
pthread_detach(tid);
pool->counter++;//增加线程池中正在运行线程数
}
pthread_mutex_unlock(&pool->mutex);
}
//销毁线程池
void thread_pool_destroy(struct ThreadPool*pool){
if(pool->quit==1){
return;
}
pthread_mutex_lock(&pool->mutex);
//更行销毁线程池标志
pool->quit=1;
if(pool->counter > 0)//如果仍有线程在运行
{
if(pool -> idle > 0){//如果有睡眠线程
//广播通知所有线程,以确保它们能够检查线程池销毁标志
pthread_cond_broadcast(&pool -> cond);
}
//等待所有正在运行的线程完成工作
while(pool -> counter > 0)
{
pthread_cond_wait(&pool -> cond,&pool -> mutex);//睡眠销毁线程(主线程)
}
}
//释放任务队列
while(pool->queueHead->next!=NULL){
struct Task*t=pool->queueHead->next;
pool->queueHead->next=t->next;
free(t);
}
free(pool->queueHead);//释放任务队列头指针
pthread_mutex_unlock(&pool->mutex);
//销毁互斥锁 条件变量
pthread_mutex_destroy(&pool->mutex);
pthread_mutex_destroy(&pool->task_mutex);
pthread_cond_destroy(&pool->cond);
//释放线程池结构体
free(pool);
}
void solve(){
int server_socket, client_socket;
struct sockaddr_in server_address, client_address;
socklen_t client_address_len = sizeof(client_address);
//epoll
int epoll_fd,event_count;
struct epoll_event events[MAX_EVENTS];
//创建套接字
server_socket=socket(AF_INET,SOCK_STREAM,0);
if (server_socket == -1) {
perror("Error creating socket.");
exit(EXIT_FAILURE);
}
//设置服务器地址
memset(&server_address, 0, sizeof(server_address));
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = INADDR_ANY;
server_address.sin_port = htons(PORT);
//绑定套接字
if (bind(server_socket, (struct sockaddr *)&server_address, sizeof(server_address)) == -1) {
perror("Error binding socket");
exit(EXIT_FAILURE);
}
//监听连接
if (listen(server_socket, 10) == -1) {//服务器套接字 允许等待的最大数量
perror("Error listening");
exit(EXIT_FAILURE);
}
printf("Server listening on port %d...\n", PORT);
//创建epoll实例
epoll_fd=epoll_create(MAX_CLIENTS);
//添加服务器到epoll实例
struct epoll_event event;
event.events=EPOLLIN;
event.data.fd=server_socket;
epoll_ctl(epoll_fd,EPOLL_CTL_ADD,server_socket,&event);//epoll实例文件描述符 “ ” 服务器套接字 epoll事件
pool=create_thread_pool(10);
if(NULL==pool){//创建失败则直接返回
return;
}
printf("线程池创建完成\n");
sleep(1);
int num=0;
while(true){
//使用epoll等待事件
event_count=epoll_wait(epoll_fd,events,MAX_EVENTS,-1);//epoll实例文件描述符 事件数组 事件数组的长度 没有连接就一直等待
for(int i=0;i<event_count;i++){
if(events[i].data.fd==server_socket){//就绪客户端请求服务器地址等于当前服务器地址,响应请求
//有新连接
client_socket=accept(server_socket,(struct sockaddr*)&client_address,&client_address_len);//服务器套接字 客户端地址 客户端地址长度
//连接成功
printf("Accepted connection from %s\n", inet_ntoa(client_address.sin_addr));
printf("num=%d\n",++num);
//将新连接的客户端socket添加到epoll实例
event.events=EPOLLIN|EPOLLET;
event.data.fd=client_socket;
epoll_ctl(epoll_fd,EPOLL_CTL_ADD,client_socket,&event);
thread_pool_add_task(pool,task_func,client_socket);
}
else if(events[i].events & EPOLLIN){
printf("server不需要接收client的数据\n");
}
}
}
sleep(2);//主线程等待所有线程完成任务
thread_pool_destroy(pool);//销毁线程池
}
int main(){
solve();
return 0;
}
client:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/time.h>
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8080
#define NUM_CLIENTS 10000
#define MAX_BUFFER_SIZE 1024
//自定义协议格式
struct Node{
int length;//数据长度
char data[MAX_BUFFER_SIZE];//数据
};
void solve(){
int client_sockets[NUM_CLIENTS];
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr);//ipv4 服务器地址 服务器地址家族和端口号
for (int i = 0; i < NUM_CLIENTS; ++i) {
client_sockets[i] = socket(AF_INET, SOCK_STREAM, 0);//ip4 流式套接字 自动适配协议族
if (client_sockets[i] < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
int con=connect(client_sockets[i], (struct sockaddr *)&server_addr, sizeof(server_addr));
if (con < 0) {
perror("Connection failed");
exit(EXIT_FAILURE);
}
struct Node receive_data;
recv(client_sockets[i],&receive_data,sizeof(receive_data),0);
printf("client%d> receive data from server:%s\n",i,receive_data.data);
close(client_sockets[i]);
}
}
int main() {
struct timeval start,end;
//开始时间
gettimeofday(&start,NULL);
solve();
gettimeofday(&end,NULL);
printf("start time is %lds %ldus\n",start.tv_sec,start.tv_usec);
printf("end time is %lds %ldus\n",end.tv_sec,end.tv_usec);
long seconds=end.tv_sec-start.tv_sec;
long microseconds=end.tv_usec-start.tv_usec;
double absolute_time=seconds+microseconds*1e-6;
printf("absolute time is %.6lf seconds\n",absolute_time);
return 0;
}