1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > 简单的网络文件传输工具(TCP连接)

简单的网络文件传输工具(TCP连接)

时间:2021-10-12 07:44:41

相关推荐

简单的网络文件传输工具(TCP连接)

一、项目描述

功能:通过网络传输,实现文件的跨设备传输,包含服务器和客户端。

服务器功能:等待客户端的连接,支持多客户端并发,根据客户端发送过来的命令,执行相应的操作,并向客户端发送器所需要的数据。

客户端功能:负责链接服务器后,向服务器发送请求,并等待服务器的回应,同时处理服务回应的数据。

命令设置:

1. ls

用来获取服务器目录下的文件信息(文件名)

2. get file

用来从服务器上获取file指定的文件

如果服务器存在该文件,则服务器回复文件内容

如果服务器不存在该文件,则回复错误码

3. put file

用来往服务器上上传file指定的文件

如果服务器愿意接收该文件,则后续发送文件内容

如果服务器不愿意接收该文件,则不发送

4. bye

用来告诉服务器,客户端即将断开连接

服务器收到bye之后,关闭与客户端的连接套接字,并结束该客户端处理分支

二、实现步骤

1. 利用socket接口编程搭建服务器和客户端;参考socket接口编程

2. 设计数据包格式:

(1)客户端发送的请求命令格式

参数:比如get file或者put file的时候都需要说明文件的名称,以及文件名字的长度

包头 pkg_len cmd_no arg_1 arg_2.... 包尾

包头:可以以0xC0作为包头(数据包中的第一个字节,规定每一个数据包都以0xC0

为开头),这个包头占1个字节,在实际应用中,为了保证包头的唯一性,一般会

设置多个字节。

pkg_len:占4个字节,表示这个数据包的总长度(不包括包头和包尾),以小端模式

进行存储(先存低字节)

cmd_no:占4个字节,表示这个数据包的命令号,小端模式存储

arg_1:由两部分组成

arg1_len 占4个字节,表示第一个参数的长度

arg1_data 占arg1_len个字节,表示参数的内容

arg_2:由两部分组成

arg2_len 占4个字节,表示第二个参数的长度

arg2_data 占arg2_len个字节,表示参数的内容

包尾:数据包的最后一个字节,每一个数据包都以0xC0作为结尾,占1个字节。

(2) 服务器回复客户端的命令格式

参数:包头 pkg_len cmd_no resp_len result res_conent 包尾

包头:可以以0xC0作为包头(数据包中的第一个字节,规定每一个数据包都以0xC0

为开头),这个包头占1个字节,在实际应用中,为了保证包头的唯一性,一般会

设置多个字节。

pkg_len:占4个字节,表示这个数据包的总长度(不包括包头和包尾),以小端模式

进行存储(先存低字节)

cmd_no:占4个字节,表示这个数据包的命令号,小端模式存储

resp_len:占4个字节,回复的内容的长度result + resp_conent

result:占1个字节,表示的是执行命令成功或者失败

resp_conent:

回复的内容

失败:

失败可以回一个错误码,这个错误码由程序猿自己定义。

此时回复的内容就是错误的原因。

成功:

ls:服务器主目录下所有的目录项的名称,每一个名称以空格隔开

get:先把文件的大小发送过去,下一次再把文件内容发送过去,占4个字节。

三、代码演示

(1)头文件ftp_protocol.h

#ifndef __FTP_PROTOCOL_H__#define __FTP_PROTOCOL_H__//命令号typedef enum ftp_cmd_no{FTP_LS = 1,FTP_GET,FTP_PUT,FTP_BYE,UNKOWN_CMD = 9999}FTP_CMD;#endif

(2)服务器代码

#include "head.h"#include "ftp_protocol.h"//服务器的主目录#define SERVER_PATH "/home/china/tftpboot"//标志位,标志着服务器是否退出//为0表示不退出,为1表示退出int Terminate = 0;unsigned char Cmd[][128] = {"ls","get","put","bye"};/*FTP:File Transmit Protocol文件传输协议FTP的服务端Usage : ./a.out <SERVER_IP> <SERVER_PORT>*//*根据IP地址字符串和端口号字符串创建监听套接字@返回值:成功返回监听套接字,失败返回-1*/int Create_Socket(char *ip,char *port){//1.创建套接字int sock = socket(AF_INET,SOCK_STREAM,0);if(sock == -1){perror("Create Socket Failed!");return -1;}//2.指定服务器(本机)的IP地址和端口号struct sockaddr_in local;local.sin_family = AF_INET;//指定协议族local.sin_port = htons(atoi(port));//指定网络程序的端口号local.sin_addr.s_addr = inet_addr(ip);//指定IP地址int ret = bind(sock,(struct sockaddr *)&local,sizeof(local));if(ret == -1){perror("Bind Failed");close(sock);return -1;}int on = 1;setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,(void *)&on,sizeof(on));//允许地址重用setsockopt(sock,SOL_SOCKET,SO_REUSEPORT,(void *)&on,sizeof(on));//允许端口重用//3.监听套接字ret = listen(sock,20);if(ret == -1){perror("Listen Failed");close(sock);return -1;}return sock;}/*服务器回复客户端LS请求*/void Resp_ls(int sock){//打开目录DIR *dir = opendir(SERVER_PATH);if(dir == NULL){perror("Opendir Failed");return ;}unsigned char filenames[4096] = {0};int r = 0;struct dirent *dirp = NULL;//读取目录项while(dirp = readdir(dir)){if(!strcmp(dirp->d_name,".") || !strcmp(dirp->d_name,"..")){continue;}r += snprintf(filenames + r,4096 - r,"%s ",dirp->d_name);}closedir(dir);//准备回复数据包unsigned char resp[8192] = {0};int i = 0;int pkg_len = 13 + strlen(filenames);int resp_len = 1 + strlen(filenames);FTP_CMD cmd_no = FTP_LS;resp[i++] = 0xC0;//包头resp[i++] = pkg_len & 0xFF;resp[i++] = (pkg_len >> 8) & 0xFF;resp[i++] = (pkg_len >> 16) & 0xFF;resp[i++] = (pkg_len >> 24) & 0xFF;//包长度以小端模式存储resp[i++] = cmd_no & 0xFF;resp[i++] = (cmd_no >> 8) & 0xFF;resp[i++] = (cmd_no >> 16) & 0xFF;resp[i++] = (cmd_no >> 24) & 0xFF;//命令号以小端模式存储resp[i++] = resp_len & 0xFF;resp[i++] = (resp_len >> 8) & 0xFF;resp[i++] = (resp_len >> 16) & 0xFF;resp[i++] = (resp_len >> 24) & 0xFF;//回复的内容的长度,以小端模式存储resp[i++] = 0;//回复的结果->假设成功strcpy(resp+i,filenames);i = i + strlen(filenames);resp[i++] = 0xC0;//包尾//把回复包发给客户端write(sock,resp,i);}/*服务器回复客户端GET请求将filename指定的文件发送给客户端*/int Resp_Get(int sock,char *filename){//回复包的数据格式://0xC0 pkg_len cmd_no resp_len result res_conent 0xC0unsigned char pathname[1024] = {0};sprintf(pathname,"%s/%s",SERVER_PATH,filename);//获取filename指向的文件的文件大小struct stat st;int r = lstat(pathname,&st);if(r < 0){perror("Lstat Failed");return -1;}unsigned char resp[8192] = {0};int i = 0;int pkg_len = 17;int resp_len = 5;FTP_CMD cmd_no = FTP_GET;int file_size = st.st_size;resp[i++] = 0xC0;//包头resp[i++] = pkg_len & 0xFF;resp[i++] = (pkg_len >> 8) & 0xFF;resp[i++] = (pkg_len >> 16) & 0xFF;resp[i++] = (pkg_len >> 24) & 0xFF;//包长度以小端模式存储resp[i++] = cmd_no & 0xFF;resp[i++] = (cmd_no >> 8) & 0xFF;resp[i++] = (cmd_no >> 16) & 0xFF;resp[i++] = (cmd_no >> 24) & 0xFF;//命令号以小端模式存储resp[i++] = resp_len & 0xFF;resp[i++] = (resp_len >> 8) & 0xFF;resp[i++] = (resp_len >> 16) & 0xFF;resp[i++] = (resp_len >> 24) & 0xFF;//回复的内容的长度,以小端模式存储resp[i++] = 0;//回复的结果->假设成功resp[i++] = file_size & 0xFF;resp[i++] = (file_size >> 8) & 0xFF;resp[i++] = (file_size >> 16) & 0xFF;resp[i++] = (file_size >> 24) & 0xFF;//回复的内容的长度,以小端模式存储resp[i++] = 0xC0;//包尾//把get的响应包发送获取send(sock,resp,i,0);int fd = open(pathname,O_RDWR);if(fd == -1){perror("Open Failed");return -1;}//将文件内容分多次发送给客户端unsigned char buf[1024] = {0};while(1){int ret = read(fd,buf,sizeof(buf));if(ret == 0){break;}else if(ret < 0){perror("Read File Failed");return -1;}else{//将读取到的文件内容发送给客户端write(sock,buf,ret);}}close(fd);close(sock);return 0;}void Resp_Put(int sock,char *filename){}/*服务器与客户端的处理函数*/void Handle_Connect(int sock,struct sockaddr_in caddr){int ret;unsigned char ch;while(1){//找到包头do{//一个字节一个字节的去读找到包头ret = read(sock,&ch,1);//读取失败则跳出循环if(ret < 0){break;}}while(ch != 0xC0);//此时ch == 0xC0,但还需要防止这个0xC0是上一个包的包尾while(ch == 0xC0){ret = read(sock,&ch,1);if(ret < 0){break;}}//此时ch就是包头后面的第一个字节 包头 + 数据 + 包尾//读取客户端的请求数据包的数据部分unsigned char cmd[512] = {0};int i = 0;while(ch != 0xC0)//意味着还没有来到包尾--->ch还是指向数据部分{cmd[i++] = ch;ret = read(sock,&ch,1);if(ret < 0){break;}}//客户端的请求命令已经读取完毕了,读取到的内容为i个字节int pkg_len = cmd[0] | (cmd[1] << 8) | (cmd[2] << 16) | (cmd[3] << 24);if(pkg_len != i){//意味着包接收不完整printf("Package Length should be %d bytes,But Actually read %d bytes!\n",pkg_len,i);continue;}//将命令号解析出来int cmd_no = cmd[4] | cmd[5] << 8 | cmd[6] << 16 |cmd[7] << 24;printf("Client [ IP : %s ] [ PORT : %d ] Send Cmd [ %s ]\n",inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port),Cmd[cmd_no-1]);if(cmd_no == FTP_LS){Resp_ls(sock);}else if(cmd_no == FTP_GET){//意味着客户端需要去下载文件//先获取到客户端要获取的文件的文件名的长度int arg_len = cmd[8] | (cmd[9] << 8) | (cmd[10] << 16) | (cmd[11] << 24);//获取到客户端要获取的文件的文件名unsigned char filename[512] = {0};strncpy(filename,cmd+12,arg_len);printf("Client [ IP : %s ] [ PORT : %d ] want to get %s\n",inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port),filename);//服务器回应客户端Resp_Get(sock,filename);}else if(cmd_no == FTP_PUT){//意味着客户端需要去上传文件//先获取到客户端要获取的文件的文件名的长度int arg_len = cmd[8] | (cmd[9] << 8) | (cmd[10] << 16) | (cmd[11] << 24);//获取到客户端要获取的文件的文件名unsigned char filename[512] = {0};strncpy(filename,cmd+12,arg_len);printf("Client [ IP : %s ] [ PORT : %d ] want to put %s\n",inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port),filename);//服务器回应客户端Resp_Put(sock,filename);}else if(cmd_no == FTP_BYE){//意味着客户端要断开连接了printf("Client [ IP : %s ] [ PORT : %d ] is disconnected!\n",inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port));close(sock);exit(0);}} }/*信号处理函数*/void sig_handler(int sig){switch(sig){case SIGTERM:case SIGINT:Terminate = 1;break;default:printf("This Signal Not Support!\n");break;}}int main(int argc,char **argv){if(argc != 3){printf("Usage : %s <IP> <PROT>\n",argv[0]);return -1;}//捕捉ctrl+c信号signal(SIGINT,sig_handler);signal(SIGTERM,sig_handler);//将监听套接字准备好int sock = Create_Socket(argv[1],argv[2]);fd_set rfds;//可读监听集合struct timeval timeout;//等待客户端与服务器来进行连接while(!Terminate){//将监听集合清空FD_ZERO(&rfds);//将服务器的套接字的文件描述符加入到监听集合中去//监听套接字是否可读FD_SET(sock,&rfds);//5s的超时时间timeout.tv_sec = 5;timeout.tv_usec = 0;int ret = select(sock + 1,&rfds,NULL,NULL,&timeout);if(ret == 0){printf("Time Out!\n");continue;}else if(ret < 0){perror("Select Failed");continue;}//判断sock是否可读--->有客户端与我连接if(FD_ISSET(sock,&rfds)){struct sockaddr_in caddr;socklen_t len = sizeof(caddr);//处理客户端的连接得到连接套接字int confd = accept(sock,(struct sockaddr *)&caddr,&len);if(confd == -1){perror("Server Connect Failed");continue;}//将与服务器成功连接的客户端的地址信息打印出来printf("Connect [ IP : %s ] [ PORT : %d ]\n",inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port));//创建一个子进程去与当前客户端进行通信pid_t pid = fork();if(pid == -1){perror("Fork Failed");continue;}else if(pid == 0){//关闭监听套接字close(sock);//子进程负责与客户端进行通信Handle_Connect(confd,caddr);}else {//关闭连接套接字close(confd);//不阻塞回收任意子进程waitpid(-1,NULL,WNOHANG);continue;}}}}

(3)客户端代码

#include "head.h"#include "ftp_protocol.h"/*FTP:File Transmit Protocol文件传输协议FTP的客户端Usage : ./a.out <SERVER_IP> <SERVER_PORT>*///标志位,标志着服务器是否退出//为0表示不退出,为1表示退出int Terminate = 0;/*创建套接字@返回值:成功返回套接字,失败返回-1*/int Create_Socket(void){//1.创建套接字int sock = socket(AF_INET,SOCK_STREAM,0);if(sock == -1){perror("Create Socket Failed!");return -1;}int on = 1;setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,(void *)&on,sizeof(on));//允许地址重用setsockopt(sock,SOL_SOCKET,SO_REUSEPORT,(void *)&on,sizeof(on));//允许端口重用return sock;}/*信号处理函数*/void sig_handler(int sig){switch(sig){case SIGTERM:case SIGINT:Terminate = 1;break;default:printf("This Signal Not Support!\n");break;}}/*向服务器发送LS请求,接收服务器的回应并解析*/int Handle_Ls(int sock){unsigned char cmd[1024] = {0};int i = 0;int pkg_len = 8;FTP_CMD cmd_no = FTP_LS;cmd[i++] = 0xC0;//包头cmd[i++] = pkg_len & 0xFF;cmd[i++] = (pkg_len >> 8) & 0xFF;cmd[i++] = (pkg_len >> 16) & 0xFF;cmd[i++] = (pkg_len >> 24) & 0xFF;//包长度以小端模式存储cmd[i++] = cmd_no & 0xFF;cmd[i++] = (cmd_no >> 8) & 0xFF;cmd[i++] = (cmd_no >> 16) & 0xFF;cmd[i++] = (cmd_no >> 24) & 0xFF;//命令号以小端模式存储cmd[i++] = 0xC0;//包尾//把包发送给服务器write(sock,cmd,i);int ret;unsigned char ch;//等待服务器的回应并且解析do{ret = read(sock,&ch,1);if(ret <= 0){break;}}while(ch != 0xC0);//此时ch == 0xC0,但还需要防止这个0xC0是上一个包的包尾while(ch == 0xC0){ret = read(sock,&ch,1);if(ret <= 0){break;}}i = 0;//将服务器回复给我们的数据全部先读取出来unsigned char resq[2048] = {0};while(ch != 0xC0){resq[i++] = ch;ret = read(sock,&ch,1);}//处理数据//验证包的长度是否正确pkg_len = resq[0] | (resq[1] << 8) | (resq[2] << 16) | (resq[3] << 24);if(pkg_len != i){//意味着包接收不完整printf("Package Length should be %d bytes,But Actually read %d bytes!\n",pkg_len,i);return -1;}cmd_no = resq[4] | (resq[5] << 8) | (resq[6] << 16) | (resq[7] << 24);if(cmd_no != FTP_LS){printf("Response Cmd Error");return -1;}if(resq[12] != 0){printf("Get File List Failed");return -1;}printf("%s\n",resq + 13);return 0;}/*向服务器发送获取filename指定的文件请求,接收服务器的回应并解析*/int Handle_Get(int sock,char *filename){}/*向服务器上传filename指定的文件请求,接收服务器的回应并解析*/int Handle_Put(int sock,char *filename){}/*向服务器发送断开连接请求*/int Handle_Bye(int sock){unsigned char cmd[1024] = {0};int i = 0;int pkg_len = 8;FTP_CMD cmd_no = FTP_BYE;cmd[i++] = 0xC0;//包头cmd[i++] = pkg_len & 0xFF;cmd[i++] = (pkg_len >> 8) & 0xFF;cmd[i++] = (pkg_len >> 16) & 0xFF;cmd[i++] = (pkg_len >> 24) & 0xFF;//包长度以小端模式存储cmd[i++] = cmd_no & 0xFF;cmd[i++] = (cmd_no >> 8) & 0xFF;cmd[i++] = (cmd_no >> 16) & 0xFF;cmd[i++] = (cmd_no >> 24) & 0xFF;//命令号以小端模式存储cmd[i++] = 0xC0;//包尾//把包发送给服务器write(sock,cmd,i);}int main(int argc,char **argv){if(argc != 3){printf("Usage : %s <SERVER_IP> <SERVER_PORT>\n",argv[0]);return -1;}//捕捉ctrl+c信号//signal(SIGINT,sig_handler);//signal(SIGTERM,sig_handler);//将套接字准备好int sock = Create_Socket();//先连接上服务器struct sockaddr_in saddr;saddr.sin_family = AF_INET;//指定协议族saddr.sin_port = htons(atoi(argv[2]));//指定网络程序的端口号saddr.sin_addr.s_addr = inet_addr(argv[1]);//指定IP地址int ret = connect(sock,(struct sockaddr *)&saddr,sizeof(saddr));if(ret == -1){perror("Client Connect Failed");close(sock);return -1;}unsigned char buf[256] = {0};while(!Terminate){//从终端上获取控制指令fprintf(stderr,"ftp > ");fgets(buf,256,stdin);//根据用户的输入组建对应的请求包发送给服务器if(!strncmp(buf,"ls",2) || !strncmp(buf,"LS",2) || !strncmp(buf,"lS",2) || !strncmp(buf,"Ls",2)){Handle_Ls(sock);}else if(!strncmp(buf,"get",3)){int i = 3;//从buf中把要get的文件名获取到unsigned char filename[256] = {0};while(buf[i] == ' '){i++;}strcpy(filename,buf + i);filename[strlen(filename) - 1] = '\0';Handle_Get(sock,filename);}else if(!strncmp(buf,"put",3)){//Handle_Put(sock,filename);}else if(!strncmp(buf,"bye",3)){Handle_Bye(sock);close(sock);Terminate = 1;}else {printf("Unkown Cmd Type!\n");}}return 0;}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。