C语言线程池文件的并行下载文件

在这里插入图片描述

线程池

线程池的特点就是尽量减少创建和销毁线程的开销,缺点是不利于保护共享资源。现在公司用的多都是封装好的线程池,比如qt框架的QThread::QThread(QObject *parent = Q_NULLPTR)。
这一方面带来好的好处就是少了很多造轮子的过程,qt是我见过帮助文档最好的框架了,但是手撕线程池的也能让自己对其内部实现更加清晰。
在Linux下线程和进程其实是同一种东西。
**struct task;的结构体,fork()和pthread_create()虽然是不同的函数,但是在内部实现都调用了一个do_fork的函数加入到链表中。**当调用fork()的时候,子进程是几乎完整得拷贝了用户态的所有数据,堆栈之类的。以及大部分内核态的数据,除了未决信号集屏蔽,和文件fd。所需要的开销是很大的。这个task中可能存储了进程的调度,以及父进程的id信息。
pthread_create()调用了do_fork以及一些特定的参数,就会创建一个轻量级的线程,一起共享线程之间的数据。
在这里插入图片描述

其中进程开销最大的就是1个CPU下,上下文切换,每秒少则数十次,多则数千次的上下文。而进程除了上下文切换浪费时间,进程之间交互数据也需要特殊的手段,包括共享内存,QSharedMemory(const QString &key, QObject *parent = Q_NULLPTR);管道,本地套接字等。

为了得到多条代码执行流而复制整个内存,负担过重!

1.读取配置文件配置信息

//head.h头文件声明
#ifndef __HEAD_H__
#define __HEAD_H__

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <dirent.h>
#include <time.h>
#include <pwd.h>
#include <grp.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/mman.h>
#include <sys/select.h>
#include <sys/time.h>
#include <pthread.h>
#include <setjmp.h>
#include <sys/time.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/msg.h>
#include <sys/uio.h>
#include <arpa/inet.h>
#include <sys/shm.h>
#include <sys/epoll.h>
#define ARGS_CHECK(argc, val) \
    if(argc != val) { \
        printf("error args\n"); \
        return -1; \
    } 


#define ERROR_CHECK(ret, retVal, funcName) { \
    if(ret == retVal) { \
        perror(funcName); \
        return -1; \
    } \
}

#define THREAD_ERROR_CHECK(ret, funcName) \
    do { \
        if (0 != ret) { \
            printf("%s : %s\n", funcName, strerror(ret)); \
        } \
    }while(0)

typedef struct conf1
{
	
	char ip[17];//暂时想不到好办法
	struct conf1* next;
}Conf_t,*pConf_t;
typedef struct confNode
{
	int size;
	pConf_t phead;//保存文件配置项头部节点
	pConf_t ptail;//保存文件配置项尾部节点
}confNode_t,*pconfNode_t;
//截取字符串前的空格和后面的空格,只保留合法字符串配置数据
void Ltrim(char *string);
void Rtrim(char *string);
pConf_t load(const char *pconfName, pConf_t* conf);
#endif

首先读取配置文件,位于同级目录下的tcp.conf

 #ip为本机IP地址,port为指定端口,clientnum为创建子进程的个数                                                                                                     
  2 ip = 192.168.98.130
  3 port = 5555
  4 clientnum = 5

读取tcp.conf配置文件配置项,由于坚持不用c++代码,所以调用部分写的比较Low一点,原来光是600行的代码,就删减到200行不到了。
readConf.c

#include "../include/head.h"
void Rtrim(char *string)
{
	size_t len = 0;
	if (string == NULL)
		return;

	len = strlen(string);
	while (len > 0 && string[len - 1] == ' ')   //位置换一下   
		string[--len] = 0;
	
	return;
}
void Ltrim(char *string)
{
	size_t len = 0;
	len = strlen(string);
	char *p_tmp = string;
	if ((*p_tmp) != ' ') //不是以空格开头
		return;
	//找第一个不为空格的
	while ((*p_tmp) != '\0')
	{
		if ((*p_tmp) == ' ')
			p_tmp++;
		else
			break;
	}
	if ((*p_tmp) == '\0') //全是空格
	{
		*string = '\0';
		return;
	}
	char *p_tmp2 = string;
	while ((*p_tmp) != '\0')
	{
		(*p_tmp2) = (*p_tmp);
		p_tmp++;
		p_tmp2++;
	}
	(*p_tmp2) = '\0';
	return;
}
pConf_t  load(const char *pconfName, pConf_t* conf)
{
	//pConf_t *pcur;
	int sum = 0;
	pconfNode_t pcur = (pconfNode_t)calloc(1, sizeof(confNode_t));
	FILE *fp;
	fp = fopen(pconfName, "r");
	if (fp == NULL)
		return NULL;
    else
    {
        printf("成功打开配置文件\n");
    }
	//每一行配置文件读出来都放这里
    
	char  linebuf[501];
	while (!feof(fp))
	{
	//为空或者第一个字符为空格都要忽略
		if (fgets(linebuf, 500, fp) == NULL)
			continue;
		if (linebuf[0] == 0)
			continue;
		//处理注释行
		if (*linebuf == ';' || *linebuf == ' ' || *linebuf == '#' || *linebuf == '\t' || *linebuf == '\n')
			continue;
	lblprocstring:
		//屁股后边若有换行,回车,空格等都截取掉,goto循环截取
		if (strlen(linebuf) > 0)
		{
			if (linebuf[strlen(linebuf) - 1] == 10 || linebuf[strlen(linebuf) - 1] == 13 || linebuf[strlen(linebuf) - 1] == 32)
			{
				linebuf[strlen(linebuf) - 1] = 0;
				goto lblprocstring;
			}
		}
		if (linebuf[0] == 0)
			continue;
		if (*linebuf == '[') //[开头的也不处理
			continue;
		//==0肯定就是没读到数据
		
		
		char *ptmp = strchr(linebuf, '=');
		
		//走到=停下来,注意编写配置文件的格式
		//需要截取右边的
		if (ptmp != NULL)
		{
			
			strcpy((*conf)->ip,ptmp + 1);
			//拷贝出来
			Rtrim((*conf)->ip);
			Ltrim((*conf)->ip);
		}
		pConf_t cf = (pConf_t)calloc(1, sizeof(Conf_t));
		strcpy(cf->ip, (*conf)->ip);
		//把读取到的数据,存储到动态分配的cf节点中
		//这里不采用head为空的方式,不习惯。
		//如果是第一次插入节点
		if (pcur->size == 0)
		{
			pcur->phead = cf;
			pcur->ptail = cf;
			pcur->size++;
			
		}
		else
		{
			pcur->ptail->next = cf;
			pcur->ptail = cf;
			pcur->size++;
			
		}

	}
	pConf_t p;
	p = pcur->phead;
	while (pcur->phead)
	{
		printf("%s\n", pcur->phead->ip);
		//打印测试
		pcur->phead = pcur->phead->next;
	}
	
	return p;//把指针返回回去。本来想做个传入传出的,太麻烦了,算了

}

在这里插入图片描述
测试结果如上str为我需要创建的子线程个数,也从配置文件中读取。

2.初始化任务和任务队列数据

//workque.h
#include "../include/head.h"

typedef struct
{
    int clientFd;//存储客户端的fd,代表着每一个任务
    pNode_t next;
}Node_t,*pNode_t;
typedef struct
{
    int size;//存储任务节点的个数
    pNode_t pHead;//任务队列的头节点
    pNode_t Ptail;//任务队列的尾节点
    pthread_cond_t cond;//条件变量
    pthread_mutex_t mutex;//互斥量
    int flag;//flag用于令线程结束的标志位
}Que_t,*pQue_t;
//workque.c
#include "../include/head.h"
#include "../include/workque.h"
int queInit(pQue_t pQue)
{
    //初始化
    pthread_mutex_init(&pQue->mutex,NULL);
    pthread_cond_init(&pQue->cond,NULL);
    pQue->pHead = NULL;
    pQue->Ptail = NULL;
    pQue->size = 0; 
    
}
int queInsert(pQue_t pQue,pNode_t pnew)//插入任务
{
    //从任务队列里面插入新的任务节点
    if(pQue == NULL)
    {
        perror("pQue is NULL");
        return -1;
    }
    if(pQue->size == 0)
    {
        pQue->pHead = pnew;
        pQue->Ptail = pnew;
        pQue->size++;
    }
    else
    {
        pQue->Ptail->next = pnew;
        pQue->Ptail = pnew;
        //尾插法,移动到下一个新节点
       pQue->size++; 
    }
    return 0;
    
}
int queGet(pQue_t pQue,pNode_t* pget)
{
    if(pQue->size == 0)
    {
        perror("pQue is NULL not Get");
        return -1;       
    }
    //取出头节点的任务,先进先出
    *pget = pQue->pHead;
    pQue->pHead = pQue->pHead->next;
    pQue->size--;
    return 0;
}

3.初始化线程池的结构和任务队列

//thread_pool.h
#ifndef __THREADPOOL_H__
#define __THREADPOOL_H__
#include "head.h"
#include "workque.h"

typedef struct {
    int len;
    char buf[1000];//约定的私有协议
}Train_t;
//线程池的数据结构
typedef struct{
    int threadNum;
    pthread_t * pthid;
    Que_t Que;
}threadPool_t, *pThreadPool_t;

int threadPoolInit(pThreadPool_t pPool, int threadNum)//初始化线程池
int threadPoolStart(pThreadPool_t pPool);//启动线程池
int transFile(int clienFd);//子线程需要处理的任务——发送文件
int tcpInit(char *ip, char *port, int *sockFd);//tcp初始化
int epollAddFd(int fd, int epfd);//epoll将文件描述符纳入监听红黑树上
#endif
//thread_pool.c
#include"../include/workque.h"
#include "../include/thread_Pool.h"
#include "../include/head.h"
//信号处理函数,用于进程退出的
void cleanFunc(void *p)
{
    pQue_t pQue = (pQue_t)p;
    
    //解锁
    pthread_mutex_unlock(&pQue->mutex);
}
void *threadFunc(void *p)
{
    pQue_t pQue = (pQue_t)p;
    pNode_t pCur;
    int getsuccess = 1;
    while(1)
    {
        if(pQue->flag == 0)
        {
            pthread_exit(NULL);
        }
        pthread_mutex_lock(&pQue->mutex);
        //使用线程终止清理函数清理锁资源
        pthread_cleanup_push(cleanFunc, pQue);
        if(0 == pQue->size)
        {
            //等待队列里面有任务
            pthread_cond_wait(&pQue->cond, &pQue->mutex);
        }
        getsuccess = queGet(pQue, &pCur);
        //如果pthread_cond_wait被pthread_cond_signal激发,就会取出一个节点
        pthread_mutex_unlock(&pQue->mutex);
        if(0 == getsuccess)
        {
            transFile(pCur->clientFd);//调用该函数,处理自己的任务
            free(pCur);
            pCur = NULL;
        }
        pthread_cleanup_pop(1);
    }
    pthread_exit(NULL);
}

int threadPoolInit(pThreadPool_t pPool, int threadNum)
{
    pPool->threadNum = threadNum;
    pPool->pthid = (pthread_t*)calloc(threadNum, sizeof(pthread_t));
    queInit(&pPool->Que);
    return 0;
}

int threadPoolStart(pThreadPool_t pPool)
{
    
    for(int i = 0; i < pPool->threadNum; ++i)
    {
        pthread_create(pPool->pthid+i, NULL, threadFunc, &pPool->Que);
    }
    return 0;
}

3.1子线程任务处理函数

#define _GNU_SOURCE
#include"../include/workque.h"
#include "../include/thread_Pool.h"
#include "../include/head.h"
#include<fcntl.h>
void sigfunc(int signum)
{
    printf("sig is comming\n");
}

int transFile(int clienFd)
{

    signal(SIGPIPE, sigfunc);
    //使用私有协议发送数据,人为的规定发送数据的边界
    Train_t train;
    printf("xxxx\n");
    memset(&train, 0, sizeof(train));
    int fd = open("file", O_RDWR);

    //存储文件名长度
    train.len = 4;
    strcpy(train.buf, "file");

    //发送文件名
    send(clienFd, &train, 4 + train.len, 0);

    struct stat fileInfo;
    bzero(&fileInfo, sizeof(fileInfo));

    
    fstat(fd, &fileInfo);

    train.len = sizeof(fileInfo.st_size);
    memcpy(train.buf, &fileInfo.st_size, train.len);

    //发送文件的总长度
    send(clienFd, &train, 4 + train.len, 0);
    printf("filesize = %ld\n", fileInfo.st_size);


    
    int sfd[2];
    pipe(sfd);
    //splice需要借助管道发送数据,一次最多可以发送65535个
    int recvLen = 0;
    while(recvLen < fileInfo.st_size){
        int ret = splice(fd, 0, sfd[1], 0, 4096, 0);
        ret = splice(sfd[0], 0, clienFd, 0, ret, 0);
        recvLen += ret;
    }

    
    return 0;
}

在这里插入图片描述

4.tcpinit()和epoll.c初始化

//tcpinit.c
//功能:用于返回一个sfd用于监听的文件描述符,为参数3,传入传出
//随后续将其纳入epoll红黑树的监听,等待就绪到wait双向链表上
#include"../include/workque.h"
#include "../include/thread_Pool.h"
#include "../include/head.h"
int tcpInit(char *ip, char *port, int *sockFd)
{
    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    ERROR_CHECK(sfd, -1, "socket");

    struct sockaddr_in serAddr;
    memset(&serAddr, 0, sizeof(serAddr));
    serAddr.sin_family = AF_INET;
    serAddr.sin_addr.s_addr = inet_addr(ip);
    serAddr.sin_port = htons(atoi(port));

    //设置套接口地址可重用
    int reuse = 1; // 变量大于0,设置的选项生效
    setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

    int ret = 0;
    ret = bind(sfd, (struct sockaddr*)&serAddr, sizeof(serAddr));
    ERROR_CHECK(ret, -1, "bind");

    ret = listen(sfd, 10);
    ERROR_CHECK(ret, -1, "listen");

    *sockFd = sfd;

    return 0;

}
//epollAddFd.c
#include"../include/workque.h"
#include "../include/thread_Pool.h"
#include "../include/head.h"
int epollAddFd(int fd, int epfd)
{   
    struct epoll_event event;
    memset(&event, 0, sizeof(event));

    event.events = EPOLLIN;
    event.data.fd = fd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);

    return 0;
}

5.主函数逻辑流程及下载完成线程退出

在这里插入图片描述

#include "../include/head.h"
#include "../include/workque.h"
#include "../include/thread_Pool.h"


int exitPipe[2];
void sigFunc(int sig)
{
    printf("触发信号函数,给子进程管道写入flag\n");
    write(exitPipe[1],&sig,4);
    
}
int main()
{
    pConf_t conf =(pConf_t) calloc(1, sizeof(Conf_t));
	pConf_t phead;
	phead = load("tcp.conf",&conf);
    //打开配置文件,读取配置项
    
    char ip[20],port[5];
    char num[4];
    int ret = 0;

    int str = atoi(phead->next->next->ip);
    printf("ip  = %s\tport = %s\n",phead->ip,phead->next->ip);
    printf("str : %d\n",str);
    pipe(exitPipe);
    if(fork())
    {
        close(exitPipe[0]);
        signal(SIGUSR1,sigFunc);
        wait(NULL);
        
    }
    threadPool_t pool;
    threadPoolInit(&pool, str);

    //2.启动线程池
    
    threadPoolStart(&pool);

    //3.创建tcp监听套接字
    int sfd = 0;
    tcpInit(phead->ip, phead->next->ip, &sfd);

    //4.创建epoll
    int epfd = epoll_create(1);
    
    epollAddFd(sfd, epfd);
    epollAddFd(exitPipe[0],epfd);
    //将管道读端纳入监听队列
    struct epoll_event evs[2];

    int newFd = 0;
    int sums = 0;
    while(1){
        sums = epoll_wait(epfd, evs, 2, -1);
        for(int i = 0; i < sums; ++i)
        {
            if(evs[i].data.fd == sfd)
            {
                newFd = accept(sfd, NULL, NULL);
                
              
                    printf("成功建立连接\n");
              
                //创建任务节点,newFd交给节点
                pNode_t pNew = (pNode_t)calloc(1, sizeof(Node_t));
                pthread_mutex_lock(&pool.Que.mutex);
                pNew->clientFd = newFd;
                pool.Que.flag = 1; 
                //把节点插入到任务队列里面
                queInsert(&pool.Que, pNew);
                //任务队列里面有数据了,唤醒等待的子线程
                pthread_cond_signal(&pool.Que.cond);
                pthread_mutex_unlock(&pool.Que.mutex);
            }
            else if(evs[i].data.fd == exitPipe[0])
            {
                for(int a = 0;a<str ;a++)
                {
                    //循环遍历所有子线程
                    pool.Que.flag = 0;
                }
                for(int a = 0 ;a < str; a++)
                {
                    pthread_join(pool.pthid[a],NULL);
                }
            }
        }
    }
    return 0;
}

后续等待完善中……
待实现功能:

  • 文件的秒上传功能。通过比对数据库的文件内容哈希值。
  • 数据库用户表。用户的注册,登录,修改密码。
  • 数据库文件表,虚拟文件系统
  • cd进入本用户的目录
  • ls查看自己目录下属于自己的文件
  • 断点续传功能
  • 2.日志记录 (1) 服务器日志记录客户端请求信息及客户端连接时间。 (2) 服务器日志记录客户端操作记录及操作时间
  • mmap 将大文件映射入内存,进行网络传递。当发现文件大于 100M,就使用 mmap 方式映
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值