概念:
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:局部静态变量(它的值一直存在)

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

  1. 读书百遍,真的能其义自见吗?

    从小我们就接受 仙人教诲:读书百遍,其义自见的真理....?请看我们可爱的百度文库的教诲: 中国有一句古话:"少年不读书,老来空白首."世间万物,皆属身外,惟有书能够渗心入骨地擦拭 ...

  2. 读书千遍,其义自见 古人诚不欺我

    2009年1月22日 周四 记得小时候父亲的朋友喜欢教我背唐诗宋词,每当我懵懂不知其意时他总是会告诉群我熟读唐诗三百首不会作诗也会吟.于是我背了一堆堆的唐诗宋词,当时看来除了锻炼出比较好的记忆力和炫耀 ...

  3. 学习心得,书读百遍其义自见!

    书读百遍其义自见! 在小学课文上第一次见这句话的时候,还觉得没道理,尤其是像语文课本里的文言文,我当时觉得读一百遍,也还是那样,似懂非懂混混沌沌. 直到长大后,近些日子学习vue,我发现这句格言真是有 ...

  4. 读书百遍,其义自见,要不得

    等到自己开始写博客,才发现文字的歧义是随处可在的,这也是为什么我们在看别人写的教程的时候总是要走很多弯路.这篇文章汇总如何正确的提问,如何写出更少歧义的文字. 一:常见逻辑谬误 逻辑谬误的概念 逻辑谬 ...

  5. 书读百遍其义自见 - 六大原则 23种设计模式

    作者: 西魏陶渊明 博客: https://blog.springlearn.cn/ 天下代码一大抄, 抄来抄去有提高, 看你会抄不会抄! 一.设计模式 设计模式一般分为三类:创建型模式.结构型模式. ...

  6. 书读百遍其义自见-------2017--05--27读书篇

    什么是 HTML? HTML 是用来描述网页的一种语言. HTML 指的是超文本标记语言 (Hyper Text Markup Language) HTML 不是一种编程语言,而是一种标记语言 (ma ...

  7. 品读鸿蒙HDF架构系列 | 读码百遍其义自见

    不同读码人都会有各自读代码的习惯和切入点,HDF是Harmony Driver Fundation的缩写,实际上便是鸿蒙形成的一套管理设备驱动的框架模型,也被称为"驱动子系统".本 ...

  8. 大端(Big Endian)与小端(Little Endian)详解

    大端(Big Endian)与小端(Little Endian)简介 /// 1. 你从哪里来? 端模式(Endian)的这个词出自JonathanSwift书写的<格列佛游记>.这本书根 ...

  9. 转载:独立思考能力吞噬

    其实想写这篇文章很久了,因为随着科技和互联网的迅速发展,人们的独立思考能力可能正在慢慢消失.独立思考的能力是一个人在工作和生活中最重要的一种能力,有独立思考能力的人和无独立思考能力的人绝对是产生人生价 ...

最新文章

  1. 正由另一进程使用,因此该进程无法访问此文件。
  2. 2002高教社杯---A车灯线光源的优化设计
  3. apache配置网络驱动器
  4. 爱说说技术原理:与TXT交互及MDataTable对Json的功能扩展(二)
  5. android 通知显示时间,android:在特定时间显示通知?
  6. VMware虚拟机安装centos
  7. 【电子信息复试】考研复试常考问题——软件工程
  8. 大学c语言下上机考试题,计算机考试二级C语言上机试题下[5]
  9. php系统维护,软件系统维护主要包含什么
  10. jQuery 表格实现
  11. 获取Class对象方式
  12. Linux 的 diff 命令
  13. 【spring boot】注解@ApiParam @PathVariable @RequestParam三者区别
  14. 苹果4是android吗,时至2020年,苹果手机还有这几个优势,让安卓毫无“招架之力”...
  15. python删除某个文件夹_Python 实现删除某路径下文件及文件夹
  16. Discuz论坛架设从零起步之三
  17. ecplise git修改提交信息_Eclipse中Git的使用说明之一:使用Git上传新项目到远程仓库...
  18. ZOJ 3256 Tour in the Castle(插头DP-按行递推—矩阵)
  19. c语言笔试题大题带答案,c语言常见笔试题及答案
  20. stm32f030移植到stm32f072

热门文章

  1. 在word中制作书签
  2. opencv 将多帧图像合成为视频 cv2.VideoWriter()
  3. 写一份高逼格的简历,有哪些新技能呢?
  4. php基于BS模式的物业管理系统设计与实现毕业设计源码290918
  5. java加密工作模式None_simple1234
  6. HTML5 浏览器Notification Win10推送消息
  7. OpenGL快速入门 1
  8. UserWarning: semaphore_tracker: There appear to be 4 leaked semaphores to clean up at shutdown
  9. myeclipse mysql连接_怎么连接myeclipse与mysql数据库
  10. uni-app rtm插件集成指南及常见问题--iOS