一、线程池:
因为父线程和子线程公用文件描述符,所以线程池并不像进程池一样,需要socketpair、sendmsg、recvmsg来传递描述符。
当有连接请求时,父进程产生一个new_fd,放到一个队列中。而子线程从队列中往外取new_fd,进行处理。
需要的数据结构:
一个共同的路径信息。保存现在用户所在的当前路径。
父线程流程:
1.socket();
2.bind();
3.listen() 监听客户端的请求
4.队列的初始化。
5.产生num个子线程。每个子线程有自己不同的事。所以用num个条件变量。
5.accept(),收到请求后,产生一个new_fd中。主线程与客户端进行通话,看客户端需要什么操作。根据不同的操作,signal通知不同的子线程进行处理。
子线程:
1.子线程等待被唤醒,去做自己的事。如果为空就wait();
2.
客户端:
1.socket();
2.connect();
3.接收数据
细节一:头文件如何防止重复引用
在一个大的软件工程里面,可能会有多个文件同时包含一个头文件,当这些文件编译链接成一个可执行文件时,就会出现大量重定义的错误。在头文件中实用#ifndef #define #endif能避免头文件的重定义。
方法:例如要编写头文件test.h
#ifndef _TEST_H //在头文件开头写上两行。一般是文件名的大写,这样便于看。
#define _TEST_H
...
#endif //头文件结尾写上一行. 这样一个工程文件里同时包含两个test.h时,就不会出现重定义的错误了。
分析:当第一次包含test.h时,由于没有定义_TEST_H,条件为真,这样就会包含(执行)#ifndef _TEST_H和#endif之间的代码,当第二次包含test.h时前面一次已经定义了_TEST_H,条件为假,#ifndef _TEST_H和#endif之间的代码也就不会再次被包含,这样就避免了重定义了。主要用于防止重复定义宏和重复包含头文件。
线程池:服务器端 /******head.h******/ #ifndef __FUNC_H__ #define __FUNC_H__ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <sys/epoll.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/uio.h> #include <pthread.h> #endif /*****work_que.h*****/ #ifndef __WOEK_QUE_H__ #define __WOEK_QUE_H__ #include "head.h" typedef struct node{ //队列的节点 int new_fd; //主线程接收到新的请求产生一个new_fd,放到队列中 struct node* pnext; }Node,*pNode typedef struct{ pNode phead,ptail; //队列的头尾指针 pthread_mutex_t mutex; //队列包含一个锁,要求互斥地访问队列 int size; //当前队列中元素个数 }que_t,*pque_t; void que_init(pque_t); void que_insert(pque_t,int); void que_get(pque_t,int*); #endif /******factory.h*******/ #ifndef __FACTORY_H__ #define __FACTORY_H__ #include "head.h" #include "work_que.h" #define FILENAME "file" typedef void* (*pfunc)(void*); //定义一个pfunc类型。这是一个函数指针类型。 typedef struct{ //工厂结构体 pthread_t* thread; //num个线程ID的首地址 int th_num; //要创建多少个线程 pthread_cond_t cond; //条件变量,用于线程之间的同步 int capacity; //最多同时有多少个请求 que_t que; //工厂有一个队列 pfunc func_sendfile; //线程函数指针 int flag; //标记,避免工厂启动多次 }factory,*pf; typedef struct{ //小火车数据类型 int len; char buf[1000]; }data,*pdata; void factory_init(pf,int,int,pfunc); //工厂数据结构初始化 void factory_start(pf); //工厂启动 void* child_thread(void*); #endif /*****send_file.c*****/ #include "head.h" void send_n(int new_fd,char* buf,int len){ int ret; int total=0; while(total<len){ ret=send(new_fd,buf+total,len-total,0); total=total+ret; } } void recv_n(int new_fd,char* buf,int len){ int ret; int total=0; while(total<len){ ret=recv(new_fd,buf+total,len-total,0); total=total+ret; } } void send_file(int fdw){ data d; memset(&d,0,sizeof(d)); d.len=strlen(FILENAME); strcpy(d.buf,FILENAME); int ret; ret=send(fdw,&d,4+d.len,0); //发送文件名 if(-1==ret){ perror("send"); exit(-1); } int fd=open(FILENAME,O_RDONLY); if(-1==fd){ perror("open"); exit(-1); } while(memset(&d,0,sizeof(d)),(d.len=read(fd,d.buf,sizeof(d.buf)))>0){ send_n(fdw,&d,4+d.len); } ret=0; send_n(fdw,&ret,sizeof(int)); //发送文件结束标志 close(fdw); } work_que.c #include "work_que.h" void que_init(pque_t pq){ //初始化队列 pq->phead=NULL; pq->ptail=NULL; pthread_mutex_init(&pq->mutex,NULL); //初始化队列的锁 pq->size=0; } void que_insert(pque_t pq,int fd){ //把新产生的fd放到队列中 pNode pnew=(pNode)calloc(1,sizeof(Node)); //新建一个节点,存放fd pnew->new_fd=fd; pthread_mutex_lock(&pq->mutex); //互斥地访问队列 if(pq->phead==NULL){ //采用尾插法,插入新的节点 pq->phead=pnew; pq->ptail=pnew; }else{ pq->ptail->pnext=pnew; pq->ptail=pnew; } (pq->size)++; pthread_mutex_unlock(&pq->mutex); } void que_get(pque_t pq,int* fd){ //取出一个new_fd pNode pcur; pcur=pq->phead; *fd=pcur->new_fd; //把队首的元素取出来 pq->phead=pcur->pnext; free(pcur); //尽量把malloc和free放到锁外面 if(pq->phead == NULL){ pq->ptail=NULL; } (pq->size)--; pcur=NULL; //防止产生野指针 } /*****factory.c*****/ #include "factory.h" void factory_init(pf p,int num,int cap,pfunc func_sendfile){ //初始化工厂:num为要创建的线程数,cap是同时请求的最大数目,func_sendfile 是一个函数指针类型的变量。所以func_sendfile等于child_thread,指向子线程 p->thread=(pthread_t*)calloc(num,sizeof(pthread_t)); //num个线程ID的首地址.pthread_t pth_id[num].因为num变化,所以calloc。 p->th_num=num; //要创建的线程数 pthread_cond_init(&p->cond,NULL); //条件变量初始化 p->capacity=cap; //最多同时有多少个请求 que_init(&p->que); //初始化队列。因为要改变队列的值,所以传地址 p->func_sendfile=func_sendfile; //p->func_sendfile也是函数指针类型。所以p->func_sendfile等于child_thread p->flag=0; } void factory_start(pf p){ //工厂启动。p是factory * 类型 int i; if(p->flag ==0){ //如果工厂未被启动 for(i=0;i<p->th_num;i++){ pthread_create(&p->thread[i],NULL,p->func_sendfile,(void*)p); //创建p->th_num个子线程。并把p作为参数传递给子线程 } } p->flag=1; //标记工厂已经被启动 } /*****main.c*******/ #include "factory.h" void* child_thread(void* p){ pf p1=(pf)p; int new_fd; while(1){ pthread_mutex_lock(&p1->que.mutex); //互斥地访问队列 if(p1->que.size==0){ //如果进程中没有new_fd,就等待被唤醒 pthread_cond_wait(&p1->cond,&p1->que.mutex); } que_get(&p1->que,&new_fd); //取出一个new_fd,返回给new_fd pthread_mutex_unlock(&p1->que.mutex); send_file(new_fd); //发送文件。同进程池的send_file函数 } } int main(int argc,char* argv[]){ //参数:IP地址 端口 创建的线程数 capacity if(argc!=5){ printf("error args\n"); return -1; } int num=atoi(argv[3]); //要创建的线程数 int cap=atoi(argv[4]); //容量,最多多少个线程连接 factory f; factory_init(&f,num,cap,child_thread); //初始化工厂。child_thread是函数的地址(函数指针类型)。 factory_start(&f); //工厂启动:创建num个子线程。 int sfd=socket(AF_INET,SOCK_STREAM,0); if(-1==sfd){ perror("socket"); return -1; } struct sockaddr_in ser; memset(&ser,0,sizeof(ser)); ser.sin_family=AF_INET; ser.sin_port=htons(atoi(argv[2]));//一定要用htons ser.sin_addr.s_addr=inet_addr(argv[1]); int ret; ret=bind(sfd,(struct sockaddr*)&ser,sizeof(struct sockaddr)); if(-1==ret){ perror("bind"); return -1; } listen(sfd,cap); //最大可接受的请求的数量,即队列中最多有多少个new_fd int new_fd; while(1){ new_fd=accept(sfd,NULL,NULL); if(-1==new_fd){ perror("accept"); return -1; } que_insert(&f.que,new_fd); //把新产生的new_fd放到队列中 pthread_cond_signal(&f.cond); //唤醒一个子进程 } } |
//客户端:与进程池客户端完全相同 #include "func.h" void send_n(int new_fd,char* buf,int len){ int ret; int total=0; while(total<len){ ret=send(new_fd,buf+total,len-total,0); total=total+ret; } } void recv_n(int new_fd,char* buf,int len){ int ret; int total=0; while(total<len){ ret=recv(new_fd,buf+total,len-total,0); total=total+ret; } } int main(int argc,char** argv){ //传入参数:IP地址、端口 if(argc !=3){ printf("error args\n"); return -1; } int sfd=socket(AF_INET,SOCK_STREAM,0); if(-1==sfd){ perror("socket"); return -1; } struct sockaddr_in ser; memset(&ser,0,sizeof(ser)); ser.sin_family=AF_INET; ser.sin_port=htons(atoi(argv[2])); ser.sin_addr.s_addr=inet_addr(argv[1]); int ret; ret=connect(sfd,(struct sockaddr*)&ser,sizeof(struct sockaddr)); if(-1==ret){ perror("connect"); return -1; } data d; //定义小火车结构体 memset(&d,0,sizeof(d)); recv_n(sfd,&d.len,sizeof(int)); //读取要接收文件名字的长度 recv_n(sfd,d.buf,d.len); //读取文件名 int fd; fd=open(d.buf,O_RDWR|O_CREAT,0666); //在本地创建文件 if(-1==fd){ perror("open"); return -1; } while(1){ memset(&d,0,sizeof(d)); recv_n(sfd,&d.len,sizeof(int)); //接收小火车头 if(d.len >0){ //如果后面有数据 recv_n(sfd,d.buf,d.len); //读出buf的长度。一定要保证读出len的长度,而不能读少了。 write(fd,d.buf,d.len); //读完之后写到文件中。 }else{ //如果只有火车头,后面没有数据,说明文件已经下载完了 break; } } close(sfd); close(fd); return 0; } |