FTP项目总结

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 .可以看到修改了那几行的代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值