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