FTP

1.项目概述

1.1项目概述

  • 本项目基于linux系统编程实现,使用了linux文件操作与网络编程等技术
  • 采用C/S模式设计,服务器与客户端可相互发送任意大小文件,支持一些常用指令查看文件,如ls,cd,pwd

1.2大体实现方法

  • 由于需要传输大文件,因此需要将文件分包传输
  • 本项目采用UDP协议传输文件,由于UDP是面向无连接,不可靠传输,因此需要我们手动进行包管理
  • 在发送端将每个包编号,接收方每接收一个包就会发送确认信息给发送方,发送方在接收到确认消息后才会发送下一个包,否则就会重发
  • 每组包附带crc8校验,确保传输过程无差错

1.3完整项目地址

  • https://gitee.com/meiyangyang945/ftp/tree/master/

2.项目实现细节

2.1crc8校验算法

  • 关于crc8校验算法,网上有很多大神的博客,此处参考

2.2通用结构体

  • 为保证收发双方指令交互的方便,在此封装了指令结构体
/*枚举,保存指令类型*/
typedef enum
{
    ls = 1,
    lls,
    pwd,
    lpwd,
    cd,
    lcd,
    get,
    put,
    help,
    quit
}te_cmd;

/*指令交互包*/
typedef struct cmd_msg
{
    te_cmd cmd;             /*指令类型*/
    char buff[BUF_SIZE];    /*指令附带的额外内容,如put a.out的a.out*/
}ts_cmd_msg;
  • 还定义了一个数据包结构体,用于保存拆分的数据包
typedef struct package_msg
{
    int id;                 /*包ID*/
    int buff_size;          /*包大小*/
    unsigned char crc8;     /*包crc校验码*/
    int is_resend;          /*是否重发标志位*/
}ts_package_msg;
typedef struct package
{
    struct package_msg pmsg;
    char data_buf[PACK_SIZE];/*数据包具体传输内容*/
}ts_package;

2.3服务器细节实现

2.1.1主函数

int main(void)
{
    char cmd_nnum[1024] = {0};
    ts_cmd_msg cmd_pack;
    char clientIPV4[16];
    struct sockaddr_in sin;
    // socklen_t sin_len = sizeof(sin);
    struct sockaddr_in client_msg;
    socklen_t client_msg_len = sizeof(client_msg);
    int b_reuse = 1;
    int fd = -1;
    if((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        perror("create socket failed in:socket():12");
        exit(-1);
    }
    /*允许绑定地址快速重用 */
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(int));
    bzero(&sin, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_port = htons(SERV_PORT);
    sin.sin_addr.s_addr = htonl(INADDR_ANY);
    /*绑定*/
    if(bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
    {
        perror("bind failed in:bind():23");
        exit(-1);
    }
    printf("server init ok\n");
    while (1)
    {
        bzero(&client_msg, sizeof(client_msg));
        /*阻塞等待客户端指令*/
        if(recvfrom(fd, &cmd_pack, sizeof(cmd_pack), 0, (struct sockaddr *)&client_msg, &client_msg_len) < 0)
        {
            perror("recv msg failed in:recvfrom():30");
            break;
        }
        /*网络字节序转本地字节序*/
        if(!inet_ntop(AF_INET, (void *)&client_msg, clientIPV4, sizeof(client_msg)))
        {
            perror("can not know client mseeage in:inet_ntop()");
            exit(-1);
        }
        printf("Recive from(%s:%d) cmd is:%d\n", clientIPV4, ntohs(client_msg.sin_port), cmd_pack.cmd);
        /*解析指令*/
        if(enum_to_cmd(&cmd_pack, cmd_nnum, fd, &client_msg) < 0)
        {
            printf("parse command failed in:enum_to_cmd()\n");
        }
        /*不用回复客户端的指令*/
        if(cmd_pack.cmd == get || cmd_pack.cmd == lls || cmd_pack.cmd == lpwd || cmd_pack.cmd == lcd || cmd_pack.cmd == put)
        {
            bzero(&cmd_pack, sizeof(cmd_pack));
        }
        else
        {
            resend_cmd_pack:
            //printf("send buff is:%s\n", cmd_pack.buff);/*DEBUG*/
            /*回复客户端消息*/
            if((sendto(fd, &cmd_pack, sizeof(cmd_pack), 0, (struct sockaddr *)&client_msg, client_msg_len)) < 0)
            {
                perror("send faied in:sendto()");
                goto resend_cmd_pack;
            }
            bzero(&cmd_pack, sizeof(cmd_pack));
        } 
    }
    return 0;
}

2.1.2解析指令函数

int enum_to_cmd(ts_cmd_msg *cmd_pack_local, char *s, int fd, struct sockaddr_in *cli_msg)
{   
    printf("cmd type is %d\n", cmd_pack_local->cmd);
    switch (cmd_pack_local->cmd)
    {
        case ls:
            strcpy(s, "ls");
            if(get_cmd_res(cmd_pack_local, s) < 0)
            {
                printf("get cmd res failed in:get_cmd_res()\n");
                return -1;
            }
            break;
        case pwd:
            strcpy(s, "pwd");
            if(get_cmd_res(cmd_pack_local, s) < 0)
            {
                printf("get cmd res failed in:get_cmd_res()\n");
                return -1;
            }
            break;
        case cd:
            printf("dir is %s\n", cmd_pack_local->buff);
            if(chdir(cmd_pack_local->buff) < 0)
            {
                perror("change dir failed in:chdir()");
                return -1;
            }
            if(get_cmd_res(cmd_pack_local, "pwd") < 0)
            {
                printf("get cmd res failed in:get_cmd_res()\n");
                return -1;
            }
            break;
        case lls:
        case lpwd:
        case lcd:
            break;
        case get:
            if(!access(cmd_pack_local->buff, F_OK))
            {
                sendto(fd, cmd_pack_local, sizeof(*cmd_pack_local), 0, (struct sockaddr *)cli_msg, sizeof(*cli_msg));
                printf("will send file\n");
                serv_send_file_process(fd, cli_msg, cmd_pack_local);
            }
            else
            {
                strcpy(cmd_pack_local->buff, "N");
                sendto(fd, cmd_pack_local, sizeof(*cmd_pack_local), 0, (struct sockaddr *)cli_msg, sizeof(*cli_msg));
                printf("No such file or directory\n");
            }
            //strcpy(cmd_pack_local->buff, "send done\n");
            break;
        case put:
            printf("will recv file\n");
            serv_recv_file_process(fd, cli_msg, cmd_pack_local);
            break;
        default:
            return -1;
    }
    return 0;
}

2.1.3获取指令执行结果

  • 函数popen
#include 

/*
功能:调用fork()产生子进程,然后在子进程中执行参数command的指令
参数:
参数1:指令
参数2:'r'代表读取,'w'代表写入,如果'r',那么调用进程读进command的标准输出,
        如果为'w',那么调用进程写到command的标准输入
返回值:成功则返回文件指针,否则返回NULL
*/
FILE *popen(const char *command, const char *type);
  • 代码
int get_cmd_res(ts_cmd_msg *cmd_pack_local, char *s)
{
    int rsize = 0;
    printf("cmd is %s\n", s);/*DEBUG*/
    FILE *dir = popen(s, "r");
    if(dir == NULL)
    {
        perror("fail to open dir in:popen()");
        return -1;
    }
    bzero(cmd_pack_local->buff, BUF_SIZE);
    if((rsize = fread(cmd_pack_local->buff, sizeof(char), BUF_SIZE, dir)) < 0)
    {
        perror("get cmd res failed in:fread()");
        return -1;
    }
    //printf("popen buff is:%s\n", cmd_pack_local->buff);/*DEBUG*/
    pclose(dir);
    return rsize;
}

2.1.4服务器发数据

int serv_send_file_process(int fd, struct sockaddr_in *cli_msg, ts_cmd_msg *cmd_pack_local)
{
    socklen_t client_msg_len = sizeof(*cli_msg);
    char cli_ip[16] = {0};
    if(!inet_ntop(AF_INET, (void *)cli_msg, cli_ip, sizeof(*cli_msg)))
    {
        perror("can not know client mseeage in:inet_ntop()");
        exit(-1);
    }
    printf("start send to %s:%d\n", cli_ip, ntohs(cli_msg->sin_port));
    FILE *file;
    //file = fopen("test.gif", "rb");
    file = fopen(cmd_pack_local->buff, "rb");
    if(file == NULL)
    {
        perror("file open failed");
        return -1;
    }
    printf("open file success\n");
    int pack_id = 1;
    int check_id = 0;
    int pack_size = 0;
    int send_size = 0;
    ts_package pack;
    ts_package_msg check_msg;
    bzero(&pack, sizeof(pack));
    /*每次读取PACK_SIZE个字节*/
    while ((pack_size = fread(pack.data_buf, sizeof(char), PACK_SIZE, file)) > 0)
    {
        pack.pmsg.id = pack_id;
        pack.pmsg.buff_size = pack_size;
        pack.pmsg.crc8 = cal_crc_table(pack.data_buf, pack_size);
        pack.pmsg.is_resend = 0;
        
        /*重发标签*/
        resend:
        if((send_size = sendto(fd, &pack, sizeof(pack), 0, (struct sockaddr *)cli_msg, client_msg_len)) < 0)
        {
            perror("send faied in:sendto()");
            goto resend;
        }
        printf("sending pack %d, size %d Byte\n", pack.pmsg.id, send_size);
        /*接收客户端反馈信息*/
        recvfrom(fd, &check_msg, sizeof(check_msg), 0, (struct sockaddr *)cli_msg, &client_msg_len);
        /*包错误。重发*/
        if(check_msg.is_resend == 1)
        {
            check_id = check_msg.id;
            printf("pack %d err\n", check_id);
            goto resend;
        }
        else
        {
            pack_id++;
            bzero(&pack, sizeof(pack));
        }
    }  
    /*发送结束标志,即空的结构体*/
    sendto(fd, &pack, 0, 0, (struct sockaddr *)cli_msg, client_msg_len);
    printf("send done...\n");
    fclose(file);
    return 0;
}

2.1.5服务器接收数据

int serv_recv_file_process(int fd, struct sockaddr_in *cli_msg, ts_cmd_msg *cmd_pack_local)
{
    FILE *file;
    file = fopen(cmd_pack_local->buff, "wb");
    if(file == NULL)
    {
        perror("open file failed in:fopen()");
    }
    printf("open file success\n");
    int recv_len = 0;
    int check_pack_id = 1;
    ts_package pack;
    unsigned char check_crc;
    ts_package_msg check_msg;
    bzero(&pack, sizeof(pack));
    socklen_t cli_msg_len = sizeof(*cli_msg);
    /*接收数据包*/
    while((recv_len = recvfrom(fd, &pack, sizeof(pack), 0, (struct sockaddr *)cli_msg, &cli_msg_len)) > 0)
    {
        //printf("in while\n");
        printf("recving %d pack, size %d Byte\n", pack.pmsg.id, recv_len);
        check_crc = cal_crc_table(pack.data_buf, pack.pmsg.buff_size);
        /*检测数据包顺序是否发送正常*/
        if(check_pack_id == pack.pmsg.id)
        {
            /*crc8校验*/
            if(pack.pmsg.crc8 == check_crc)
            {
                /*成功接收包,发送反馈信息*/
                check_msg.id = check_pack_id++;
                check_msg.is_resend = 0;
                fwrite(pack.data_buf, sizeof(char), pack.pmsg.buff_size, file);
                bzero(&pack, sizeof(pack));
                sendto(fd, &check_msg, sizeof(check_msg), 0, (struct sockaddr *)cli_msg, cli_msg_len);
            }
            else
            {
                printf("crc8 check err\n");
                check_msg.id = check_pack_id;
                /*将错误标志置为1*/
                check_msg.is_resend = 1;
                /*清空错误数据包*/
                bzero(&pack, sizeof(pack));
                /*发送错误信息*/
                sendto(fd, &check_msg, sizeof(check_msg), 0, (struct sockaddr *)cli_msg, cli_msg_len);
            }
        }
        else
        {
            printf("pack id err\n");
            check_msg.id = check_pack_id;
            check_msg.is_resend = 1;
            bzero(&pack, sizeof(pack));
            sendto(fd, &check_msg, sizeof(check_msg), 0, (struct sockaddr *)cli_msg, cli_msg_len);
        }
    }
    printf("recv done\n");
    fclose(file);
    //bzero(cmd_pack_local, sizeof(*cmd_pack_local));
    return 0;
}

2.4客户端细节实现

2.4.1主函数

int main(int argc, char ** argv)
{
    int port = -1;
    int fd = -1;
    ts_cmd_msg cmd_pack;
    struct sockaddr_in cin;
    socklen_t cin_len = sizeof(cin);
    // char buff[64] = {0};
    // int ret = -1;
    if(argc != 3)
    {
        tips(argv[0]);
        exit(-1);
    }
    port = atoi(argv[2]);
    if(port < 5000)
    {
        tips(argv[0]);
        exit(-1); 
    }
    /*创建socket */
    if((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        perror("socket");
        exit(-1);
    }
    /*填充结构体,即目标服务器IP和端口 */
    bzero(&cin, sizeof(cin));
    cin.sin_family = AF_INET;
    cin.sin_port = htons(port);
    if(inet_pton(AF_INET, argv[1], (void *)&cin.sin_addr.s_addr) != 1)
    {
        perror("inet_pton");
        exit(-1);
    }
    puts("UDP client start ok");
    bzero(&cmd_pack, sizeof(cmd_pack));
    while (1)
    {
        char input_cmd[50] = {0};
        int re_cmd = 0;
        printf(">");
        if((fgets(input_cmd, 50, stdin)) == NULL)
        {
            perror("fgets");
        }
        char *s = strstr(input_cmd, "\n");
        strcpy(s, "\0");/*去换行符*/
        if((re_cmd = cmd_to_enum(&cmd_pack, input_cmd)) < 0)
        {
            printf("err cmd\n");
        }
        else if(re_cmd > 0)
        {
            break;
        }
        switch (cmd_pack.cmd)
        {
            case get:
                sendto(fd, &cmd_pack, sizeof(cmd_pack), 0, (struct sockaddr *)&cin, cin_len);
                recvfrom(fd, &cmd_pack, sizeof(cmd_pack), 0, (struct sockaddr *)&cin, &cin_len);
                if(!strcmp(cmd_pack.buff, "N"))
                {
                    printf("No such file or directory\n");
                }
                else
                {
                    cli_recv_file_process(fd, &cin, &cmd_pack);
                }
                break;
            case put:
                if(!access(cmd_pack.buff, F_OK))
                {
                    sendto(fd, &cmd_pack, sizeof(cmd_pack), 0, (struct sockaddr *)&cin, cin_len);
                    cli_send_file_process(fd, &cin, &cmd_pack);
                }
                else
                {
                    printf("No such file or directory\n");
                }
                break;
            case lls:
            case lpwd:
            case lcd:
                break;
            default:
                /*发送指令*/
                sendto(fd, &cmd_pack, sizeof(cmd_pack), 0, (struct sockaddr *)&cin, cin_len);
                bzero(&cmd_pack, sizeof(cmd_pack));
                /*接收消息*/
                recvfrom(fd, &cmd_pack, sizeof(cmd_pack), 0, (struct sockaddr *)&cin, &cin_len);
                printf("%s", cmd_pack.buff);
                //printf("%s\n", cmd_pack.buff);
                break;
        } 
    }
    return 0;
}

2.4.2指令解析函数

int cmd_to_enum(ts_cmd_msg *cmd_pack_local, char *s)
{
    char cmd[50] = {0};
    strncpy(cmd, s, 50);
    //printf("cmd is %s\n", cmd);/*DEBUG*/
    if(!strcmp("ls", cmd))
    {
        cmd_pack_local->cmd = ls;
        return 0;
    }
    if(!strcmp("lls", cmd))
    {
        system("ls");
        cmd_pack_local->cmd = lls;
        return 0;
    }                
    if(!strcmp("pwd", cmd))
    {
        cmd_pack_local->cmd = pwd;
        return 0;
    }     
    if(!strcmp("lpwd", cmd))
    {
        system("pwd");
        cmd_pack_local->cmd = lpwd;
        return 0;
    }             
    if(!strncmp(cmd, "cd ", strlen("cd ")))
    {
        char *s = strstr(cmd, "cd ");
        // printf("input:%s", s+strlen("cd "));
        cmd_pack_local->cmd = cd;
        strcpy(cmd_pack_local->buff, s+strlen("cd "));
        return 0;
    }
    if(!strncmp(cmd, "lcd ", strlen("lcd ")))
    {
        char *s = strstr(cmd, "lcd ");
        // printf("input:%s", s+strlen("cd "));
        cmd_pack_local->cmd = lcd;
        chdir(s+strlen("lcd "));
        system("pwd");
        return 0;
    }
    if(!strncmp(cmd, "get ", strlen("get ")))
    {
        char *s = strstr(cmd, "get ");
        // printf("input:%s", s+strlen("cd "));
        cmd_pack_local->cmd = get;
        strcpy(cmd_pack_local->buff, s+strlen("get "));
        // recv_file_process(fd, serv_msg, cmd_pack_local);
        return 0;
    }
    if(!strncmp(cmd, "put ", strlen("put ")))
    {
        char *s1 = strstr(cmd, "put ");
        cmd_pack_local->cmd = put;
        strcpy(cmd_pack_local->buff, s1+strlen("put "));
        return 0;
    }
    if(!strcmp("quit", cmd))
    {
        return 1;
    } 
    return -1;
}

2.4.3客户端接收数据

int cli_recv_file_process(int fd, struct sockaddr_in *serv_msg, ts_cmd_msg *cmd_pack_local)
{
    FILE *file;
    file = fopen(cmd_pack_local->buff, "wb");
    if(file == NULL)
    {
        perror("open file failed in:fopen()");
    }
    printf("open file success\n");
    int recv_len = 0;
    int check_pack_id = 1;
    ts_package pack;
    unsigned char check_crc;
    ts_package_msg check_msg;
    bzero(&pack, sizeof(pack));
    socklen_t serv_msg_len = sizeof(*serv_msg);
    while((recv_len = recvfrom(fd, &pack, sizeof(pack), 0, (struct sockaddr *)serv_msg, &serv_msg_len)) > 0)
    {
        //printf("in while\n");
        printf("recving %d pack, size %d Byte\n", pack.pmsg.id, recv_len);
        check_crc = cal_crc_table(pack.data_buf, pack.pmsg.buff_size);
        if(check_pack_id == pack.pmsg.id)
        {
            if(pack.pmsg.crc8 == check_crc)
            {
                /*成功接收包,发送反馈信息*/
                check_msg.id = check_pack_id++;
                check_msg.is_resend = 0;
                fwrite(pack.data_buf, sizeof(char), pack.pmsg.buff_size, file);/*(待补全)错误处理*/
                bzero(&pack, sizeof(pack));
                sendto(fd, &check_msg, sizeof(check_msg), 0, (struct sockaddr *)serv_msg, serv_msg_len);
            }
            else
            {
                printf("crc8 check err\n");
                check_msg.id = check_pack_id;
                check_msg.is_resend = 1;
                bzero(&pack, sizeof(pack));
                sendto(fd, &check_msg, sizeof(check_msg), 0, (struct sockaddr *)serv_msg, serv_msg_len);
            }
        }
        else
        {
            printf("pack id err\n");
            check_msg.id = check_pack_id;
            check_msg.is_resend = 1;
            bzero(&pack, sizeof(pack));
            sendto(fd, &check_msg, sizeof(check_msg), 0, (struct sockaddr *)serv_msg, serv_msg_len);
        }
    }
    fclose(file);
    printf("recv done\n");
    //bzero(cmd_pack_local, sizeof(*cmd_pack_local));
    return 0;
}

2.4.4客户端发送数据

int cli_send_file_process(int fd, struct sockaddr_in *serv_msg, ts_cmd_msg *cmd_pack_local)
{
    socklen_t serv_msg_len = sizeof(*serv_msg);
    printf("start send to server\n");
    FILE *file;
    //file = fopen("test.gif", "rb");
    file = fopen(cmd_pack_local->buff, "rb");
    if(file == NULL)
    {
        perror("file open failed");
        return -1;
    }
    printf("open file success\n");
    int pack_id = 1;
    int check_id = 0;
    int pack_size = 0;
    int send_size = 0;
    ts_package pack;
    ts_package_msg check_msg;
    bzero(&pack, sizeof(pack));
    while ((pack_size = fread(pack.data_buf, sizeof(char), PACK_SIZE, file)) > 0)
    {
        pack.pmsg.id = pack_id;
        pack.pmsg.buff_size = pack_size;
        pack.pmsg.crc8 = cal_crc_table(pack.data_buf, pack_size);
        pack.pmsg.is_resend = 0;
        
        resend:
        if((send_size = sendto(fd, &pack, sizeof(pack), 0, (struct sockaddr *)serv_msg, serv_msg_len)) < 0)
        {
            perror("send faied in:sendto()");
            goto resend;
        }
        printf("sending pack %d, size %d Byte\n", pack.pmsg.id, send_size);
        recvfrom(fd, &check_msg, sizeof(check_msg), 0, (struct sockaddr *)serv_msg, &serv_msg_len);
        if(check_msg.is_resend == 1)
        {
            check_id = check_msg.id;
            printf("pack %d err\n", check_id);
            goto resend;
        }
        else
        {
            pack_id++;
            bzero(&pack, sizeof(pack));
        }
    }  
    sendto(fd, &pack, 0, 0, (struct sockaddr *)serv_msg, serv_msg_len);
    bzero(cmd_pack_local, sizeof(*cmd_pack_local));
    printf("send done...\n");
    fclose(file);
    return 0;
}

2.5Makefile

TAR1 = server
TAR2 = client
TAR3 = crc8
CC := gcc
SRC = $(wildcard ./Src/*.c)
OBJS = $(patsubst %.c, %.o, $(wildcard ./Src/*.c))
CFLAGS = -c -Wall -I Inc

all:$(TAR1) $(TAR2)
$(TAR1):$(OBJS)
    $(CC) ./Src/$(TAR1).o ./Src/$(TAR3).o -o $(TAR1)
$(TAR2):$(OBJS)
    $(CC) ./Src/$(TAR2).o ./Src/$(TAR3).o -o $(TAR2)
%.o:%.c
    $(CC) $(CFLAGS) $^ -o $@

.PHONY:
clean_all:
    rm $(OBJS) $(TAR1) $(TAR2)

你可能感兴趣的:(FTP)