FTP项目
这一周,我们运营网络编程的知识以及关于文件读写的相关操作等进行了了这次的FTP项目实战。我们做了查看目录操作 ls,从客户端上传文件到服务端操作put,从服务端下载文件到客户端操作get以及查看历史纪录操作hist和用户的登陆操作。
具体实现步骤如下:
ls查看目录操作:
server:
if(in_cmd->cmd==FTP_CMD_LS){
FILE *fp = popen(in_cmd->args,"r");
if(NULL!=fp){
int ret = fread(out_cmd->data,1,sizeof(out_cmd->data),fp);
log_write("fread ret %d %s\n",ret,out_cmd->data);
pclose(fp);
}
client:
if(FTP_CMD_LS == msg_recv->cmd){
printf("%s",msg_recv->data);
ls操作其实很简单,当客户端向服务端发出 ls指令,服务端检测指令正确后读取相应内容存储到发出指令变量out_cmd->data中,并重新发回给客户端,当客户端检测接受指令一致时,即打印出接收的内容即可完成ls操作。
get操作:
关于文件的下载操作,其实简单来说就是当服务端接受到来自客户端的get请求时,打开相应的文件,读取其中内容,将读取的内容发送给客户端,客户端再将其写入的过程,关键代码如下:
server:
if(in_cmd->cmd == FTP_CMD_GET){
char *filename;
filename=(char *)malloc(128);
if(split_string(in_cmd->args,filename)<0){
out_cmd->cmd = FTP_CMD_ERROR;
log_write("filename is not find\n");
return ;
}
FILE *fp=fopen(filename,"r");
if(fp != NULL){
int ret = fread(out_cmd->data,1,sizeof(out_cmd->data),fp);
out_cmd->data_leng = ret;
log_write("fread ret %d,%s\n",ret,out_cmd->data);
log_write("data_leng %d\n",out_cmd->data_leng);
fclose(fp);
}
client:
if(FTP_CMD_GET == msg_recv->cmd){
char *filename;
filename = (char *)malloc(128);
filename[0]='_';
split_string(msg_send->args,&filename[1]);
FILE *fp=fopen(filename,"w");
if(fp!=NULL){
int ret = fwrite(msg_recv->data,1,msg_recv->data_leng,fp);
log_write("fwrite ret %d\n",ret);
log_write("fwrite data is %s\n");
fclose(fp);
}
put:
文件的上传操作,即从客户端上传文件到服务端操作,跟get相反,即从客户端读取文件内容到服务端写入,具体实现代码如下:
server :
client
if(0 == memcmp(buf,"put",3)){
cmd=FTP_CMD_PUT;
char filename[32];
if(split_string(buf,filename)<0){
log_write("filename no find\n");
return -1;
}
FILE *fp=fopen(filename,"r");
if(fp!=NULL){
int ret=fread(msg_send->data,1,length,fp);
msg_send->data_leng=ret;
log_write("fread = %d",ret);
fclose(fp);
}
server:
if(in_cmd->cmd == FTP_CMD_PUT){
char *filename;
filename=(char *)malloc(128);
filename[0]='+';
split_string(in_cmd->args,&filename[1]);
FILE *fp=fopen(filename,"w");
if(NULL!=fp){
int ret = fwrite(in_cmd->data,1,in_cmd->data_leng,fp);
log_write("fwrite ret %d,filename %s data_leng %d\n",ret,fi>
fclose(fp);
}
hist:
历史纪录是依靠链表的知识来实现的,当有一条纪录进来后以头插法的方式插入纪录即可,具体代码如下:
插入链表操作
void linklist_insert(struct LinkList **head, char *cmd)
{
struct LinkList *node = (struct LinkList *)malloc(sizeof(struct LinkLis>
node->next = NULL;
strcpy(node->cmd, cmd);
node->next = *head;
*head = node;
}
打印历史纪录:
void linklist_printf(struct LinkList *head)
{
struct LinkList *p = head;
while (p != NULL) {
printf("%s\n", p->cmd);
p = p->next;
}
}
void linklist_get_cmd(struct LinkList *head, char *out_buf)
{
struct LinkList *p = head;
char buf[32];
while (p != NULL) {
// 在命令后加\n
sprintf(buf, "%s\n", p->cmd);
// 拷贝命令
strcat(out_buf, buf);
// 指向下一个节点
p = p->next;
}
}
server:
if (FTP_CMD_HIST == in_cmd->cmd) {
linklist_get_cmd(hist, out_cmd->data);
}
client:
if (FTP_CMD_HIST == msg_recv->cmd ) {
printf("%s", msg_recv->data);
}
用户等登陆操作:
首先创建一个存储用户名和密码的结构体Auth
struct Auth {
enum FTP_CMD cmd;
// 用户名
char username[32];
// 密码
char password[32];
};
验证用户密码操作很简单,当服务器与客户端连接成功后,在客户端提示用户输入用户名和密码,并将指令置为FTP_CMD_AUTH,发送给服务端,当服务端接收到后,打开服务端存储的相应文件内容,并验证从客户端发送来的账号密码和服务端的账号密码是否一致,如果一致,则提示登陆成功并将指令返回给客户端,如果失败,则将指令置为无效指令发送给客户端,当客户端接收到的是无效指令后,提示用户名与密码错误。具体实现步骤如下:
client
struct Auth auth;
// 输入用户名密码
printf("username:");
scanf("%s", auth.username);
printf("password:");
scanf("%s", auth.password);
auth.cmd = FTP_CMD_AUTH;
// 发送给服务端验证
ret = send(sock, &auth, sizeof(struct Auth), 0);
log_write("send ret %d", ret);
// 判断用户名密码是否正确
ret = recv(sock, &auth, sizeof(struct Auth), 0);
if (FTP_CMD_ERROR == auth.cmd) {
printf("username or password error\n");
return -1;
}
printf("well done\n");
server:
// 读取用户名密码
struct Auth auth;
ret = recv(sock, &auth, sizeof(struct Auth), 0);
log_write("%s %s\n", auth.username, auth.password);
// 获取本地用户名密码
struct Auth server;
FILE *fp = fopen("passwd", "r");
if (fp != NULL) {
fscanf(fp, "%s %s", server.username, server.password);
log_write("server %s %s\n", server.username, server.password);
fclose(fp);
}
.
// 校验用户名密码
if (0 != memcmp(auth.username, server.username, strlen(server.username)>
0 != memcmp(auth.password, server.password, strlen(server.password)>
// 不一样
auth.cmd = FTP_CMD_ERROR;
log_write("auth failed\n");
}
// 发送检验结果
ret = send(sock, &auth, sizeof(struct Auth), 0);
log_write("send %d\n", ret);
if (FTP_CMD_ERROR == auth.cmd) {
return -1;
}
在这次的FTP实验中,由于代码量较多,万一发生了错误也很难判断在哪发生了错误,不好调试,因此我学会了用日志去纪录我运行的每一条的结果,例如,我在进行get操作时,我发现并没有实现这个操作,于是我查看了日志,发现在从客户端向服务端发送时指令是有传过去的,可是当服务端发送给客户端结果时,竟然没有收到结果,因此我意识到了,应该是在服务端那出了问题,于是我看了下,发现我没有将接受到的指令赋予发送指令,等于发送指令为空。写入日志代码如下:
#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);
}