1.项目概述
1.1项目概述
- 本项目基于linux系统编程实现,使用了linux文件操作与网络编程等技术
- 采用C/S模式设计,服务器与客户端可相互发送任意大小文件,支持一些常用指令查看文件,如
ls
,cd
,pwd
等
1.2大体实现方法
- 由于需要传输大文件,因此需要将文件分包传输
- 本项目采用UDP协议传输文件,由于UDP是面向无连接,不可靠传输,因此需要我们手动进行包管理
- 在发送端将每个包编号,接收方每接收一个包就会发送确认信息给发送方,发送方在接收到确认消息后才会发送下一个包,否则就会重发
- 每组包附带crc8校验,确保传输过程无差错
1.3完整项目地址
- https://gitee.com/meiyangyang945/ftp/tree/master/
2.项目实现细节
2.1crc8校验算法
- 关于crc8校验算法,网上有很多大神的博客,此处参考
2.2通用结构体
- 为保证收发双方指令交互的方便,在此封装了指令结构体
/*枚举,保存指令类型*/
typedef enum
{
ls = 1,
lls,
pwd,
lpwd,
cd,
lcd,
get,
put,
help,
quit
}te_cmd;
/*指令交互包*/
typedef struct cmd_msg
{
te_cmd cmd; /*指令类型*/
char buff[BUF_SIZE]; /*指令附带的额外内容,如put a.out的a.out*/
}ts_cmd_msg;
typedef struct package_msg
{
int id; /*包ID*/
int buff_size; /*包大小*/
unsigned char crc8; /*包crc校验码*/
int is_resend; /*是否重发标志位*/
}ts_package_msg;
typedef struct package
{
struct package_msg pmsg;
char data_buf[PACK_SIZE];/*数据包具体传输内容*/
}ts_package;
2.3服务器细节实现
2.1.1主函数
int main(void)
{
char cmd_nnum[1024] = {0};
ts_cmd_msg cmd_pack;
char clientIPV4[16];
struct sockaddr_in sin;
// socklen_t sin_len = sizeof(sin);
struct sockaddr_in client_msg;
socklen_t client_msg_len = sizeof(client_msg);
int b_reuse = 1;
int fd = -1;
if((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
perror("create socket failed in:socket():12");
exit(-1);
}
/*允许绑定地址快速重用 */
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(int));
bzero(&sin, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);
sin.sin_addr.s_addr = htonl(INADDR_ANY);
/*绑定*/
if(bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
{
perror("bind failed in:bind():23");
exit(-1);
}
printf("server init ok\n");
while (1)
{
bzero(&client_msg, sizeof(client_msg));
/*阻塞等待客户端指令*/
if(recvfrom(fd, &cmd_pack, sizeof(cmd_pack), 0, (struct sockaddr *)&client_msg, &client_msg_len) < 0)
{
perror("recv msg failed in:recvfrom():30");
break;
}
/*网络字节序转本地字节序*/
if(!inet_ntop(AF_INET, (void *)&client_msg, clientIPV4, sizeof(client_msg)))
{
perror("can not know client mseeage in:inet_ntop()");
exit(-1);
}
printf("Recive from(%s:%d) cmd is:%d\n", clientIPV4, ntohs(client_msg.sin_port), cmd_pack.cmd);
/*解析指令*/
if(enum_to_cmd(&cmd_pack, cmd_nnum, fd, &client_msg) < 0)
{
printf("parse command failed in:enum_to_cmd()\n");
}
/*不用回复客户端的指令*/
if(cmd_pack.cmd == get || cmd_pack.cmd == lls || cmd_pack.cmd == lpwd || cmd_pack.cmd == lcd || cmd_pack.cmd == put)
{
bzero(&cmd_pack, sizeof(cmd_pack));
}
else
{
resend_cmd_pack:
//printf("send buff is:%s\n", cmd_pack.buff);/*DEBUG*/
/*回复客户端消息*/
if((sendto(fd, &cmd_pack, sizeof(cmd_pack), 0, (struct sockaddr *)&client_msg, client_msg_len)) < 0)
{
perror("send faied in:sendto()");
goto resend_cmd_pack;
}
bzero(&cmd_pack, sizeof(cmd_pack));
}
}
return 0;
}
2.1.2解析指令函数
int enum_to_cmd(ts_cmd_msg *cmd_pack_local, char *s, int fd, struct sockaddr_in *cli_msg)
{
printf("cmd type is %d\n", cmd_pack_local->cmd);
switch (cmd_pack_local->cmd)
{
case ls:
strcpy(s, "ls");
if(get_cmd_res(cmd_pack_local, s) < 0)
{
printf("get cmd res failed in:get_cmd_res()\n");
return -1;
}
break;
case pwd:
strcpy(s, "pwd");
if(get_cmd_res(cmd_pack_local, s) < 0)
{
printf("get cmd res failed in:get_cmd_res()\n");
return -1;
}
break;
case cd:
printf("dir is %s\n", cmd_pack_local->buff);
if(chdir(cmd_pack_local->buff) < 0)
{
perror("change dir failed in:chdir()");
return -1;
}
if(get_cmd_res(cmd_pack_local, "pwd") < 0)
{
printf("get cmd res failed in:get_cmd_res()\n");
return -1;
}
break;
case lls:
case lpwd:
case lcd:
break;
case get:
if(!access(cmd_pack_local->buff, F_OK))
{
sendto(fd, cmd_pack_local, sizeof(*cmd_pack_local), 0, (struct sockaddr *)cli_msg, sizeof(*cli_msg));
printf("will send file\n");
serv_send_file_process(fd, cli_msg, cmd_pack_local);
}
else
{
strcpy(cmd_pack_local->buff, "N");
sendto(fd, cmd_pack_local, sizeof(*cmd_pack_local), 0, (struct sockaddr *)cli_msg, sizeof(*cli_msg));
printf("No such file or directory\n");
}
//strcpy(cmd_pack_local->buff, "send done\n");
break;
case put:
printf("will recv file\n");
serv_recv_file_process(fd, cli_msg, cmd_pack_local);
break;
default:
return -1;
}
return 0;
}
2.1.3获取指令执行结果
#include
/*
功能:调用fork()产生子进程,然后在子进程中执行参数command的指令
参数:
参数1:指令
参数2:'r'代表读取,'w'代表写入,如果'r',那么调用进程读进command的标准输出,
如果为'w',那么调用进程写到command的标准输入
返回值:成功则返回文件指针,否则返回NULL
*/
FILE *popen(const char *command, const char *type);
int get_cmd_res(ts_cmd_msg *cmd_pack_local, char *s)
{
int rsize = 0;
printf("cmd is %s\n", s);/*DEBUG*/
FILE *dir = popen(s, "r");
if(dir == NULL)
{
perror("fail to open dir in:popen()");
return -1;
}
bzero(cmd_pack_local->buff, BUF_SIZE);
if((rsize = fread(cmd_pack_local->buff, sizeof(char), BUF_SIZE, dir)) < 0)
{
perror("get cmd res failed in:fread()");
return -1;
}
//printf("popen buff is:%s\n", cmd_pack_local->buff);/*DEBUG*/
pclose(dir);
return rsize;
}
2.1.4服务器发数据
int serv_send_file_process(int fd, struct sockaddr_in *cli_msg, ts_cmd_msg *cmd_pack_local)
{
socklen_t client_msg_len = sizeof(*cli_msg);
char cli_ip[16] = {0};
if(!inet_ntop(AF_INET, (void *)cli_msg, cli_ip, sizeof(*cli_msg)))
{
perror("can not know client mseeage in:inet_ntop()");
exit(-1);
}
printf("start send to %s:%d\n", cli_ip, ntohs(cli_msg->sin_port));
FILE *file;
//file = fopen("test.gif", "rb");
file = fopen(cmd_pack_local->buff, "rb");
if(file == NULL)
{
perror("file open failed");
return -1;
}
printf("open file success\n");
int pack_id = 1;
int check_id = 0;
int pack_size = 0;
int send_size = 0;
ts_package pack;
ts_package_msg check_msg;
bzero(&pack, sizeof(pack));
/*每次读取PACK_SIZE个字节*/
while ((pack_size = fread(pack.data_buf, sizeof(char), PACK_SIZE, file)) > 0)
{
pack.pmsg.id = pack_id;
pack.pmsg.buff_size = pack_size;
pack.pmsg.crc8 = cal_crc_table(pack.data_buf, pack_size);
pack.pmsg.is_resend = 0;
/*重发标签*/
resend:
if((send_size = sendto(fd, &pack, sizeof(pack), 0, (struct sockaddr *)cli_msg, client_msg_len)) < 0)
{
perror("send faied in:sendto()");
goto resend;
}
printf("sending pack %d, size %d Byte\n", pack.pmsg.id, send_size);
/*接收客户端反馈信息*/
recvfrom(fd, &check_msg, sizeof(check_msg), 0, (struct sockaddr *)cli_msg, &client_msg_len);
/*包错误。重发*/
if(check_msg.is_resend == 1)
{
check_id = check_msg.id;
printf("pack %d err\n", check_id);
goto resend;
}
else
{
pack_id++;
bzero(&pack, sizeof(pack));
}
}
/*发送结束标志,即空的结构体*/
sendto(fd, &pack, 0, 0, (struct sockaddr *)cli_msg, client_msg_len);
printf("send done...\n");
fclose(file);
return 0;
}
2.1.5服务器接收数据
int serv_recv_file_process(int fd, struct sockaddr_in *cli_msg, ts_cmd_msg *cmd_pack_local)
{
FILE *file;
file = fopen(cmd_pack_local->buff, "wb");
if(file == NULL)
{
perror("open file failed in:fopen()");
}
printf("open file success\n");
int recv_len = 0;
int check_pack_id = 1;
ts_package pack;
unsigned char check_crc;
ts_package_msg check_msg;
bzero(&pack, sizeof(pack));
socklen_t cli_msg_len = sizeof(*cli_msg);
/*接收数据包*/
while((recv_len = recvfrom(fd, &pack, sizeof(pack), 0, (struct sockaddr *)cli_msg, &cli_msg_len)) > 0)
{
//printf("in while\n");
printf("recving %d pack, size %d Byte\n", pack.pmsg.id, recv_len);
check_crc = cal_crc_table(pack.data_buf, pack.pmsg.buff_size);
/*检测数据包顺序是否发送正常*/
if(check_pack_id == pack.pmsg.id)
{
/*crc8校验*/
if(pack.pmsg.crc8 == check_crc)
{
/*成功接收包,发送反馈信息*/
check_msg.id = check_pack_id++;
check_msg.is_resend = 0;
fwrite(pack.data_buf, sizeof(char), pack.pmsg.buff_size, file);
bzero(&pack, sizeof(pack));
sendto(fd, &check_msg, sizeof(check_msg), 0, (struct sockaddr *)cli_msg, cli_msg_len);
}
else
{
printf("crc8 check err\n");
check_msg.id = check_pack_id;
/*将错误标志置为1*/
check_msg.is_resend = 1;
/*清空错误数据包*/
bzero(&pack, sizeof(pack));
/*发送错误信息*/
sendto(fd, &check_msg, sizeof(check_msg), 0, (struct sockaddr *)cli_msg, cli_msg_len);
}
}
else
{
printf("pack id err\n");
check_msg.id = check_pack_id;
check_msg.is_resend = 1;
bzero(&pack, sizeof(pack));
sendto(fd, &check_msg, sizeof(check_msg), 0, (struct sockaddr *)cli_msg, cli_msg_len);
}
}
printf("recv done\n");
fclose(file);
//bzero(cmd_pack_local, sizeof(*cmd_pack_local));
return 0;
}
2.4客户端细节实现
2.4.1主函数
int main(int argc, char ** argv)
{
int port = -1;
int fd = -1;
ts_cmd_msg cmd_pack;
struct sockaddr_in cin;
socklen_t cin_len = sizeof(cin);
// char buff[64] = {0};
// int ret = -1;
if(argc != 3)
{
tips(argv[0]);
exit(-1);
}
port = atoi(argv[2]);
if(port < 5000)
{
tips(argv[0]);
exit(-1);
}
/*创建socket */
if((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
perror("socket");
exit(-1);
}
/*填充结构体,即目标服务器IP和端口 */
bzero(&cin, sizeof(cin));
cin.sin_family = AF_INET;
cin.sin_port = htons(port);
if(inet_pton(AF_INET, argv[1], (void *)&cin.sin_addr.s_addr) != 1)
{
perror("inet_pton");
exit(-1);
}
puts("UDP client start ok");
bzero(&cmd_pack, sizeof(cmd_pack));
while (1)
{
char input_cmd[50] = {0};
int re_cmd = 0;
printf(">");
if((fgets(input_cmd, 50, stdin)) == NULL)
{
perror("fgets");
}
char *s = strstr(input_cmd, "\n");
strcpy(s, "\0");/*去换行符*/
if((re_cmd = cmd_to_enum(&cmd_pack, input_cmd)) < 0)
{
printf("err cmd\n");
}
else if(re_cmd > 0)
{
break;
}
switch (cmd_pack.cmd)
{
case get:
sendto(fd, &cmd_pack, sizeof(cmd_pack), 0, (struct sockaddr *)&cin, cin_len);
recvfrom(fd, &cmd_pack, sizeof(cmd_pack), 0, (struct sockaddr *)&cin, &cin_len);
if(!strcmp(cmd_pack.buff, "N"))
{
printf("No such file or directory\n");
}
else
{
cli_recv_file_process(fd, &cin, &cmd_pack);
}
break;
case put:
if(!access(cmd_pack.buff, F_OK))
{
sendto(fd, &cmd_pack, sizeof(cmd_pack), 0, (struct sockaddr *)&cin, cin_len);
cli_send_file_process(fd, &cin, &cmd_pack);
}
else
{
printf("No such file or directory\n");
}
break;
case lls:
case lpwd:
case lcd:
break;
default:
/*发送指令*/
sendto(fd, &cmd_pack, sizeof(cmd_pack), 0, (struct sockaddr *)&cin, cin_len);
bzero(&cmd_pack, sizeof(cmd_pack));
/*接收消息*/
recvfrom(fd, &cmd_pack, sizeof(cmd_pack), 0, (struct sockaddr *)&cin, &cin_len);
printf("%s", cmd_pack.buff);
//printf("%s\n", cmd_pack.buff);
break;
}
}
return 0;
}
2.4.2指令解析函数
int cmd_to_enum(ts_cmd_msg *cmd_pack_local, char *s)
{
char cmd[50] = {0};
strncpy(cmd, s, 50);
//printf("cmd is %s\n", cmd);/*DEBUG*/
if(!strcmp("ls", cmd))
{
cmd_pack_local->cmd = ls;
return 0;
}
if(!strcmp("lls", cmd))
{
system("ls");
cmd_pack_local->cmd = lls;
return 0;
}
if(!strcmp("pwd", cmd))
{
cmd_pack_local->cmd = pwd;
return 0;
}
if(!strcmp("lpwd", cmd))
{
system("pwd");
cmd_pack_local->cmd = lpwd;
return 0;
}
if(!strncmp(cmd, "cd ", strlen("cd ")))
{
char *s = strstr(cmd, "cd ");
// printf("input:%s", s+strlen("cd "));
cmd_pack_local->cmd = cd;
strcpy(cmd_pack_local->buff, s+strlen("cd "));
return 0;
}
if(!strncmp(cmd, "lcd ", strlen("lcd ")))
{
char *s = strstr(cmd, "lcd ");
// printf("input:%s", s+strlen("cd "));
cmd_pack_local->cmd = lcd;
chdir(s+strlen("lcd "));
system("pwd");
return 0;
}
if(!strncmp(cmd, "get ", strlen("get ")))
{
char *s = strstr(cmd, "get ");
// printf("input:%s", s+strlen("cd "));
cmd_pack_local->cmd = get;
strcpy(cmd_pack_local->buff, s+strlen("get "));
// recv_file_process(fd, serv_msg, cmd_pack_local);
return 0;
}
if(!strncmp(cmd, "put ", strlen("put ")))
{
char *s1 = strstr(cmd, "put ");
cmd_pack_local->cmd = put;
strcpy(cmd_pack_local->buff, s1+strlen("put "));
return 0;
}
if(!strcmp("quit", cmd))
{
return 1;
}
return -1;
}
2.4.3客户端接收数据
int cli_recv_file_process(int fd, struct sockaddr_in *serv_msg, ts_cmd_msg *cmd_pack_local)
{
FILE *file;
file = fopen(cmd_pack_local->buff, "wb");
if(file == NULL)
{
perror("open file failed in:fopen()");
}
printf("open file success\n");
int recv_len = 0;
int check_pack_id = 1;
ts_package pack;
unsigned char check_crc;
ts_package_msg check_msg;
bzero(&pack, sizeof(pack));
socklen_t serv_msg_len = sizeof(*serv_msg);
while((recv_len = recvfrom(fd, &pack, sizeof(pack), 0, (struct sockaddr *)serv_msg, &serv_msg_len)) > 0)
{
//printf("in while\n");
printf("recving %d pack, size %d Byte\n", pack.pmsg.id, recv_len);
check_crc = cal_crc_table(pack.data_buf, pack.pmsg.buff_size);
if(check_pack_id == pack.pmsg.id)
{
if(pack.pmsg.crc8 == check_crc)
{
/*成功接收包,发送反馈信息*/
check_msg.id = check_pack_id++;
check_msg.is_resend = 0;
fwrite(pack.data_buf, sizeof(char), pack.pmsg.buff_size, file);/*(待补全)错误处理*/
bzero(&pack, sizeof(pack));
sendto(fd, &check_msg, sizeof(check_msg), 0, (struct sockaddr *)serv_msg, serv_msg_len);
}
else
{
printf("crc8 check err\n");
check_msg.id = check_pack_id;
check_msg.is_resend = 1;
bzero(&pack, sizeof(pack));
sendto(fd, &check_msg, sizeof(check_msg), 0, (struct sockaddr *)serv_msg, serv_msg_len);
}
}
else
{
printf("pack id err\n");
check_msg.id = check_pack_id;
check_msg.is_resend = 1;
bzero(&pack, sizeof(pack));
sendto(fd, &check_msg, sizeof(check_msg), 0, (struct sockaddr *)serv_msg, serv_msg_len);
}
}
fclose(file);
printf("recv done\n");
//bzero(cmd_pack_local, sizeof(*cmd_pack_local));
return 0;
}
2.4.4客户端发送数据
int cli_send_file_process(int fd, struct sockaddr_in *serv_msg, ts_cmd_msg *cmd_pack_local)
{
socklen_t serv_msg_len = sizeof(*serv_msg);
printf("start send to server\n");
FILE *file;
//file = fopen("test.gif", "rb");
file = fopen(cmd_pack_local->buff, "rb");
if(file == NULL)
{
perror("file open failed");
return -1;
}
printf("open file success\n");
int pack_id = 1;
int check_id = 0;
int pack_size = 0;
int send_size = 0;
ts_package pack;
ts_package_msg check_msg;
bzero(&pack, sizeof(pack));
while ((pack_size = fread(pack.data_buf, sizeof(char), PACK_SIZE, file)) > 0)
{
pack.pmsg.id = pack_id;
pack.pmsg.buff_size = pack_size;
pack.pmsg.crc8 = cal_crc_table(pack.data_buf, pack_size);
pack.pmsg.is_resend = 0;
resend:
if((send_size = sendto(fd, &pack, sizeof(pack), 0, (struct sockaddr *)serv_msg, serv_msg_len)) < 0)
{
perror("send faied in:sendto()");
goto resend;
}
printf("sending pack %d, size %d Byte\n", pack.pmsg.id, send_size);
recvfrom(fd, &check_msg, sizeof(check_msg), 0, (struct sockaddr *)serv_msg, &serv_msg_len);
if(check_msg.is_resend == 1)
{
check_id = check_msg.id;
printf("pack %d err\n", check_id);
goto resend;
}
else
{
pack_id++;
bzero(&pack, sizeof(pack));
}
}
sendto(fd, &pack, 0, 0, (struct sockaddr *)serv_msg, serv_msg_len);
bzero(cmd_pack_local, sizeof(*cmd_pack_local));
printf("send done...\n");
fclose(file);
return 0;
}
2.5Makefile
TAR1 = server
TAR2 = client
TAR3 = crc8
CC := gcc
SRC = $(wildcard ./Src/*.c)
OBJS = $(patsubst %.c, %.o, $(wildcard ./Src/*.c))
CFLAGS = -c -Wall -I Inc
all:$(TAR1) $(TAR2)
$(TAR1):$(OBJS)
$(CC) ./Src/$(TAR1).o ./Src/$(TAR3).o -o $(TAR1)
$(TAR2):$(OBJS)
$(CC) ./Src/$(TAR2).o ./Src/$(TAR3).o -o $(TAR2)
%.o:%.c
$(CC) $(CFLAGS) $^ -o $@
.PHONY:
clean_all:
rm $(OBJS) $(TAR1) $(TAR2)