基於TCP的文件傳輸

技術點:
chdir:將工作目錄切到當前。
fprintf:拼接字符串。

服務器端:

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <dirent.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>


#pragma pack(1)

struct file_info
{
    char name[300];
    int size; //-1表示爲文件夾,非負整數表示普通文件
};

#pragma pack()


struct file_info fi;
const char* file_path; //要發送文件的路徑

void* srv_thr(void* arg);
void send_file(int sock_conn, const char* path);
int send_regfile(int sock_conn, const char* path);
int send_dir(int sock_conn, const char* path);


int main(int argc, char** argv)
{
    if(argc != 2)
    {
        fprintf(stderr, "命令語法錯誤!\n");
        return -1;
    }

    if(access(argv[1], R_OK) != 0)
    {
        fprintf(stderr, "%s不存在或不可讀!\n", argv[1]);
        return -1;
    }

    signal(SIGPIPE, SIG_IGN);

    //獲取要發送的文件的路徑
    file_path = argv[1];

    //第一步:創建監聽套接字
    //sock_listen:套接字描述符,用來唯一地標識一個套接字
    //socket函數的第一個參數表示地址家族
    //AF_INET:使用Internet地址家族,即採用TCP/IP網絡標準
    //socket函數的第二個參數表示套接字類型,主要有三種類型:
    //SOCK_STREAM 即流套接字,用於TCP協議通信
    //SOCK_DGRAM 即數據報套接字,用於UDP協議通信
    //SOCK_RAW 即原始套接字,用於網絡底層通信
    //socket函數的第三個參數表示使用的通信協議,0表示使用默認的協議
    int sock_listen = socket(AF_INET, SOCK_STREAM, 0);

    //設置端口複用
    int opt_val = 1;
    setsockopt(sock_listen, SOL_SOCKET, SO_REUSEADDR, &opt_val, sizeof(opt_val));


    //第二步:給套接字綁定一個地址
    struct sockaddr_in myaddr;
    myaddr.sin_family = AF_INET; //指定地址家族爲Internet地址家族
    myaddr.sin_addr.s_addr = INADDR_ANY; //指定IP地址爲本機任意地址
    myaddr.sin_port = htons(9999); //指定端口號爲8888
    //htons:將一個short類型數據從主機字節序轉化爲網絡字節序

    if(-1 == bind(sock_listen, (struct sockaddr*)&myaddr, sizeof(myaddr)))
    {
        perror("bind");
        exit(-1);
    }

    //第三步:將套接字設置爲監聽狀態
    //listen函數的第二個參數表示監聽等待隊列的長度,通常設置爲5
    listen(sock_listen, 5);

    //設置接收超時爲300ms
    struct timeval rcv_timeout;
    rcv_timeout.tv_sec = 0;
    rcv_timeout.tv_usec = 300000;

    pthread_t tid;
    int sock_conn;
    struct sockaddr_in client_addr;
    socklen_t len;

    while(1)
    {
        //第四步:接受客戶端連接請求
        //accept函數的返回值爲一個連接套接字,專門用於對應的連接上和客戶端進行通信
        //調用accept函數時如果沒有客戶端連接請求到來,它將阻塞當前線程的執行,直到接收到一個客戶端連接請求或者出錯才返回
        len = sizeof(client_addr);

        sock_conn = accept(sock_listen, (struct sockaddr*)&client_addr, &len);

        if(sock_conn == -1)
        {
            perror("accept");
            continue;
        }

        printf("%s:%d已經連接!\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));


        //設置recv超時時間爲300ms
        setsockopt(sock_conn, SOL_SOCKET, SO_RCVTIMEO, &rcv_timeout, sizeof(rcv_timeout));

        if(0 != pthread_create(&tid, NULL, srv_thr, (void*)sock_conn))
        {
            perror("pthread_create");
            close(sock_conn);
        }
    }

    //第七步:關閉監聽套接字
    close(sock_listen);

    return 0;
}


void* srv_thr(void* arg)
{
    pthread_detach(pthread_self());

    int sock_conn = (int)arg;

    send_file(sock_conn, file_path);

    close(sock_conn);

    return NULL;
}


//發送文件
void send_file(int sock_conn, const char* path)
{
    //獲取文件名和所處路徑
    const char* file_name = NULL;
    char file_path[300] = "./";

    file_name = strrchr(path, '/');

    if(file_name == NULL)
    {
        file_name = path;
    }
    else 
    {
        file_name++;
        strncpy(file_path, path, file_name - path);
    }

    //將當前工作目錄設置爲要發送的文件所在的目錄
    chdir(file_path);


    //判斷文件類型
    struct stat st;
    stat(path, &st);

    if(S_ISDIR(st.st_mode))
    {
        //文件夾
        send_dir(sock_conn, file_name);
    }
    else
    {
        //非文件夾
        send_regfile(sock_conn, file_name);
    }
}


//發送普通文件
int send_regfile(int sock_conn, const char* path)
{
    //獲取文件相對路徑
    struct file_info fi = {"", 0};  
    strcpy(fi.name, path);

    //獲取文件大小
    struct stat st;
    stat(path, &st);

    fi.size = st.st_size;

    send(sock_conn, &fi, sizeof(fi), 0);

    char msg[1024];
    int ret;
    FILE* fp =NULL;

    fp = fopen(path, "rb");

    if(fp == NULL) return 1;

    while(!feof(fp))
    {
        ret = fread(msg, 1, sizeof(msg), fp);
        if(send(sock_conn, msg, ret, 0) < 0)
            break;
    }

    fclose(fp);

    ret = recv(sock_conn, msg, sizeof(msg)-1, 0);

    if(ret > 0)
    {
        msg[ret] = '\0';

        if(strcmp(msg, "ok") == 0)
        {
            //printf("發送文件成功!\n");
            return 0;
        }
        else if(strcmp(msg, "err") == 0)
        {
            //printf("發送文件失敗!\n");
            return 1;
        }
        else
        {
            //printf("非法客戶端!\n");
            return 2;
        }
    }
    else
    {
        return 3;
    }
}


//發送文件夾
int send_dir(int sock_conn, const char* path)
{
    struct file_info fi = {"", -1};
    int ret;
    char reply[10];
    int send_flag = 0;

    strcpy(fi.name, path);
    send(sock_conn, &fi, sizeof(fi), 0);

    ret = recv(sock_conn, reply, sizeof(reply) - 1, 0);

    if(ret > 0)
    {
        reply[ret] = '\0';

        if(strcmp(reply, "ok") == 0) 
        {
            send_flag = 1;
        }
    }

    if(send_flag != 1) return 1;

    DIR* dirp = opendir(path);

    struct dirent* de = NULL;
    char file_path[300];
    struct stat st;

    while(de = readdir(dirp))
    {
        if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
            continue;

        sprintf(file_path, "%s/%s", path, de->d_name);

        stat(file_path, &st);

        if(S_ISDIR(st.st_mode))
        {
            //文件夾
            if(send_dir(sock_conn, file_path) != 0) return 1;
        }
        else
        {
            //非文件夾
            if(send_regfile(sock_conn, file_path) != 0) return 1;
        }
    }

    closedir(dirp);

    return 0;
}

客戶端

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <unistd.h>

#pragma pack(1)//設置爲1字節對齊
struct file_info
{
    char name[300];
    int size;
};
#pragma pack()
int rcv_file(int socket_conn);
int main(int argc,char** argv)
{
    if(argc<3 || argc>4)
    {
        printf("參數有誤");
        exit(-1);
    }

    if(argc==4)//到指定目錄創建文件夾
    {
        if(-1==access(argv[3],W_OK))//判斷指定目錄是否存在
        {
            perror("當前目錄不可寫");
            exit(-1);
        }

        if(-1==chdir(argv[3]))
        {
            perror("設置當前工作目錄失敗");
            return -1;
        }
    }

    int socket_conn=socket(AF_INET,SOCK_STREAM,0);

    struct sockaddr_in ser_addr;
    ser_addr.sin_family=AF_INET;
    ser_addr.sin_addr.s_addr=inet_addr(argv[1]);
    ser_addr.sin_port=htons(atoi(argv[2]));

    if(-1==connect(socket_conn,(struct sockaddr*)&ser_addr,sizeof(ser_addr)))
    {
        perror("connect");
        exit(-1);
    }

    //設置接收超時爲1秒
    struct timeval tv;
    tv.tv_sec=1;
    tv.tv_usec=0;
    setsockopt(socket_conn,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv));

    int i;
    while(1)
    {
        i=rcv_file(socket_conn);    
        if(i==0)
        {
            printf("接收成功\n");
            break;
        }
    }
    close(socket_conn);
    return 0;
}
int rcv_file(int socket_conn)
{
    struct file_info fi = {"",0};
    int ret;
    char buf[1024];
    char name[100];
    struct stat s;

    ret=recv(socket_conn,&fi,sizeof(fi),0);
    if(ret==0)//對方斷開連接
    {
        return 0;
    }
    if(ret!=sizeof(fi))
    {
        fprintf(stderr," 接收失敗\n");
        return -1;
    }

    if(fi.size==-1)//文件夾
    {
        mkdir(fi.name,0777);
        send(socket_conn,"ok",2,0);
    }
    else if(fi.size>=0)//普通文件
    {
        FILE* fp=fopen(fi.name,"wb");
        int recv_cnt=0;

        while(recv_cnt<fi.size)//根據文件大小,判斷什麼時候接收文件
        {
            ret=recv(socket_conn,buf,sizeof(buf),0);
            if(ret<=0)
            {
                perror("recv");
                break;
            }
            recv_cnt+=ret;
            fwrite(&buf,ret,1,fp);
        }

        fclose(fp);
        if(recv_cnt==fi.size) 
        {
                send(socket_conn,"ok",2,0);     
        }
        else
        {
                send(socket_conn,"err",3,0);
        }

    }
    else
    {
        fprintf(stderr,"server err\n");
        return -1;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章