ftp文件传输协议

FTP文件传输协议

何为文件传输协议

解:文件传输时用于在网络上进行文件传输的一套标准协议,它在OSI模型的第七层上工作。简而言之,即用于连接服务端与客户端。
基本模型

在这里插入图片描述

Socket服务器和客户端的开发步骤

1.创建套接字
2.为套接字添加信息IP地址和端口号
3.监听网络连接
4.监听到有客户端接入-〉接收一个连接
5.数据交互
6.关闭套接字,断开连接

写一个日志函数

log.h
#ifndef LOG_H
#define LOG_H
void log_create(const char *filename);   //创建前缀
void log_destroy();   //销毁/关闭文件
void log_write(const char *filename,...);	//写入文件
#endif


log.c
#include <stdio.h>
#include <stdarg.h>
#include "log.h"
FILE *g_log = NULL;	//定义一个全局变量
void log_create(const char *filename){		//创建前缀
	g_log = fopen(filename,"a+");
	if(g_log==NULL){
		printf("fopen %s failed\n",filename);
	}
}
void log_destroy(){		//关闭文件
	fclose(g_log);
	g_log = NULL;		//防止变成野指针
}
void log_write(const char *format,...){		//写入文件,以下命令可通过查找va_list 找到其具体用法 
	va_list args;		//定义va_list变量
	va_start(args,format);		//创建
	vfprintf(g_log,format,args);		//输出字符
	va_end(args);		//关闭
	fflush(g_log);		//强制写入文件

}

注:fflush()函数不能忘,否则有可能导致写入失败

编写一个测试单元

log_unisttest.c
#include <stdio.h>
#include <string.h>
#include "log.h"
int main(){
	log_create(1.txt);
	log_write("hello\n");
	log_write("world\n");
	log_destroy();
	return 0;
}

命令结构体函数

msg.h
#ifndef MSG_H
#define MSG_H
#define SERVER_PORT 8888
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];		//md5校验值
    int data_length;		//实际长度
    char data[5000];
};

struct AUTH{
    enum FTP_CMD cmd;
    char username[12];
    char password[12];

};
#endif 
分割字符串及获取文件的实际长度函数
utils.h
#ifndef  UTILS_H
#define UTILS_H
int split_string(char *in_str,char *out_str);		//分割字符串,以空格为分隔符		in_str 待分割的字符串     out_str 去掉命令后的第一个参数
long get_length(char *filename);		//获取文件的实际长度
void get_md5(char *filename,char *md5sum);		//获取文件的md5
#endif
utils.c
#include "utils.h"
#include <stdio.h>
#include <string.h>
int split_string(char *in_str, char *out_str) {
    char *one;
    char *_i;
    one = strstr(in_str, " ");		//从in_str查找第一个空格
    _i = strstr(in_str, "\n");		//找到\n
    while (1) {
        if (one == NULL || *one == '\0') {			//假设没有找到空格
            return -1;
        }
        one += 1;			//移动到下一个空格
        if (one != " ") {		//判断当前字符是否为空格
            break;
        }
    }
  
    strncpy(out_str, one, _i - one);		//不拷贝\n
    out_str[_i - one] = '\0';				//设置文件结尾'\0'
    return 0;
}
long get_length(char *filename) {
    FILE *fp = fopen(filename, "r");
    if (fp != NULL) {
        int ret = fseek(fp, 0, SEEK_END); 			//移动到文件末尾,计算长度
        if(ret<0){
            log_write("fseek\n");
            return -1;
        }
        long length = ftell(fp);			//获取长度
        //fseek(fp, 0, SEEK_SET);
        fclose(fp);
        return length;
    }
    return -1;
}
void get_md5(char *filename,char *md5sum){
    char cmd[64];		//转换成命令md5sum 1.txt
    sprintf(cmd,"md5sum %s",filename);
    char md5[64];		//popen调用md5sum 获取文件的md5
    FILE *p = popen(cmd,"r");
    if(p!=NULL){
        int ret = fread(md5,1,sizeof(md5),p);
        log_write("fread:%d,md5:%s",ret,md5);
        pclose(p);
    }
    sscanf(md5,"%s",md5sum);		//分割md5的值

    return ;
}
/*
int main(){
    char *in_str;
    char *out_str;
    char *filename;
    split_string(in_str,out_str);
    get_length(filename);
    return 0;
}*/
链表

链表头文件

Linklist.h
#ifndef LINKLIST_H
#define LINKLIST_H
struct Linklist{
    char cmd[64];     //history历史纪录
    struct Linklist *next;    //next piont下一个节点

};
void Linklist_insert(struct Linklist **head,char *cmd);		//采用头插法插入节点
void Linklist_print(struct Linklist *head);		
void Linklist_get_cmd(struct Linklist *head,char *obuf);		//获取所有的命令
#endif

链表

Linklist.c
#include<stdio.h>
#include<stdlib.h>
#include "Linklist.h"
#include<string.h>

void Linklist_insert(struct Linklist **head,char *cmd){
    struct Linklist *node = (struct Linklist*)malloc(sizeof(struct Linklist));		//申请节点并初始化
    node->next=NULL;
    strcpy(node->cmd,cmd);
    node->next=*head;
    *head=node;

}

void Linklist_print(struct Linklist *head){

    struct Linklist *p=head;
    while(p!=NULL){
        printf("%s\n",p->cmd);
        p=p->next;
    
    }

}

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

}

链表的测试单元

#include<stdio.h>
#include"Linklist.h"

int main(int argc,char **argv){
    struct Linklist *h=NULL;
    Linklist_insert(&h,"ls");
    Linklist_insert(&h,"cd dir");
    Linklist_insert(&h,"get 1.txt");
    Linklist_print(h);
    char buf[64]={0};
    Linklist_get_cmd(h,buf);
    printf("%s\n",buf);

    return 0;
}

设置你的密码

passwd
abc 123

服务端

写一个server服务端

server.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include "msg.h"
#include "log.h"
#include "utils.h"
#include "Linklist.h"
int g_running;

void handle_cmd_1(struct Msg *in_cmd, struct Msg *out_cmd) {
    FILE *fd = NULL;
    int ret;
    char filename[32];
    static struct Linklist *hist = NULL;		//局部静态变量,其值一直存在 
    log_write("cmd: %d,args: %s\n", in_cmd->cmd, in_cmd->args);		//in_cmd从网络读取数据,全部写入日志,用于调试
    out_cmd->cmd = in_cmd->cmd;		//返回的命令
    Linklist_insert(&hist,in_cmd->args);		//保存的历史命令
    if (FTP_CMD_LS == in_cmd->cmd) {		//判断in_cmd的命令类型
        fd = popen(in_cmd->args, "r");
        if (NULL != fd) {
            ret = fread(out_cmd->data, 1, sizeof(out_cmd->data), fd);
            log_write("fread:%d,eof:%d,args:%s\n", ret, feof(fd),
                      out_cmd->data);
            pclose(fd);
        }
    } else if (FTP_CMD_GET == in_cmd->cmd) {
        if (split_string(in_cmd->args, filename) < 0) {		//分割字符
            out_cmd->cmd = FTP_CMD_ERROR;
            log_write("filename not found\n");
            return;
        }
        fd = fopen(filename, "r");
        if (fd != NULL) {

            ret = fread(out_cmd->data, 1, out_cmd->data_length, fd);

            log_write("fread:%d,eof:%d,args:%s\n", ret, feof(fd),
                      out_cmd->data);
            pclose(fd);
        }
    } else if (FTP_CMD_PUT == in_cmd->cmd) {
        filename[0] = '+';		//获取文件名,并以+开头
        split_string(in_cmd->args, &filename[1]);
  
        fd = fopen(filename, "w");		//把文件内容写入文件中

        if (fd != NULL) {
            ret = fwrite(in_cmd->data, 1, in_cmd->data_length, fd);
            log_write("fwrite:%d,filename:%s,data_length:%d\n", ret, filename,
                      in_cmd->data_length);
            fclose(fd);
        }
        char md5[64];		//对比客户端的md5
        get_md5(filename, md5);
        if (memcmp(md5, in_cmd->md5, 32) != 0) {		//若md5不同,则删掉服务端的文件
            remove(filename);
            log_write("client:%s,server:%s\n", in_cmd->md5, md5);
        }
    } else if (FTP_CMD_QUIT == in_cmd->cmd) {
        log_write("quit\n");
        g_running = 0;

    } else if (FTP_CMD_CD == in_cmd->cmd) {
        char dirname[32];
        if (split_string(in_cmd->args, dirname) < 0) {		//获取文件夹的名字
            return;
        }
        ret = chdir(dirname);		//切换目录
        log_write("chdir:%d", ret);
    }else if(FTP_CMD_HIST==in_cmd->cmd){
    Linklist_get_cmd(hist,out_cmd->data);
    }
}

int main(int argc, char **argv) {
    int listenfd;
    int ret;
    int sock;
    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("server.txt");
    listenfd = socket(AF_INET, SOCK_STREAM, 0);		//socket
    if (listenfd < 0) {
        log_write("socket failed,ret:%d\n", listenfd);
        return -1;
    }
    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = SERVER_PORT;
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    int on = 1;		//允许地址重用
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

    ret = bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
    if (ret < 0) {
        log_write("bind failed,ret:%d\n", ret);
        return -1;
    }

    ret = listen(listenfd, 0);		//监听模式
    if (ret < 0) {
        log_write("listen failed,ret:%d\n", ret);
        return -1;
    }
    sock = accept(listenfd, NULL, 0);		//返回已经完成3次握手的socket
    if (ret < 0) {
        log_write("accept failed,ret:%d\n", ret);
        return -1;
    }

    log_write("hello client,connect.\n");		//成功建立TCP连接
    struct AUTH auth;		//读取用户名和密码
    ret = recv(sock, &auth, sizeof(struct AUTH), 0);
    log_write("username:%s,password:%s\n", auth.username, auth.password);
    struct AUTH server;		//获取本地用户名和密码
    FILE *p = fopen("passwd", "r");
    if (p != NULL) {
        fscanf(p, "%s %s", server.username, server.password);
        fclose(p);
        log_write("%s %s\n",server.username,server.password);
    }
    if (memcmp(auth.username, server.username, strlen(server.username)) != 0 ||
        memcmp(auth.password, server.password, strlen(server.password)) != 0) {		//校验用户名密码
        auth.cmd = FTP_CMD_ERROR;
        log_write("failed\n");
    }

    ret = send(sock, &auth, sizeof(struct AUTH), 0);		//发送校验结果
    log_write("send:%d\n", ret);
    if (auth.cmd == FTP_CMD_ERROR) {
        return -1;
    }
    g_running = 1;		//运行服务端 
    while (g_running) {
        ret = recv(sock, msg_recv, sizeof(struct Msg), 0);		//接收到客户端命令
        log_write("recv:%d\n", ret);
        handle_cmd_1(msg_recv, msg_send);		//处理客户端命令
        ret = send(sock, msg_send, sizeof(struct Msg), 0);		//发送处理结果给客户端
        log_write("send:%d\n", ret);
    }
    log_destroy();
    return 0;
}


客户端

一个客户端

client.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "log.h"
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include "msg.h"
#include "utils.h"
int g_running;
enum FTP_CMD get_cmd(char *buf,struct Msg *msg) {		//把用户输入的字符串转为FTP_CMD
	char *ptr_args;
	if(0==memcmp(buf,"ls",2)){
		return FTP_CMD_LS;
	}
    return FTP_CMD_ERROR;
}

int handle_usr_1(struct Msg *msg_send) {
    char buf[32];
    enum FTP_CMD cmd;
    fgets(buf, 32, stdin);		//读取命令
    log_write("%s\n", buf);		//打印调试信息
    //检测命令
    if (memcmp(buf, "ls", 2) == 0) {
        cmd = FTP_CMD_LS;

    } else if (memcmp(buf, "get", 3) == 0) {
        cmd = FTP_CMD_GET;

    } else if (memcmp(buf, "put", 3) == 0) {
        cmd = FTP_CMD_PUT;

    } else if(memcmp(buf,"quit",4)==0){
        cmd = FTP_CMD_QUIT;
        g_running=0;
    }else if(memcmp(buf,"cd",2)==0){
        cmd = FTP_CMD_CD;
    }else if(memcmp(buf,"hist",4)==0){
        cmd = FTP_CMD_HIST;
    }
    else {
        cmd = FTP_CMD_ERROR;
    }
    if (cmd == FTP_CMD_ERROR) {		//无效命令,返回失败
        return -1;
    }
    msg_send->cmd = cmd;		//初始化msg_send
    strcpy(msg_send->args, buf);
    return 0;
}

int main(int argc, char **argv) {
    int sock;
    int ret;
    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");
    struct sockaddr_in serveraddr;
    sock = socket(AF_INET, SOCK_STREAM, 0);		//建立socket
    memset(&serveraddr, 0, sizeof(serveraddr));		//初始化服务端地址
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = SERVER_PORT;
    serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");

 
    ret = connect(sock, (struct sockaddr *)&serveraddr, sizeof(serveraddr));		//连接
    if (ret < 0) {		//连接失败则退出程序
        log_write("connect failed,ret:%d\n", ret);
        return -1;
    }
    struct AUTH auth;		//输入用户名密码
    printf("username:\n");
    scanf("%s",auth.username);
    printf("password:\n",auth.password);
    scanf("%s",auth.password);
    auth.cmd=FTP_CMD_AUTH;
    ret = send(sock,&auth,sizeof(struct AUTH),0);		//发送给服务端验证
    log_write("auth.send:%d\n",ret);
    ret = recv(sock,&auth,sizeof(struct AUTH),0);		//判断用户名密码是否正确
    log_write("auth.recv:%d\n",ret);
    if(auth.cmd==FTP_CMD_ERROR){
        printf("error\n");
        return -1;
    
    }
    printf("good job!\n");
    g_running=1;
    while (g_running) {		
        // memset(msg_send,sizeof(msg_send));
        char filename[28];
        FILE *p;
        if (handle_usr_1(msg_send) < 0) {		//等待用户输入
            continue;
        }
        ret = send(sock, msg_send, sizeof(struct Msg), 0);		//发送
        log_write("send:%d\n", ret);
        ret = recv(sock, msg_recv, sizeof(struct Msg), 0);		//接收
        log_write("recv:%d\n", ret);

        log_write("data:%s\n", msg_recv->data);
        if (FTP_CMD_HIST==msg_recv->cmd||FTP_CMD_LS == msg_recv->cmd) {
            printf("%s\n", msg_recv->data);
        } else if (FTP_CMD_GET == msg_recv->cmd) {
            filename[0] = '_';
            split_string(msg_send->args, &filename[1]);

            p = fopen(filename, "w");
            if (p != NULL) {

                ret = fwrite(msg_recv->data, 1,msg_recv->data_length, p);
                log_write("fwrite:%d", ret);
                fclose(p);
                
            }
        } else if (FTP_CMD_PUT == msg_recv->cmd) {

            get_md5(filename,msg_send->md5);
            if (split_string(msg_send->args, filename) < 0) {
                msg_recv->cmd = FTP_CMD_ERROR;
                log_write("filename miss\n");
                return -1;
            }
            p = fopen(filename, "r");
            if (p != NULL) {
                msg_send->data_length = fread(msg_send->data, 1, sizeof(msg_send->data), p);
                log_write("fread:%d\n",msg_send->data_length);
                pclose(p);
            }
        }else if(FTP_CMD_QUIT==msg_recv->cmd){
            printf("see you next time!\n");
            break;
        }

    
    }

 
    log_destroy();

    return 0;
}

注意区别

fopen与popen

fopen 函数是打开一个文件,在当前目录下打开文件并对其进行操作。
popen函数通过创建一个管道,调用fork()产生一个子进程,执行一个shell以运行命令来开启一个进程。

fgets

char *fgets(char *s,int size,FILE *stream);
fgets函数用于从文件指针中读取一行,其功能是从stream流中读取size个字符存储到字符指针变量s所指向的内存空间。

ftell

ftell函数用于得到文件位置指针当前相对于文件首的偏移字节数。
使用fseek函数后再调用ftell函数就能够非常容易地确定文件的当前位置。

fscanf

从一个流中执行格式化输入,fscanf遇到空格和换行时结束。

sscanf

取指定长度的字符串

体验

存在问题:编写过程容易将客户端与服务端混淆,不够细心,常常因为一些细节引起段错误,导致程序运行不出来。对于一些函数的理解不够,以致于编写困难。
收获:终于get一个既能git用法,恩!虽然懂得其用法,但有时意识不够到位,需加强。每添加一个功能莫忘先测试在添加,以免出现不可逆转的错误!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值