Linux下线程池的实现

之前用epoll和进程池实现的简单的文件传输功能有一个缺点,就是没有排队功能。当进程池中所有进程都在工作,后到来的请求可能因为timing合适,正好有进程空闲,反而比先到的请求更早得到相应。为了实现“先请求客户端,优先服务”的功能,本文用进程池和任务队列实现了排队功能,让客户端遵从先到先得的顺序进行任务传输。

一、概念分析
线程池的实现类似于生产者消费者模型,首先需要定义一个线程池的结构体来控制线程的运行和终止;其次,要满足不同线程执行不同的任务,而且在程序执行过程中还可以动态加入任务的功能,还需要定义一个任务链表。 当有客户端发来任务请求时,将new_fd加入队列尾;当任务队列不为空且有空闲的线程时,线程从队列头取走一个任务。
我们使用链表实现队列。队列当有新请求发来时,尾插法;当有线程空闲时执行队列头的任务,头部删除法。

主线程思路:

socket bind listen
while(1)
{
   new_fd=accept()
   que_set()
   pthread_cond_signal(&cond);
}

线程思路:
while(1)
{
	
   拿任务   
   做任务
}

二、数据结构

队列结点

typedef struct tag_node{
	int new_fd;				//客户端套接字
	struct tag_node *pnext;
}node_t,*pnode_t;

队列

typedef struct{
	pnode_t que_head,que_tail;//头尾指针
	int que_capacity;		//容量,表示可连接的客户端上限
	int que_size;			//当前的结点数,表明有多少个客户端请求待应答
	pthread_mutex_t mutex;
}que_t,*pque_t;

线程池

typedef  void* (*pfunc)(void*);//定义了一种pfunc指向某种函数的指针类型。这种函数以一个void*为参数并返回void*类型。后面就可以像使用int,char一样使用pfunc了。
typedef struct{
    que_t que;				//队列类型,也是结构体
    pthread_cond_t cond;	//条件变量,为了让子线程睡觉
    pthread_t* pthid;		//子线程的指针,用指针是因为不知道要创建多少子进程
    int flag;				//线程启动标志
    pfunc thread_func;		//子线程入口函数
    int thread_num;			//线程数
}factory,*pfac;

数据传输用的“小火车”结构

typedef struct{
	int len;				//发送的文件长度	
	char buf[1000];			//文件内容
}train;

三、服务端代码实现

1.头文件

head.h,用于存放头文件

#ifndef __HEAD_H__
#define __HEAD_H__
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define FILENAME "filename.pdf"
typedef struct{
	int len;
	char buf[1000];
}train;
#endif

 
work_que.h,用于存放与队列相关的数据结构和函数声明

#ifndef _WORK_QUE_H
#define _WORK_QUE_H
#include "head.h"

//队列的单个结点,结点上的new_fd是客户端套接字
typedef struct tag_node{
    int new_fd;
    struct tag_node* pnext;
}node_t,*pnode_t;

//队列,用于存储待应答的客户端套接字
typedef struct{
    pnode_t que_head,que_tail;
    int que_capacity;//容量,表示可连接的客户端上限
    int que_size;//当前的结点数,表明有多少个客户端请求待应答
    pthread_mutex_t mutex;
}que_t,*pque_t;

void que_set(pque_t,pnode_t);
void que_get(pque_t,pnode_t*);
#endif

 
factory.h,用于存放线程池结构和相关函数声明

#ifndef __FACTORY_H__
#define __FACTORY_H__
#include "head.h"
#include "work_que.h"

typedef  void* (*pfunc)(void*);


typedef struct{
	que_t que;				//队列类型,也是结构体
	pthread_cond_t cond;	//条件变量,为了让子线程睡觉
	pthread_t* pthid;		//子线程的指针,用指针是因为不知道要创建多少子进程
	int flag;				//线程启动标志
	pfunc thread_func;		//子线程入口函数
	int thread_num;			//线程数
}factory,*pfac;

void factory_init(pfac p,int thread_num,int capacity,pfunc tran_file);
void factory_start(pfac);
int sendn(int,char*,int);
void trans_file(int);
#endif

2、源文件
factory.c

#include "factory.h"

void factory_init(pfac p,int thread_num,int capacity,pfunc th_func)
{
	p->que.que_head=NULL;
	p->que.que_tail=NULL;
	p->que.que_size=0;
	p->que.que_capacity=capacity;
	pthread_mutex_init(&p->que.mutex,NULL);
	pthread_cond_init(&p->cond,NULL);
	p->pthid=(pthread_t*)calloc(thread_num,sizeof(pthread_t));
	p->flag=0;//0代表未启动,1代表启动
	p->thread_func=th_func;
	p->thread_num=thread_num;
}

void factory_start(pfac p)
{
	if(0==p->flag)
	{
		int i;
		for(i=0;i<p->thread_num;i++)
		{
			pthread_create(p->pthid+i,NULL,p->thread_func,p);
		}
		p->flag=1;
	}else{
		printf("factory has start\n");
	}
}

 
work_que.c

#include "work_que.h"

void que_set(pque_t pq,pnode_t pnew)
{
	pthread_mutex_lock(&pq->mutex);
	if(NULL==pq->que_head)
	{
		pq->que_head=pnew;
		pq->que_tail=pnew;
	}else{
		//尾插法
		pq->que_tail->pnext=pnew;
		pq->que_tail=pnew;
	}
	pq->que_size++;
	pthread_mutex_unlock(&pq->mutex);
}

void que_get(pque_t pq,pnode_t *p)
{
	//从头开始删除节点,配合尾插法加入节点,形成了一个队列
	*p=pq->que_head;
	pq->que_head=pq->que_head->pnext;
	if(NULL==pq->que_head)
	{
		pq->que_tail=NULL;
	}
	pq->que_size--;
}

 
send_file.c

#include "factory.h"

void sig(int signum)
{
	printf("%d is coming\n",signum);
}

void trans_file(int new_fd)
{
	signal(SIGPIPE,sig);
	train t;
	strcpy(t.buf,FILENAME);//把文件名放入buf
	t.len=strlen(t.buf);
	sendn(new_fd,(char*)&t,4+t.len);//发送文件名火车给对端
	int fd;
	fd=open(FILENAME,O_RDONLY);
	if(-1==fd)
	{
		perror("open");
		return;
	}
	int ret;
	while(bzero(&t,sizeof(t)),(t.len=read(fd,t.buf,sizeof(t.buf)))>0)
	{//逗号表达式的结果就是最后一个表达式的结果
		ret=sendn(new_fd,(char*)&t,4+t.len);
		if(-1==ret)
		{
			goto end;
		}
	}
	//文件读完且发送完后,发送一个len成员为0且buf为空的数据,表示文件发送完毕
	t.len=0;
	sendn(new_fd,(char*)&t,4);
end:
	close(new_fd);
	close(fd);
}

 
tran_n.c

#include "head.h"

int sendn(int sfd,char* buf,int len)
{
	int total=0;
	int ret;
	while(total<len)
	{
			ret=send(sfd,buf+total,len-total,0);
			if(-1==ret)
			{
				printf("errno=%d\n",errno);
				return -1;
			}
			total=total+ret;
	}
	return 0;
}


void recvn(int sfd,char* buf,int len)
{
	int total=0;
	int ret;
	while(total<len)
	{
		ret=recv(sfd,buf+total,len-total,0);
		total=total+ret;
	}
}

 
main.c

#include "factory.h"

void* th_func(void* p)
{
	pfac pf=(pfac)p;
	pque_t pque=&pf->que;
	pnode_t pcur;
	while(1)
	{
		pthread_mutex_lock(&pque->mutex);
		if(0==pque->que_size)	//队列为空
		{
			//线程挂起,等待有客户端发来请求且加入了任务队列后发出的信号
			pthread_cond_wait(&pf->cond,&pque->mutex);
		}
		que_get(pque,&pcur);//线程从任务队列领取一个发送任务
		pthread_mutex_unlock(&pque->mutex);
		trans_file(pcur->new_fd);//线程开始执行传输任务
		free(pcur);
	}
}

int main(int argc,char* argv[])
{
	if(argc!=5)
	{
		printf("./thread_pool_server IP  PORT  thread_num capacity\n");
		return -1;
	}
	int thread_num=atoi(argv[3]);
	int capacity=atoi(argv[4]);

	//初始化并启动线程池
	factory f;
	factory_init(&f,thread_num,capacity,th_func);
	factory_start(&f);

	//socket bind listen
	int sfd;
	sfd=socket(AF_INET,SOCK_STREAM,0);
	if(-1==sfd)
	{
		perror("socket");
		return -1;
	}
	printf("sfd=%d\n",sfd);

	int resue=1;
	int ret;
	ret=setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&resue,sizeof(int));
	if(-1==ret)
	{
		perror("setsockopt");
		return -1;
	}

	struct sockaddr_in ser;
	bzero(&ser,sizeof(ser));
	ser.sin_family=AF_INET;
	ser.sin_port=htons(atoi(argv[2]));//一定要用htons
	ser.sin_addr.s_addr=inet_addr(argv[1]);
	ret=bind(sfd,(struct sockaddr*)&ser,sizeof(struct sockaddr));
	if(-1==ret)
	{
		perror("bind");
		return -1;
	}

	listen(sfd,capacity);


	int new_fd;
	pque_t pque=&f.que;
	pnode_t pnew;
	while(1)
	{
		new_fd=accept(sfd,NULL,NULL);
		pnew=(pnode_t)calloc(1,sizeof(node_t));//申请内存耗时久,要放在锁外
		pnew->new_fd=new_fd;
		que_set(pque,pnew);//把客户端的下载请求加入任务队列
		printf("set in queue success\n");
		pthread_cond_signal(&f.cond);//唤醒子线程
	}
}

四、客户端代码实现
客户端代码可以与进程池客户端代码通用,故不再赘述。

你可能感兴趣的:(Linux)