1.項目概述
1.1項目概述
- 本項目基于linux系統(tǒng)編程實現(xiàn),使用了linux文件操作與網(wǎng)絡編程等技術
- 采用C/S模式設計,服務器與客戶端可相互發(fā)送任意大小文件,支持一些常用指令查看文件,如
ls
,cd
,pwd
等
1.2大體實現(xiàn)方法
- 由于需要傳輸大文件,因此需要將文件分包傳輸
- 本項目采用UDP協(xié)議傳輸文件,由于UDP是面向無連接,不可靠傳輸,因此需要我們手動進行包管理
- 在發(fā)送端將每個包編號,接收方每接收一個包就會發(fā)送確認信息給發(fā)送方,發(fā)送方在接收到確認消息后才會發(fā)送下一個包,否則就會重發(fā)
- 每組包附帶crc8校驗,確保傳輸過程無差錯
1.3完整項目地址
2.項目實現(xiàn)細節(jié)
2.1crc8校驗算法
- 關于crc8校驗算法,網(wǎng)上有很多大神的博客,此處參考
2.2通用結構體
- 為保證收發(fā)雙方指令交互的方便,在此封裝了指令結構體
/*枚舉征椒,保存指令類型*/
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;
- 還定義了一個數(shù)據(jù)包結構體,用于保存拆分的數(shù)據(jù)包
typedef struct package_msg
{
int id; /*包ID*/
int buff_size; /*包大小*/
unsigned char crc8; /*包crc校驗碼*/
int is_resend; /*是否重發(fā)標志位*/
}ts_package_msg;
typedef struct package
{
struct package_msg pmsg;
char data_buf[PACK_SIZE];/*數(shù)據(jù)包具體傳輸內容*/
}ts_package;
2.3服務器細節(jié)實現(xiàn)
2.1.1主函數(shù)
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;
}
/*網(wǎng)絡字節(jié)序轉本地字節(jié)序*/
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解析指令函數(shù)
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獲取指令執(zhí)行結果
#include <stdio.h>
/*
功能:調用fork()產生子進程,然后在子進程中執(zhí)行參數(shù)command的指令
參數(shù):
參數(shù)1:指令
參數(shù)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服務器發(fā)數(shù)據(jù)
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個字節(jié)*/
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;
/*重發(fā)標簽*/
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);
/*包錯誤慰照。重發(fā)*/
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));
}
}
/*發(fā)送結束標志,即空的結構體*/
sendto(fd, &pack, 0, 0, (struct sockaddr *)cli_msg, client_msg_len);
printf("send done...\n");
fclose(file);
return 0;
}
2.1.5服務器接收數(shù)據(jù)
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);
/*接收數(shù)據(jù)包*/
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);
/*檢測數(shù)據(jù)包順序是否發(fā)送正常*/
if(check_pack_id == pack.pmsg.id)
{
/*crc8校驗*/
if(pack.pmsg.crc8 == check_crc)
{
/*成功接收包,發(fā)送反饋信息*/
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;
/*清空錯誤數(shù)據(jù)包*/
bzero(&pack, sizeof(pack));
/*發(fā)送錯誤信息*/
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客戶端細節(jié)實現(xiàn)
2.4.1主函數(shù)
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);
}
/*創(chuàng)建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:
/*發(fā)送指令*/
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指令解析函數(shù)
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客戶端接收數(shù)據(jù)
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)
{
/*成功接收包,發(fā)送反饋信息*/
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客戶端發(fā)送數(shù)據(jù)
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)