1.功能介绍
- 日志模块
- ls(查看指令)
- get(下载指令)
- put(上传指令)
- get,put传输的文件,进行md5校验
- cd(切换目录指令)
- hist(历史纪录,利用链表)
- quit(退出)
首先是日志模块,通过日志,使我对于编程中出现的问题,并解决问题提供了很大的帮助,也极大提高了自身找bug的能力,把每一个有输出的值都写在日志里,对于调试程序至关重要。
代码如下: log.c 另log.h
··1 #include "log.h"
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <stdarg.h>
5
6 FILE *g_log=NULL; //全局文件变量
7
8 void log_creat(char *filename)
9 {
10 g_log = fopen(filename,"a+"); //打开文件
11 if(NULL == g_log){
12 printf("fopen %s failed!\n",filename);
13 exit(-1);
14 }
15 }
16
17 void log_desitory()
18 {
19 fclose(g_log); //关闭文件
20 g_log = NULL;
21 }
23 void log_write(const char *format, ...)
24 {
25 va_list args;
26 ...
27 //创建
28 va_start(args,format);
29 ....
30 //输出
31 vfprintf(g_log,format,args);
32
33 //销毁
34 va_end(args);
35
36 //强制写入文件
37 fflush(g_log);
38 }
接下来是实现ls功能,该功能相对来说还是比较容易实现,只需要调用系统函数就行了,可以使用popen函数来获取该命令,并返回结果,在这之前必须要处理该命令,可以为每一个命令设置一个枚举值,通过枚举函数实现,枚举函数如下:
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_AUTH = 5,
// 历史纪律
FTP_CMD_HIST= 6,
// 无效的命令
FTP_CMD_ERROR,
};
struct Msg {
// 命令
enum FTP_CMD cmd;
// 命令行
char args[32];
// md5校验值
char md5[64];
// data的实际长度
int data_length;
// data数据
char data[5000];
};
在实现ls功能之前,要先将服务器和客户端连接起来,ls功能,大致是客户端向服务端发送ls命令,服务端接收并处理客户端发来的ls命令后,发送回客户端,客户端接收后将结果输出。
client连接server代码:
int main(int argc,char **argv)
99 {
100 int c_fd;
101 int ret;
102 char buf[128]={"你好!"};
103 struct sockaddr_in c_addr;.
104
105 struct Msg *send_msg;.
106 struct Msg *recv_msg;.
107
108 send_msg = (struct Msg *)malloc(sizeof(struct Msg));
109 recv_msg = (struct Msg *)malloc(sizeof(struct Msg));
110
111 log_creat("./client.txt");
112
113 memset(&c_addr,0,sizeof(c_addr));
114
115 //1.socket 创建
116 c_fd = socket(AF_INET,SOCK_STREAM,0);
117 if(c_fd == -1){
118 log_write("socket failed,%d\n",c_fd);
119 exit(-1);
120 }
121
122 //2.获取ip 和 port
123 c_addr.sin_family = AF_INET;
124 c_addr.sin_port = htons(8990);
125 //inet_aton("192.168.60.129",&c_addr.sin_addr);
126 c_addr.sin_addr.s_addr = htonl(INADDR_ANY);
127
128 //3.连接服务器
129 ret = connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockadd>
130 if(ret < 0){
131 log_write("connect failed, %d\n",ret);
132 return -1;
133 }
134 log_write("connect success!\n"); //连接成功
server代码:
int main(int argc, char **argv) {
232 int s_fd;
233 int c_fd;
234 int ret;
235 struct sockaddr_in s_addr;
236 struct sockaddr_in c_addr;
237
238 struct Msg *send_msg = NULL;
239 struct Msg *recv_msg = NULL;
240
241 send_msg = (struct Msg *)malloc(sizeof(struct Msg));
242 recv_msg = (struct Msg *)malloc(sizeof(struct Msg));
243
244 log_creat("./server.txt");
245
246 memset(&s_addr, 0, sizeof(s_addr));
247 memset(&c_addr, 0, sizeof(c_addr));
248
249 // 1.socket 创建
250 s_fd = socket(AF_INET, SOCK_STREAM, 0);
251 if (s_fd == -1) {
252 log_write("socket failed,%d\n", s_fd);
253 exit(-1);
254 }
255
256 // 2.bind,获取ip and port
257 s_addr.sin_family = AF_INET;
258 s_addr.sin_port = htons(8990);
259 // inet_aton("192.168.60.129",&s_addr.sin_addr);
260 s_addr.sin_addr.s_addr = htonl(INADDR_ANY);
261
262 //配置socket
263 //端口可重用
264 int flag = 8990;
265 setsockopt(s_fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));
266
267 ret = bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr>
268 if (ret < 0) {
269 log_write("bind failed, %d\n", ret);
270 return -1;
271 }
272
273 // 3.listen 启用监听模式
274 ret = listen(s_fd, 10);
275
276 // 4.accept 接收网络,返回已经完成3次握手的socket
277 int clen = sizeof(struct sockaddr);
278 c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &clen);
279 if (c_fd == -1) {
280 log_write("accept failed, %d\n", c_fd);
281 exit(-1);
282 } else {
283 log_write("accept connect success!\n"); //连接成功
284 }
客户端发送ls指令代码如下:
int handle_user_input(struct Msg *send_msg)
31 {
32 enum FTP_CMD cmd;
33 char str[128];
34 int ret;
35
36 fgets(str,128,stdin); //从输入流读取一个字符串
37 printf("input cmd:\n");
38 log_write("fgets:%s\n",str); //指令结果写入日志
39
40 if(0 == memcmp(str,"ls",2)){ //判断是否为LS命令
41 cmd = FTP_CMD_LS;
42 }
87 if(cmd == FTP_CMD_ERROR){
88 return -1;
89 }
90 //初始化send_msg
91 send_msg->cmd = cmd;...
92 strcpy(send_msg->args,str);
93
94 //返回成功
95 return 0;
96 }
server接收处理代码如下:
//表示为1开始接收发送
320 g_running = 1;
322 while (g_running) {
323 //接收
324 ret = recv(c_fd, recv_msg, sizeof(struct Msg), 0);
325 log_write("recv ret,%d\n", ret);
326
327 //初始化发送的数据
328 memset(send_msg, 0, sizeof(struct Msg));
329
330 //处理接受的命令
331 handle_cmd2(recv_msg, send_msg);
332
333 //发送
334 ret = send(c_fd, send_msg, sizeof(struct Msg), 0);
335 log_write("send ret,%d\n", ret);
336 }
//返回的命令
146 out_cmd->cmd = in_cmd->cmd;
147
148 if (FTP_CMD_LS == in_cmd->cmd) {
149 fp = popen(in_cmd->args, "r");
150 if (NULL != fp) {
151 //把从fp文件里读到的数据放到out_cmd->data里(相当于一个缓冲>
152 //,每次读一个字节,读5000个内容,在把内容传回去,发送给客户
153 ret = fread(out_cmd->data, 1, sizeof(out_cmd->data), fp);
154 log_write("fread ret %d, eof %d, data %s\n", ret, feof(fp),
155 out_cmd->data);
156 pclose(fp);
157 }
put命令是,客户端向服务端上传文件,客户端先读取要上传的文件,客户端接收后,将接收到的内容,创建一个文件,将内容写入到里面,即上传成功,get下载命令刚好相反,客户端向服务端发起get命令,服务端将要下载的文件读取完后,发送给客户端,客户端将接收到的内容,创建文件并把内容写入到文件里,即为下载成功,在发送的过程中,我们需要计算改文件的实际大小,和md5校验,把算好的值发送给上传和下载的对象,即为get和put操作,代码如下:
put命令:
client代码:
else if(0 == memcmp(str,"put",3)){
51 cmd = FTP_CMD_PUT;
52 char filename[32];
53 if(cut_string(str,filename) <0){
54 cmd = FTP_CMD_ERROR;
55 log_write("filename not find\n");
56 return -1;
57 }
58
59 //计算文件的实际大小
60 long length = get_length(filename);
61 if(length < 0 || length > sizeof(send_msg->data)){
62 log_write("get_length failed,length %d\n",length);
63 return -1;
64 }
....
66 //获取md5
67 get_md5(filename,send_msg->md5);
68 log_write("send_msg md5 %s\n",send_msg->md5);
69
70 FILE *fp;
71 fp =fopen(filename,"r");
72 if(NULL != fp){
73 ret = fread(send_msg->data,1,length,fp);
74 send_msg->data_length= ret;
75 log_write("fread ret %d , data_lenth %d\n",ret
76 ,send_msg->data_length);
77 printf("文件上传成功!\n");
78 fclose(fp);
79 }else{
80 log_write("filename not found,%s\n",filename);
81 return -1;
82 }
}else{
84 cmd = FTP_CMD_ERROR;
85 }
86
87 if(cmd == FTP_CMD_ERROR){
88 return -1;
89 }
90 //初始化send_msg
91 send_msg->cmd = cmd;...
92 strcpy(send_msg->args,str);
93
94 //返回成功
95 return 0;
96 }
while(1){
156 //等待用户输入,并处理cmd命令
157 if( handle_user_input(send_msg) < 0 ){
158 continue;
159 }
160
161 //5.send 发送
162 ret = send(c_fd,send_msg,sizeof(struct Msg),0);
163 log_write("send %d\n",ret);
164 log_write("send msg cmd %d\n",send_msg->cmd);
165
server代码:
//接收
324 ret = recv(c_fd, recv_msg, sizeof(struct Msg), 0);
325 log_write("recv ret,%d\n", ret);
326
327 //初始化发送的数据
328 memset(send_msg, 0, sizeof(struct Msg));
329
330 //处理接受的命令
331 handle_cmd2(recv_msg, send_msg);
332
接收完了,进入处理命令函数,
else if (FTP_CMD_PUT == in_cmd->cmd) {
192 filename[0] = '+';
193 //解析命令,获取文件名
194 cut_string(in_cmd->args, &filename[1]);
195 //打开文件
196 fp = fopen(filename, "w");
197 if (fp != NULL) {
198 ret = fwrite(in_cmd->data, 1, in_cmd->data_length, fp);
199 log_write("fwrite ret %d , filename %s , data_length %d\n",>
200 filename, in_cmd->data_length);
201 fclose(fp);
202 }
203 //获取服务器校验码
205 get_md5(filename, md5);
206 printf("data %s\n", in_cmd->data);
207 if (memcmp(md5, in_cmd->md5, 32) != 0) {
208 remove(filename);
209 log_write("client md5 %s,server md5 %s\n", in_cmd->md5, md5>
210 }
get命令相反。
总结:
在编程的过程中,出现了很多的问题,对于函数的用法比较不了解,在写put功能的时候,对于概念的理解还不是很到位,所以导致在写该代码的时候,出现了很多错误,正好在写这部分代码的时候,应用了之前学习的gdb,找出了很多的错误,并加以改正,还学习了git功能,对代码进行备份,以防代码误删,修改了客户端和服务端代码以后,之前实现好的功能也不用了,就可以利用git checkout --文件名,恢复之前备份好的代码,git show可以查看提交的代码,git diff .可以看到修改了那几行的代码。