TCP项目:文件传输协议,通过网络传送文件

本文详细介绍了一种基于Socket的网络通信架构,包括服务端和客户端的实现流程。涵盖了基本功能如文件上传下载、目录切换及高级特性如用户名密码验证、MD5校验等。深入解析了消息结构、文件操作、权限验证及日志记录等关键环节。

一、架构:

    服务端:

1)socket

2)bind

3)listen

4)accept

5)recv

int recv( _In_ SOCKET s, _Out_ char *buf, _In_ int len, _In_ int flags);


      send

#include <sys/types.h>
#include <sys/socket.h>

       ssize_t send(int sockfd, const void *buf, size_t len, int flags);

      
sockfd:一个用于标识已连接套接口的描述字。
buf:包含待发送数据的缓冲区。
len:缓冲区中数据的长度。
flags:调用执行方式。

 

    客户端:

1)socket

2)connect

3)send

     recv

创建一个结构体,用作传输文件的载体:

#ifndef MSG_H
#define MSG_H
typedef enum FTP_CMD FTP_CMD;
enum FTP_CMD{
    FTP_CMD_LS=0,
    FTP_CMD_GET=1,
    FTP_CMD_PUT=2,
    FTP_CMD_QUIT=3,
    FTP_CMD_CD=4,
    FTP_CMD_AUTH=5,
    FTP_CMD_HIST=6,

    FTP_CMD_ERROR,
};

struct Msg{

    FTP_CMD cmd;

    char args[32];

    char md5[64];

    long data_lenth;

    char data[5000];
};
struct Auth{
    enum FTP_CMD cmd;
    char username[32];
    char password[32];
};
#endif

 

  客户端接收键盘输入的指令到Msg.args,客户端对Msg.arg进行处理,找到对应的cmd,将cmd传给服务端,服务端执行对应的cmd命令,将执行的结果传到Msg.data反馈给客户端。

例如输入ls指令:

//客户端:
int handle_user_input(struct Msg *msg_send) {
char buf[32];
printf("please input\n");
    
fgets(buf, 32, stdin);

if (0 == memcmp(buf, "ls", 2)) 
{
        cmd = FTP_CMD_LS;
}
if (cmd == FTP_CMD_ERROR)
 {
        return -1;
 }

  msg_send->cmd = cmd;
  strcpy(msg_send->args, buf);
return 0;
}

//服务端:
void handle_cmd(struct Msg *in_cmd, struct Msg *out_cmd) {
out_cmd->cmd = in_cmd->cmd;

    switch (in_cmd->cmd) {
        case FTP_CMD_LS:
           
            fp = popen(in_cmd->args, "r");  // popen创建一个管道,调用fork产生
            //一个子进程来执行ls,然后返回ls的结果到管道
            if (NULL != fp) {
                fread(out_cmd->data, 1, sizeof(out_cmd->data), fp);
            
                pclose(fp);
            }
            break;
         default:
            break;
    }
}

 

二、要实现的功能

基本功能:

ls:显示服务端的文件

get:下载服务端的文件。

        要点:1.分割Msg.args中get和文件名,fopen打开对应文件名的文件。

int split_string(char *in_str,char *out_str)
{
    char *first;
    char *_n;

    first = strstr(in_str," ");
    _n = strstr(in_str,"\n");

    while(1){
    if(NULL == first||*first =='\0'){
        return -1;
    }
        first++;
    if(*first !=' '){
        break;
    }

  }
strcpy(out_str,first);
out_str[_n-first]='\0';
return 0;
}

 

     2.计算文件长度,fread读取相应长度的内容到Msg.data。

long get_length(char *filename)
{
    FILE *fp = fopen(filename,"r");
    if(fp !=NULL){
        if(fseek(fp,0,SEEK_END)<0){
            log_write("fseek fialed");
            return -1;
        }
        long length =ftell(fp);
        fclose(fp);
        return length;
    }
return -1;
}

put:上传本地文件到服务端

       客户端打开本地文件fread读到Msg.data传到服务端,服务端创建文件夹,fwrite将Msg.data写进文件。

 

quit:输入quit客户端和服务端都退出。

cd:切换目录

       调用chdir函数

#unistd.h 

 int chdir(const char *path); //改变当前目录到path

高级功能:

用户名密码验证:

       服务端用一个文件存储用户账号密码,客户端connect前,需输入账号密码,上传到服务端,如果memcmp与服务端的账号密码一致,则connect,反之阻止connect。

#include <string.h>


//函数原型
int memcmp(const void *str1, const void *str2, size_t n));


//参数
str1-- 指向内存块的指针。
str2-- 指向内存块的指针。
n-- 要被比较的字节数。

md5效验:

在文件上传下载前后分别记录文件的文件的md5,如果md5 memcmp()一致,则通过,反之删除文件,上传,下载失败。

void get_md5(char *filename,char *md5sum)
{
    char cmd[64];
    sprintf(cmd,"md5sum %s",filename);

    char md5[64];
    FILE *fp=popen(cmd,"r");
    if(fp !=NULL){
    int ret=fread(md5,1,sizeof(md5),fp);
    log_write("fread ret %d,md5 %s\n",ret,md5);
    pclose(fp);
    }
    sscanf(md5,"%s",md5sum);
}

在实现md5效验的时候我犯了一个错误:打开文件fopen后,在没有关闭文件fclose的情况下记录md5,导致md5上传前后不一致,

导致上传失败。所以,记得fclose关闭文件后再记录md5的值。

 

 

history:记录输入的指令

    在客户端进行指令处理时,将输入的指令内容Msg.args记录到链表中,当用户输入”history“时,将链表内容传到Msg.data,

客户端打印记录。

void linklist_insert(struct linklist **head,char *cmd){

    struct linklist *node=(struct linklist *)malloc(sizeof(struct linklist));
    strcpy(node->cmd,cmd);
    node->pnext=*head;
    *head=node;

}
void linklist_get_cmd(struct linklist *head,char *out_buf){

    struct linklist *p = head;
    char buf[32];
    while(p!=NULL){
    //在命令后面加\n
    sprintf(buf,"%s",p->cmd);
    //拷贝命令
    strcat(out_buf,buf);
    //指向下一个节点
    p = p->pnext;
    }
}

完整程序:log.c

#include "log.h"
#include <stdio.h>
#include <stdarg.h>

FILE *g_log=NULL;

void log_create(const char *file){

    g_log=fopen(file,"a+");
    if(NULL==g_log){
    printf("fopen log fail");
    }
}

void log_destroy(){
    fclose(g_log);
    g_log=NULL;
}

void log_write(const char *format, ...){

    va_list args;

    va_start(args,format);

    vfprintf(g_log,format,args);

    va_end(args);
    fflush(g_log);
}

log.h

#ifndef LOG_H
#define LOG_H

void log_create();

void log_destroy();

void log_write(const char *format, ...);

#endif

msg.h

#ifndef MSG_H
#define MSG_H
typedef enum FTP_CMD FTP_CMD;
enum FTP_CMD{
    FTP_CMD_LS=0,
    FTP_CMD_GET=1,
    FTP_CMD_PUT=2,
    FTP_CMD_QUIT=3,
    FTP_CMD_CD=4,
    FTP_CMD_AUTH=5,
    FTP_CMD_HIST=6,

    FTP_CMD_ERROR,
};

struct Msg{

    FTP_CMD cmd;

    char args[32];

    char md5[64];

    long data_lenth;

    char data[5000];
};
struct Auth{
    enum FTP_CMD cmd;
    char username[32];
    char password[32];
};
#endif

split.c

#include <stdio.h>
#include <string.h>
#include "split.h"
#include "log.h"

int split_string(char *in_str,char *out_str)
{
    char *first;
    char *_n;

    first = strstr(in_str," ");
    _n = strstr(in_str,"\n");

    while(1){
    if(NULL == first||*first =='\0'){
        return -1;
    }
        first++;
    if(*first !=' '){
        break;
    }

  }
strcpy(out_str,first);
out_str[_n-first]='\0';
return 0;
}

long get_length(char *filename)
{
    FILE *fp = fopen(filename,"r");
    if(fp !=NULL){
        if(fseek(fp,0,SEEK_END)<0){
            log_write("fseek fialed");
            return -1;
        }
        long length =ftell(fp);
        fclose(fp);
        return length;
    }
return -1;
}

void get_md5(char *filename,char *md5sum)
{
    char cmd[64];
    sprintf(cmd,"md5sum %s",filename);

    char md5[64];
    FILE *fp=popen(cmd,"r");
    if(fp !=NULL){
    int ret=fread(md5,1,sizeof(md5),fp);
    log_write("fread ret %d,md5 %s\n",ret,md5);
    pclose(fp);
    }
    sscanf(md5,"%s",md5sum);
}

split.h

#ifndef SPLIT_H
#define SPLIT_H
int split_string(char *in_str,char *out_str);
long get_length(char *filename);
void get_md5(char *filename,char *md5sum);
#endif

server.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <stdlib.h>
#include "log.h"
#include "msg.h"
#include "split.h"
#include "linklist.h"
int g_running;

void handle_cmd(struct Msg *in_cmd, struct Msg *out_cmd) {
    static struct linklist *head =NULL;
    FILE *fp = NULL;
    int ret;
    char filename[32];
    char md5sum[64];
    int length;
    out_cmd->cmd = in_cmd->cmd;

    switch (in_cmd->cmd) {
        case FTP_CMD_LS:
            linklist_insert(&head,in_cmd->args);
            fp = popen(in_cmd->args, "r");  // popen创建一个管道,调用fork产生
            //一个子进程来执行ls,然后返回ls的结果到管道
            if (NULL != fp) {
                ret = fread(out_cmd->data, 1, sizeof(out_cmd->data), fp);
                printf("%s", out_cmd->data);
                log_write("fread ret %d,data %s\n", ret, out_cmd->data);
                pclose(fp);
            }
            break;

        case FTP_CMD_GET:
            linklist_insert(&head,in_cmd->args);
            split_string(in_cmd->args, filename);
            length = get_length(filename);
            printf("length = %d\n",length);

            fp = fopen(filename, "r");
            if (fp==NULL) {
                perror("fopen\n");
                exit(-1);
            } else {
                printf("fopen success\n");
            }
            if (NULL != fp) {
                ret = fread(out_cmd->data, 1, length, fp);
                out_cmd->data_lenth = ret;
                log_write("fread ret %d,eof %d\n", ret, feof(fp));
                fclose(fp);
            }
            get_md5(filename,out_cmd->md5);

            break;

        case FTP_CMD_PUT:
            linklist_insert(&head,in_cmd->args);
            filename[0]='+';
            split_string(in_cmd->args,&filename[1]);
            FILE *fp=fopen(filename,"w");
            if(fp !=NULL){
            int ret =fwrite(in_cmd->data,1,in_cmd->data_lenth,fp);
            log_write("fwrite ret %d,filename %s,data_lenth %ld\n",
                    ret,filename,in_cmd->data_lenth);
            fclose(fp);
            }
            get_md5(filename,md5sum);
            if(memcmp(md5sum,in_cmd->md5,32)!=0){
            printf("md5 different\nbefore md5:%s\n,after md5:%s\n",in_cmd->md5,md5sum);
            remove(filename);
            }else{
            printf("md5 same\n");
            }
            break;

        case FTP_CMD_QUIT:
            linklist_insert(&head,in_cmd->args);
            g_running=0;
           log_write("g_running=0\n"); 
            break;

        case FTP_CMD_CD:
            linklist_insert(&head,in_cmd->args);
            if(split_string(in_cmd->args,filename)<0){
                return;
            }
            ret = chdir(filename);
            log_write("chdir ret:%d",ret);
            break;
        case FTP_CMD_HIST:
            linklist_get_cmd(head,out_cmd->data);
            break;
        default:
            break;
    }
}
int main(int argc, char **argv) {

    int s_fd;
    int c_fd;
    int ret;
    char readbuf[128] = {0};
    struct sockaddr_in s_addr;
    struct sockaddr_in c_addr;
    memset(&s_addr, 0, sizeof(struct sockaddr_in));
    memset(&c_addr, 0, sizeof(struct sockaddr_in));

    struct Msg *msg_send = NULL;
    struct Msg *msg_recv = NULL;
    msg_send = (struct Msg *)malloc(sizeof(struct Msg));
    msg_recv = (struct Msg *)malloc(sizeof(struct Msg));

    // 1.socket
    s_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (s_fd == -1) {
        perror("socket");
        exit(-1);
    }

    int on = 1;
    setsockopt(s_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
    // 2.bind
    s_addr.sin_family = AF_INET;
    s_addr.sin_port = 8889;
    s_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));

    // 3.listen
    listen(s_fd, 10);
    // 4.accept
    int clen = sizeof(struct sockaddr_in);

    g_running=1;

    while (g_running) {
        log_create("server.txt");
        c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &clen);

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

        printf("server connected\n");
        //读取用户用户密码
        struct Auth auth;
        ret = recv(c_fd,&auth,sizeof(struct Auth),0);
        log_write("%s %s\n",auth.username,auth.password);
        //获取本地用户密码
        struct Auth server;
        FILE *fp=fopen("user","r");
        if (fp !=NULL){
         fscanf(fp,"%s %s",server.username,server.password);
         log_write("server %s %s\n",server.username,server.password);
         fclose(fp);
        }
        //校验用户密码
        if(0!=memcmp(auth.username,server.username,strlen(server.username))||
           0!=memcmp(auth.password,server.password,strlen(server.password))){
        auth.cmd = FTP_CMD_ERROR;
        log_write("auth failed\n");
        }
        ret = send(c_fd,&auth,sizeof(struct Auth),0);
        log_write("auth failed\n");

        if(FTP_CMD_ERROR==auth.cmd){
        return -1;
        }

        while (g_running) {
            ret = recv(c_fd, msg_recv, sizeof(struct Msg), 0);
            log_write("recv %d\n", ret);
            //处理客户端命令
            memset(msg_send, 0, sizeof(struct Msg));

            handle_cmd(msg_recv, msg_send);
            //发送
            ret = send(c_fd, msg_send, sizeof(struct Msg), 0);
            log_write("send %d\n", ret);
        }
        log_destroy();
    }

    return 0;
}

   client.c

#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <stdlib.h>
#include "log.h"
#include "msg.h"
#include "split.h"

enum FTP_CMD get_cmd(char *buf, struct Msg *msg) {
    char *ptr_args;
    if (0 == memcmp(buf, "ls", 2)) {
        return FTP_CMD_LS;
    }
    return FTP_CMD_ERROR;
}

int handle_user_input(struct Msg *msg_send) {
    char buf[32];
    int ret;
    long length;
    enum FTP_CMD cmd;
    printf("please input\n");
    // wait
    fgets(buf, 32, stdin);  // scanf遇到空格会被打断,所以改用fgets;

    //判断buf的值是否含有ls,如果有cdm=0
    if (0 == memcmp(buf, "ls", 2)) {
        cmd = FTP_CMD_LS;
    } else if (0 == memcmp(buf, "get", 3)) {
        cmd = FTP_CMD_GET;
    } else if (0 == memcmp(buf, "put", 3)) {
        cmd = FTP_CMD_PUT;
        char filename[32];
        if (split_string(buf, filename) < 0) {
            log_write("filename not find");
            return -1;
        }
        length = get_length(filename);
        if(length<0||length>sizeof(msg_send->data)){
            log_write("get_length failed,length %ld\n",length);
            return -1;
        }
        log_write("length = %d\n", length);
        printf("%ld\n",length);

        get_md5(filename,msg_send->md5);

        FILE *fp = fopen(filename, "r");
        if (NULL != fp) {
            msg_send->data_lenth = fread(msg_send->data, 1, length, fp);
            log_write("fread %d\n", msg_send->data_lenth);
            printf("%ld\n",msg_send->data_lenth);
            fclose(fp);
        } else {
            log_write("filename not find,%s", filename);
            return -1;
        }
    }else if(0 == memcmp(buf,"quit",4)){
        cmd=FTP_CMD_QUIT;
    }else if(0 == memcmp(buf,"cd",2)){
        cmd=FTP_CMD_CD;
    }else if(0 == memcmp(buf,"history",7)){
        cmd=FTP_CMD_HIST;
    }else {
        cmd = FTP_CMD_ERROR;
    }

    if (cmd == FTP_CMD_ERROR) {
        return -1;
    }
    msg_send->cmd = cmd;
    strcpy(msg_send->args, buf);
    return 0;
}

int main(int argc, char **argv) {
    struct Auth auth;
    struct Msg *msg_send = NULL;
    struct Msg *msg_recv = NULL;
    msg_send = (struct Msg *)malloc(sizeof(struct Msg));
    msg_recv = (struct Msg *)malloc(sizeof(struct Msg));

    log_create("client.txt");
    
    int ret;
    int c_fd;
    struct sockaddr_in c_addr;
    memset(&c_addr, 0, sizeof(struct sockaddr_in));
    char md5sum[64];
    // 1.socket
    c_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (c_fd == -1) {
        perror("socket");
        exit(-1);
    }
    // 2.connect
    c_addr.sin_family = AF_INET;
    c_addr.sin_port = 8889;
    c_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    if (connect(c_fd, (struct sockaddr *)&c_addr, sizeof(struct sockaddr_in)) ==
        -1) {
        perror("connect");
        exit(-1);
    }
    printf("username:");
    scanf("%s",auth.username);
    printf("password:");
    scanf("%s",auth.password);
    auth.cmd =FTP_CMD_AUTH;
    //发送给客户端验证
    ret=send(c_fd,&auth,sizeof(struct Auth),0);
    log_write("send ret %d\n",ret);

    //判断用户名密码是否正确
    ret=recv(c_fd,&auth,sizeof(struct Auth),0);
    if(FTP_CMD_ERROR == auth.cmd){
        printf("username or password error\n");
        return -1;
    }
    printf("welcome!\n");
    getchar();
    sleep(1);

    while (1) {

        if(handle_user_input(msg_send)<0){
            continue;
        }

        //发送
        ret = send(c_fd, msg_send, sizeof(struct Msg), 0);
        log_write("send ret %d\n", ret);

        //接收
        ret = recv(c_fd, msg_recv, sizeof(struct Msg), 0);
        log_write("recv ret %d\n", ret);
        if (msg_recv->cmd == FTP_CMD_LS) {
            printf("%s\n", msg_recv->data);
            printf("----------------------\n");
        } else if (msg_recv->cmd == FTP_CMD_GET) {
            char filename[32];
            filename[0]='_';
            printf("1%s\n",msg_send->args);
            split_string(msg_send->args,&filename[1]);
            FILE *fp = fopen(filename, "w");
            if (fp != NULL) {
                ret = fwrite(msg_recv->data, 1, msg_recv->data_lenth, fp);
                 log_write("fwrite ret %d\n",ret);
                fclose(fp);
            }
            get_md5(filename,md5sum);
            if(memcmp(md5sum,msg_recv->md5,32)!=0){
            log_write("md5 different\nmd5sum:%s\nmsg_recv->md5:%s\n",md5sum,msg_recv->md5);
                printf("md5 diffrent,get failed\n");
                remove(filename);
            }else{
            printf("get success\n");
            }
            printf("----------------------\n");
        }else if (msg_recv->cmd ==FTP_CMD_PUT){
            printf("put success\n");
            printf("-----------------------\n");
        
        }else if(msg_recv->cmd == FTP_CMD_QUIT){
            printf("Good bye~~\n");
            break;
        } else if(msg_recv->cmd ==FTP_CMD_HIST){
            printf("%s\n",msg_recv->data);
            printf("-----------------------\n");
        }
    }

    log_destroy();
    return 0;
}

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值