基于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;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章