从无到有的FTP(读书百遍其义自见)

概念:
FTP项目,文件传输协议,通过网络传输文件。
知识储备:
要先学会网络编程,至少知道服务端与客户端之间如何传输。
在这里插入图片描述

框架:
高屋建瓴
在这里插入图片描述

架构

  • 客户端

  • 服务端
    基本功能

  • ls,显示服务端文件

  • get, 下载服务端的文件
    计算文件实际的长度
    分割参数
    1,多个空格
    2.\n
    3.结尾的’\0’
    4.strstr或strtok
    在这里插入图片描述
    在这里插入图片描述

    服务端
    1.打开文件
    2.读取文件,保存在msg->data
    3.关闭文件
    客户端
    1.打开文件
    2.写入文件,保存在msg->data
    3.关闭文件
    get test.c

  • put,上传本地文件到服务端

  • quit,退出
    1.用户输入quit
    2.客服端处理quit命令
    3.服务端处理quit命令

  • cd,切换目录
    1.用户输入 cd dir
    2.服务端处理dir
    chdir

高级功能

  • 用户名密码验证
    在这里插入图片描述
    1.在客服端连上后,就发送用户名密码给客户端
    2.服务端对比用户密码是否一样
    不一样的话,服务端断开连接

在这里插入图片描述

  • get,put传输的文件,进行md5校验
    1.ms5sum
    2.fscanf

在这里插入图片描述

  • hist,现实历史记录,链表
    1.服务端要记录每一次命令
    2.识别并发送hist
    3.服务端获取所有命令,并发送
    4.客户端显示结果
    模块划分

  • 链表.c/.h,没有头结点的单链表
    1.插入
    在这里插入图片描述

    2.获取所有历史命令
    3.遍历
    4.单元测试
    (具体看链表的创建与遍历的方式)
    在这里插入图片描述

在这里插入图片描述
加粗样式

  • 日志模块,fopen();
    1.单元测试(需要单元测试的地方,是因为比较独立,出错了很难查找)
    2.函数列表
    log_create,打开一个文件
    log_destroy,关闭一个文件
    log_write,写入文件,需要打开一个文件(记日志很重要,可以很快找出错误的原因)
  • 公用的消息,msg.h,结构体
    在这里插入图片描述
    1.如何发送结构体
    2.如何接收结构体
    打印结构体,把每一个变量一个一个打印出来
    printf(%d, msg->cmd);
    消息定义

添砖加瓦
git的用处:
以前代码是正常的,现在代码崩溃了。
eg:
修改了client.c
不小心删除了文件
git checkout–client.c utils.c utils.h

现在增加一个put,实现不了
看到了修改了哪几行代码
git diff

查看已经修改的提交
git show

每次实现一个小功能记得:
git add .
git commit

代码实现:


client.c:


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

把用户输入的字符串转为FTP_CMD

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];
enum FTP_CMD cmd;

**// 等待用户输入**
printf("input cmd:\n");
fgets(buf, 32, stdin);
log_write("%s\n", buf);

**// buf转为FTP_CMD**
cmd = get_cmd(buf, msg_send);
log_write("cmd %d\n", cmd);
if (cmd == FTP_CMD_ERROR) {
    return -1;
}

// 初始化结构体struct Msg
msg_send->cmd = cmd;
strcpy(msg_send->args, buf);

return 0;

}

/**

  • 读取用户输入,初始化msg_send

  • msg_send

  • return 0成功,-1失败
    */
    int handle_user_input2(struct Msg *msg_send)
    {
    char buf[32];
    enum FTP_CMD cmd;

    // 读取命令
    fgets(buf, 32, stdin);

    // 从键盘读取的数据,全部写入日志
    // 打印调试信息
    log_write("%s", buf);

    // 检测这是什么命令?
    // 不支持其他命令,只支持ls命令
    // 识别到ls命令

    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_string2(buf, filename) < 0) {
    log_write(“filename not find”);
    return -1;
    }
    // 把文件内容写入data
    // #define NULL 0
    FILE *fp = fopen(filename, “r”);
    if (NULL != fp) {
    // 设置data_length
    msg_send->data_length = fread(msg_send->data, 1, sizeof(msg_send->data), fp);
    log_write(“fread %d”, msg_send->data_length);
    fclose(fp);
    } else {
    log_write(“filename not find, %s”, filename);
    return -1;
    }
    } else {
    cmd = FTP_CMD_ERROR;
    }

    // 命令不支持,返回失败
    if (cmd == FTP_CMD_ERROR) {
    return -1;
    }

    // 初始化msg_send
    msg_send->cmd = cmd;
    strcpy(msg_send->args, buf);

    // 返回成功
    return 0;
    }

int main(int argc, char **argv)
{
int ret;
int sock;
struct sockaddr_in serveraddr;
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”);
log_write(“recv \n”);
// 1 创建socket
sock = socket(AF_INET, SOCK_STREAM, 0);
// 2. 初始化服务端地址, ip, port
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”);

// 3. 连接connect
ret = connect(sock, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
if (ret < 0) {
// 链接服务端失败,退出程序
log_write(“connect failed, ret %d\n”, ret);
return -1;
}

// 成功建立tcp连接
log_write(“connect server sucess\n”);

while(1) {

// 1. 等待用户输入, 初始化msg_send
if (handle_user_input2(msg_send) < 0) {
continue;
}
// 2. 发送
ret = send(sock, msg_send, sizeof(struct Msg), 0);
log_write(“send ret %d\n”, ret);
// 3. 接收
ret = recv(sock, msg_recv, sizeof(struct Msg), 0);
log_write(“recv ret %d\n”, ret);
log_write(“cmd %d\n”, msg_recv->cmd);
log_write(“data %s\n”, msg_recv->data);
if (FTP_CMD_LS == msg_recv->cmd) {
printf("%s", msg_recv->data);
} else if (FTP_CMD_GET == msg_recv->cmd) {
// get file.txt
// file.txt
// file.txt
char filename[32];
filename[0] = '
’;
split_string2(msg_send->args, &filename[1]);
FILE *fp = fopen(filename, “w”);
if (fp != NULL) {
ret = fwrite(msg_recv->data, 1, msg_recv->data_length, fp);
log_write(“fwrite ret %d”, ret);
fclose(fp);
}
}
}

log_destroy();
return 0;
}

server.c
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h> /* See NOTES /
#include <errno.h>
#include “msg.h”
#include “log.h”
#include “utils.h”
/
*

  • @brief 处理客户端的命令in_cmd,并返回处理结果out_cmd

  • @param in_cmd

  • @param out_cmd
    */
    void handle_cmd(struct Msg *in_cmd, struct Msg *out_cmd)
    {
    FILE *fp = NULL;
    int ret;

    out_cmd->cmd = in_cmd->cmd;

    switch(in_cmd->cmd) {
    case FTP_CMD_LS:
    fp = popen(in_cmd->args, “r”);
    if (NULL != fp) {
    ret = fread(out_cmd->data, 1, sizeof(out_cmd->data), fp);
    // 一次读取5000个字节,读取一次,因为实际大小小于5000,导致EOF
    //ret = fread(out_cmd->data, sizeof(out_cmd->data), 1, fp);
    log_write(“fread ret %d, eof %d, data %s\n”,
    ret, feof(fp), out_cmd->data);
    pclose(fp);
    }
    break;
    default:
    break;
    }
    }

/**

  • @brief 处理客户端的命令in_cmd,把结果写入out_cmd

  • @param in_cmd

  • @param out_cmd
    */
    void handle_cmd2(struct Msg *in_cmd, struct Msg *out_cmd)
    {
    // in_cmd 从网络读取度数据,全部写入日志,用于调试
    log_write(“cmd %d, args %s\n”, in_cmd->cmd, in_cmd->args);

    // 返回的命令
    out_cmd->cmd = in_cmd->cmd;

    // 判断in_cmd的命令类型
    if (FTP_CMD_LS == in_cmd->cmd) {
    FILE *fp = popen(in_cmd->args, “r”);
    if (fp != NULL) {
    int ret = fread(out_cmd->data, 1, sizeof(out_cmd->data), fp);
    log_write(“fread ret %d, %s”, ret, out_cmd->data);
    pclose(fp);
    }
    } else if (FTP_CMD_GET == in_cmd->cmd) {
    char filename[32];
    // 分割字符
    if (split_string2(in_cmd->args, filename) < 0) {
    out_cmd->cmd = FTP_CMD_ERROR;
    log_write(“filename not find\n”);
    return;
    }

    FILE *fp = fopen(filename, “r”);
    if (fp != NULL) {
    // 一次性读取5000字节,如果文件小于5000字节,整个文件就读取结束
    int ret = fread(out_cmd->data, 1, sizeof(out_cmd->data), fp);
    out_cmd->data_length = ret;
    log_write(“fread ret %d, eof %d\n”, ret, feof(fp));
    fclose(fp);
    } else {
    out_cmd->cmd = FTP_CMD_ERROR;
    log_write(“filename not find %s\n”, filename);
    }
    } else if (FTP_CMD_PUT == in_cmd->cmd) {
    // 获取文件名,以+开头
    char filename[32];
    filename[0] = ‘+’;
    split_string2(in_cmd->args, &filename[1]);
    // 把文件内容写入文件
    FILE *fp = fopen(filename, “w”);
    if (fp != NULL) {
    int ret = fwrite(in_cmd->data, 1, in_cmd->data_length, fp);
    log_write(“fwrite ret %d, filename %s, data_length %d”,
    ret, filename, in_cmd->data_length);
    fclose(fp);
    }
    }
    }

int main(int argc, char **argv)
{
struct sockaddr_in serveraddr;
int listenfd;
int sock;
int ret;
struct Msg *msg_recv = NULL;
struct Msg *msg_send = NULL;

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

log_create(“server.txt”);

// socket
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd < 0) {
log_write(“socket failed, ret %d\n”, listenfd);
return -1;
}

// bind端口,SERVER_PORT
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = SERVER_PORT;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);

// 配置socket
// 允许地址重用
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, errno %d %s\n”, ret, errno, strerror(errno));
return -1;
}
// listen,启用监听模式
ret = listen(listenfd, 0);
if (ret < 0) {
log_write(“listen failed ,ret %d\n”, ret);
return -1;
}
// accept,返回已经完成3次握手的socket
sock = accept(listenfd, NULL, 0);
if (ret < 0) {
log_write(“accept failed ,ret %d\n”, ret);
return -1;
}

// 成功建立TCP连接
log_write(“client connect.\n”);

while (1) {
// 1. 接收到客户端命令
ret = recv(sock, msg_recv, sizeof(struct Msg), 0);
log_write(“recv %d\n”, ret);

// 2. handle cmd处理客户端命令
memset(msg_send, 0, sizeof(struct Msg));
handle_cmd2(msg_recv, msg_send);

// 3. 发送处理结果给客户端
ret = send(sock, msg_send, sizeof(struct Msg), 0);
log_write(“send %d\n”, ret);
}

log_destroy();
return 0;
}

log.c:

#include “log.h”
#include <stdio.h>
#include <stdarg.h>

FILE *g_log = NULL;

//创建,前缀
void log_create(const char *filename)
{
g_log = fopen(filename, “a+”);
if (NULL == g_log) {
printf(“fopen %s failed\n”, filename);
}
}

//销毁
void log_destroy()
{
fclose(g_log);
// 不想变为野指针
g_log = NULL;
}

//写入
void log_write(const char *format, …)
{
// 1 定义va_list变量
va_list args;

// 2 创建
va_start(args, format);
vfprintf(g_log, format, args);

// 3 销毁
va_end(args);

// 强制写入文件
fflush(g_log);
}

log.h

#ifndef LOG_H
#define LOG_H

//创建,前缀
void log_create(const char *filename);
//销毁
void log_destroy();
//写入
void log_write(const char *format, …);
// 参数可变
// printf(“read error, ret %d, %s”, ret, msg);
// printf(“read error, ret %d”, ret);
// printf(“read error”);

#endif // LOG_H

log_unittest:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include “log.h”

int main(int argc, char **argv)
{
log_create();

log_write(“hello\n”);
log_write(“hello %d\n”, 3);

log_destroy();
return 0;
}

Makefile(为了编译的方便,详情请看Make的博文)

client:
gcc client.c log.c -o client -g utils.c
server:
gcc server.c log.c -o server -g utils.c
test:
gcc linklist.c linklist_unittest.c -o linklist_unittest

log:
gcc log.c log_unittest.c -o log_unittest

clean:
rm server client linklist_unittest log_unittest *.txt

msg.h
#ifndef MSG_H
#define MSG_H

#define SERVER_PORT 8888

enum FTP_CMD {
// ls
FTP_CMD_LS = 0,
// get 下载
FTP_CMD_GET = 1,
// put 上传
FTP_CMD_PUT = 2,
// quit 断开连接byebye
FTP_CMD_QUIT = 3,
// cd 切换服务端目录
FTP_CMD_CD = 4,

// 无效的命令
FTP_CMD_ERROR,
};

struct Msg {
// 命令
enum FTP_CMD cmd;

// 命令行
char args[32];

// md5校验值
char md5[64];

// data的实际长度
int data_length;

// data
char data[5000];
};
#endif // MSG_H

utils.h
#include “utils.h”
#include <string.h>

// get file
// get file
int split_string2(char *in_str, char *out_str)
{
char *first;
char *_n;

// 1. 从in_str查找第一个空格
first = strstr(in_str, " ");

// 找到\n
_n = strstr(in_str, “\n”);

while (1) {
// 没有找到空格
if (NULL == first || *first == ‘\0’) {
return -1;
}

// 2. 移动到下一个字符
first += 1;

// 3. 判断当前字符是否空格
if (*first != ’ ') {
// 找到
break;
}
}

// client.c\n, 不拷贝\n
strncpy(out_str, first, _n - first);

// 设置文件结尾’\0’
out_str[_n-first] = ‘\0’;

return 0;
}

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

// 1. 从in_str查找第一个空格

// 找到\n

while (1) {
// 没有找到空格

// 2. 移动到下一个字符

// 3. 判断当前字符是否空格
}

// client.c\n, 不拷贝\n

// 设置文件结尾’\0’

return 0;
}

utils.h
#ifndef _UTILS_H
#define _UTILS_H

/**

  • @brief 分割字符串,以空格为分隔符
  • @param in_str 待分割的字符串
  • @param out_str 去掉命令后的第一个参数
  • @return 0成功,-1失败
    */
    // get, 支持
    // get file, 支持
    // get file1 file2, 不支持
    int split_string2(char *in_str, char *out_str);

#endif // UTILS_H

注意事项:
1.fgets()
读取一行

2.gdb之后,bt
#0
#1
#2
frame 1进入错误的序号

vfprintf有缓冲区,有时候写入,有时候不写入
fflush:强制写入

3.msg_send每次使用时候需要清空。

4.ctrl+z
fg
是切换前后台

5.strcat:拼接

6.static:局部静态变量(它的值一直存在)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值