美文网首页
改写文件上传支持断点续传-后端C代码

改写文件上传支持断点续传-后端C代码

作者: 温暖春阳 | 来源:发表于2018-02-09 17:13 被阅读63次

    后端代码

    #include <fcgi_stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <sys/wait.h>
    #include <dirent.h>
    #include <pthread.h>
    #include <fcgiapp.h>
    
    #include "qsi.h"
    #include "cJSON.h"
    
    #define UPLOAD_LOG_MODULE "cgi"
    #define UPLOAD_LOG_PROC   "upload"
    
    #define TEMP_BUF_MAX_LEN 51200
    #define FILE_NAME_LEN 256
    #define USER_NAME_LEN 256
    
    #define THREAD_COUNT 20  
    static int counts[THREAD_COUNT];
    
    /*
        功能:获取已经上传过后的文件列表,(文件名,MD5值)
        参数:
            filePath    文件路径
            folderPath  文件夹路径
        返回值:成功0 失败返回-1
    */
    int getChunkList(char *filePath, char *folderPath, FCGX_Request request)
    {
        /*
            将目录下的所用文件组成一个文件列表,返回到前端 
        */
        char fullpath[256];
        memset(fullpath, 0x00, 256);
        sprintf(fullpath, "%s/%s", filePath, folderPath);
            
        //创建一个文件jSON对象
        cJSON * allfile = cJSON_CreateArray();    
        cJSON * fullnode= cJSON_CreateObject();
    
        char * out = NULL;
        if(NULL == fullpath)
        {       
            FCGX_FPrintF(request.out,"%s", "enter path null!\n");
            return -1;
        }
    
        DIR *dirp = opendir(fullpath);  //打开目录
        if(dirp == NULL)
        {
            FCGX_FPrintF(request.out,"%s", "open dir err");
            return -1;
        }
            
        struct dirent * dentp = NULL;
        
        //读取目录下文件
        while((dentp = readdir(dirp)))
        {           
            if((strcmp(".", dentp->d_name)) == 0 || (strcmp("..", dentp->d_name)) == 0)
            {
                continue;
            }
            
            //不是目录文件
            if(dentp->d_type == DT_REG)
            {           
                struct stat filestruct;           
                char newfilepath[256];
                sprintf(newfilepath, "%s/%s", fullpath, dentp->d_name);
                stat(newfilepath, &filestruct);
                
                //创建一个jSON对象
                cJSON *chunkList = NULL;            
                if(strcmp(folderPath,dentp->d_name ) == 0)
                {
                    chunkList = cJSON_CreateObject();
                    cJSON_AddStringToObject(chunkList, "filename", dentp->d_name);
                    FCGX_FPrintF(request.out,"%s", cJSON_Print(chunkList));
                    return 0;
                }
                else
                {
                    cJSON_AddItemToArray(allfile, chunkList = cJSON_CreateObject());
                    cJSON_AddStringToObject(chunkList, "filename", dentp->d_name); 
                    cJSON_AddNumberToObject(chunkList, "indexof", atoi(dentp->d_name));                 
                }                                                                                     
            }   
            else
            {
                return 0;               
            }                  
        }   
        cJSON_AddItemToObject(fullnode, "chunkList", allfile);          
        FCGX_FPrintF(request.out,"%s", cJSON_Print(fullnode));
        return 0;   
    }
    
    /*
        功能:判断文件夹是否存在,不存在创建一个文件夹
        
        参数:
            filepath   文件夹路径
            filename   文件夹名称
        
        返回值:成功返回0 失败返回-1
    */
    int folderIsExit(char *filepath, char *filename, FCGX_Request request)
    {       
        char fullpath[256];
        memset(fullpath, 0x00, sizeof(fullpath));
        
        sprintf(fullpath, "%s/%s", filepath, filename);
        
        if(access(fullpath, W_OK) == 0)
        {       
            //目录存在且有写的权限    遍历目录下所有文件组织文件链表
            getChunkList(filepath, filename,request);               
        }
        else
        {
            //目录不存在或没有写的权限  
             mkdir(fullpath, S_IRWXU|S_IRWXG|S_IRWXO); //0777
             cJSON *fileinfo = cJSON_CreateObject();
             cJSON_AddStringToObject(fileinfo, "fileName", filename);       
             FCGX_FPrintF(request.out,"%s", cJSON_Print(fileinfo));
        }   
        return 0;   
    }
    
    /*
        功能:在指定长度的字符串中查找字符串
        参数:
            (in)    full_data            查找字符串
            (in)    full_data_len        查找长度长度
            (in)    substr               要查找的字符串
        返回值:成功返回要查找的字符串的初始位置 失败返回NULL   
    */
    char* memstr(char* full_data, int full_data_len, char* substr)
    {
        if (full_data == NULL || full_data_len <= 0 || substr == NULL)
        {
            return NULL;
        }
    
        if (*substr == '\0')
        {
            return NULL;
        }
    
        int sublen = strlen(substr);
        char* cur = full_data;
        int last_possible = full_data_len - sublen + 1;
        int i = 0;
        for (i = 0; i < last_possible; i++)
        {
            if (*cur == *substr)
            {
                if (memcmp(cur, substr, sublen) == 0)
                {
                    // found
                    return cur;
                }
    
            }
            cur++;
        }
        return NULL;
    }
    
    
    /**
     * @brief  解析上传的post数据 保存到本地临时路径
     *         同时得到文件上传者、文件名称、文件大小
     *
     * @param len       (in)    post数据的长度
     * @param user      (out)   文件上传者
     * @param file_name (out)   文件的文件名
     * @param p_size    (out)   文件大小
     *
     * @returns
     *          0 succ, -1 fail
     */
    int recv_save_file(long len, char *user, char *filename, long *p_size, FCGX_Request request, char *filepath)
    {   
        char log[526] = {0};
        memset(log,0x00, 526);
        int ret = 0;
        char *file_buf = NULL;
        char *begin = NULL;
        char *p, *q, *k;
        
         int fd = 0;
        int ret2 = 0;
        
        char *heto= NULL;
        char *to = NULL;
             
        int totallen = 0;
        char * toen = NULL;
        
        int num = 0;
         
        int FragmentName = 0;   
        char FragmentNum[50];
        
        char FPATH[256] = {0};
        
        char content_text[TEMP_BUF_MAX_LEN] = {0};      //文件头部信息
        char boundary[TEMP_BUF_MAX_LEN] = {0};          //分界线信息
    
        //==========> 开辟存放文件的 内存 <===========
        file_buf = (char *)malloc(len);
        memset(file_buf,0x00, sizeof(file_buf));
        
        if (file_buf == NULL)
        {     
            goto END;
        }
            
          for(int i=0; i<len; i++)   //读取标准输入
          {
            file_buf[i] = FCGX_GetChar(request.in);         
          }
                
        //===========> 开始处理前端发送过来的post数据格式 <============
        begin = file_buf;    //内存起点
        p = begin;
    
        p = strstr(begin, "\r\n");
        if (p == NULL)
        {
            ret = -1;
            goto END;
        }
            
        //拷贝分界线
        strncpy(boundary, begin, p-begin);
        boundary[p-begin] = '\0';   //字符串结束符
           
        p += 2;
        //已经处理了p-begin的长度
        len -= (p-begin);
        begin = p;
    
        p = strstr(begin, "\r\n");
        if(p == NULL)
        {      
            ret = -1;
           goto END;
        }
        strncpy(content_text, begin, p-begin);              //拷贝Content-Disposition这一行内容
        content_text[p-begin] = '\0';
       
        p += 2;
        len -= (p-begin);
    
        q = begin;
        q = strstr(begin, "name=");
        
        q += strlen("name=");
        q++;    //跳过第一个"
    
        k = strchr(q, '"');
        strncpy(user, q, k-q);  //拷贝用户名
        user[k-q] = '\0';
        
        begin = k;
        q = begin;
        q = strstr(begin, "filename=");
    
        q += strlen("filename=");
        q++;    //跳过第一个"
    
        k = strchr(q, '"');
        strncpy(filename, q, k-q);  //拷贝文件名
        filename[k-q] = '\0';
            
        begin = p;
        p = strstr(begin, "\r\n");
        p += 4;//\r\n\r\n
        len -= (p-begin);
    
        //下面才是文件的真正内容 
        begin = p;
        
        //find file's end
        p = memstr(begin, len, boundary);//util_cgi.h, 找文件结尾
        if (p == NULL)
        {
            ret = -1;
            goto END;
        }
        else
        {
            p = p - 2;//\r\n
        }
        
        //解析获的文件编号 获取总片数
         heto = p + 2;
         to = NULL;
         to = strstr(heto, "\r\n");
         to += 4;
         
         totallen = 0;
         toen = strstr(to, "\r\n");
             
         toen += 4;
         to = toen;
         toen = strstr(to, "\r\n");
         
         totallen = toen - to;
             
         memset(FragmentNum, 0x00, sizeof(FragmentNum));
         strncpy(FragmentNum, to, toen - to);               //获取片段总数
        
         num = atoi(FragmentNum);
         toen+= 2;
         to = toen;
         
         toen = strstr(to, "\r\n");
         toen += 2;
         
         to = toen;
         toen = strstr(to, "\r\n");
         
         toen += 4;
         to = toen;
         toen = strstr(to, "\r\n");
             
         memset(FragmentNum, 0x00, sizeof(FragmentNum));
         strncpy(FragmentNum, to, toen - to);  
         FragmentName = atoi(FragmentNum);
             
         sprintf(FPATH, "%s/%s", filepath, FragmentNum);
              
          fd = open(FPATH, O_CREAT|O_WRONLY, 0644);
          
          if (fd < 0)
          {
              ret = -1;
              goto END;
          }
        
          //ftruncate会将参数fd指定的文件大小改为参数length指定的大小
          ftruncate(fd, (p-begin));
          write(fd, begin, (p-begin));
          close(fd);
        
    END:
        free(file_buf);
        FCGX_FFlush(request.in);  //刷新标准输入缓冲区
        return ret;
    }
    
    /*
        功能:获取文件大小
        参数:
            path  文件路径
        返回值:成功返回文件大小 失败返回-1
    */
    int getfilesize(char *path)
    {
        FILE *pf = fopen(path, "rb");
        if (pf==NULL)
        {
            return -1;
        } 
        else
        {
            fseek(pf, 0, SEEK_END);
            int size = ftell(pf);
            fclose(pf);
            return size;//返回文件大小
        }
    }
    
    /*
        功能:将多个文件合并为一个文件, 同时删除片段文件
        参数:
            part_f_path  片段文件路径
            filepath     合并后文件路径
            filename     合并后文件名
        返回值: 成功返回 0  失败返回-1
    
    */
    int  merge(char *part_f_path , int part_num, char *filepath, char *filename, FCGX_Request request)
    {
        char newpath[part_num][600]; //代表路径 
        for(int i=0; i<part_num; i++)
        {
            memset(newpath[i], 0x00, sizeof(newpath[i]));           
        }
        
        char fullpath[500] = {0};
        memset(fullpath, 0x00, sizeof(fullpath));
        sprintf(fullpath, "%s/%s",filepath, filename);
        for (int i = 0; i < part_num; i++)
        {
            sprintf(newpath[i], "%s/%d", part_f_path, i);
        }   
            
        FILE *pfw = fopen(fullpath, "wb");
        for (int i = 0; i < part_num; i++)
        {
            int length = getfilesize(newpath[i]);
            if (length != -1)
            {
                FILE *pfr = fopen(newpath[i], "rb"); //读取
                for (int j = 0; j < length; j++)
                {
                    char ch = fgetc(pfr);
                    fputc(ch, pfw);                 //写入
                }
                fclose(pfr);
                remove(newpath[i]);
            }
        }
        fclose(pfw);
        return 0;
    }
        
    
    /*
        线程处理函数
    */
    static void *doit(void *a)
    {
        int rc, i, thread_id;
        thread_id = (int)((long)a);
        pid_t pid = getpid();
        char *server_name;
        
        int count = 0;
        int flag = 0;
        
        long len;
        int ret = 0;    
        
        char filename[256] = {0}; //文件名
        char user[56] = {0};      //文件上传者
        long size = 0;
            
        char get_filePath[20] = "filepath";
        char get_fileName[20] = "filename";
        char get_fileMd5Value[56] = "fileMd5Value";
        char get_chunks[56] = "chunks";
        
        FCGX_Request request;       
        FCGX_InitRequest(&request, 0, 0); //初始化request
        
        for (;;)
        {
            static pthread_mutex_t accept_mutex = PTHREAD_MUTEX_INITIALIZER;
            static pthread_mutex_t counts_mutex = PTHREAD_MUTEX_INITIALIZER;
    
            /* Some platforms require accept() serialization, some don't.. */        
            pthread_mutex_lock(&accept_mutex);
            rc = FCGX_Accept_r(&request); 
            pthread_mutex_unlock(&accept_mutex);
            if (rc < 0)
                break;
            //从环境变量中获取SERVER_NAME名      
            server_name = FCGX_GetParam("SERVER_NAME", request.envp);                                                     
            FCGX_FPrintF(request.out,"Content-type: text/html; charset=UTF-8\r\n\r\n");
            //获取环境变量                     
            char *contentLength = FCGX_GetParam("CONTENT_LENGTH", request.envp);                
            char *querystring = FCGX_GetParam("QUERY_STRING", request.envp);                            
            char name[256] = "method";
            
            char *method = get_query_string(querystring, name);                             
            char *filename = get_query_string(querystring, get_fileName);
            char *fileMd5Value = get_query_string(querystring, get_fileMd5Value);   
            char *chunks =  get_query_string(querystring, get_chunks);  
                                                        
            if(strcasecmp(method, "check") == 0)
            {   
                char filepath[256] = "../file";                                                                                                                 
                folderIsExit(filepath, fileMd5Value,request);
            }               
            else if(strcasecmp(method, "merge") == 0)
            {       
                 char filepath[600] ={0};
                 int int_chunks  = atoi(chunks);    
                 sprintf(filepath, "../file/%s", fileMd5Value);  
                 merge(filepath , int_chunks, filepath, filename, request);                     
            }                   
            else if(strcasecmp(method, "upload") == 0)
            {                                   
                if (contentLength != NULL)
                {
                    len = strtol(contentLength, NULL, 10);  //字符串转long, 或者atol
                }
                else
                {
                    len = 0;
                }                       
                if (len <= 0)
                {
                    FCGX_FPrintF(request.out, "No data from standard input\n");            
                    ret = -1;
                }
                else
                {       
                    char filepath[600] ={0};    
                    sprintf(filepath, "../file/%s", fileMd5Value);                                                  
                    if (recv_save_file(len, user, filename, &size, request, filepath) < 0)
                    {                   
                        ret = -1;
                        return 0;
                    }                                                                                   
                }               
            }                                         
            pthread_mutex_lock(&counts_mutex);
            ++counts[thread_id];
            pthread_mutex_unlock(&counts_mutex);    
            FCGX_Finish_r(&request);
        }
        
        return NULL;
    }
    
    int main()
    {
        long i;  
        pthread_t id[THREAD_COUNT];    
        FCGX_Init();  
      
       //这里强制类型转换需要注意一下我的系统是64位,32位可能会出错
        for (i = 1; i < THREAD_COUNT; i++)          
            pthread_create(&id[i], NULL, doit, (void*)i);      
      
        doit(0);    
        return 0;   
    }
    

    这个是依赖于nginx、Fastcgi的程序,还需要CJson库,供参考。
    以上的解析post数据特定的,仅供参考。

    相关文章

      网友评论

          本文标题:改写文件上传支持断点续传-后端C代码

          本文链接:https://www.haomeiwen.com/subject/blcqtftx.html