之前用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);//唤醒子线程
}
}
四、客户端代码实现
客户端代码可以与进程池客户端代码通用,故不再赘述。