一、项目描述

功能:通过网络传输,实现文件的跨设备传输,包含服务器和客户端。
        服务器功能: 等待客户端的连接,支持多客户端并发,根据客户端发送过来的命令,执行相应的操作,并向客户端发送器所需要的数据。
        客户端功能:负责链接服务器后,向服务器发送请求,并等待服务器的回应,同时处理服务回应的数据。

命令设置:

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;
}

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

  1. 使用 Charles 简单解决微信开发者工具网络连接失败的问题

    Charles安装网址https://www.charlesproxy.com/ 安装 Charles 然后启动一下就行了.此时再启动微信开发者工具就可以扫描二维码了(此法仅供参考,不能保证一定有效) ...

  2. linux tcp连接计算机,计算机基础知识——linux socket套接字tcp连接分析

    2016.7.4 今天晚上对项目顶层文件(daemon)进行了分析,对其中的TCP连接进行具体的代码级分析. 1.需求分析 首先得知道我们这里为什么要用TCP连接,我们的整个测试系统是由上位机作为客户 ...

  3. tcp连接探测Keepalive和心跳包

    采用TCP连接的C/S模式软件,连接的双方在连接空闲状态时,如果任意一方意外崩溃.当机.网线断开或路由器故障,另一方无法得知TCP连接已经失效,除非继续在此连接上发送数据导致错误返回.很多时候,这不是 ...

  4. 简单总结nodejs处理tcp连接的核心流程

    这篇文章主要介绍了nodejs处理tcp连接的核心流程,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下 前几天和一个小伙伴交流了一下nodejs中 ...

  5. Linux实战:awl-2.0工具模拟洪水攻击,建立大量的TCP连接

    实战:awl工具模拟洪水攻击,建立大量未完成第二次握手的TCP连接 基于TCP协议的数据传输,需要进行三次握手四次挥手才可以完成.每一次连接都会占用系统内存,直至连接关闭才会释放系统内存.在进行第一次 ...

  6. 介绍一个查看TCP连接的工具TCPView

    该工具可以帮助使用者查看该机器上建立的所有的TCP连接, 以及TCP连接所使用的端口信息, 连接的状态信息, 进程ID等. 还可以看到连接过来的对方的机器名, 端口号. 在工具窗体下方的工具栏里, 还 ...

  7. 服务器与客户端的文件传输报告,网络文件传输设计报告.doc

    网络文件传输设计报告 合肥学院 计算机科学与技术系 课程设计报告 2011-2012学年第一学期 课程Java语言程序设计课程设计名称网络文件传输系统专业班级08网络工程(1)班姓名王阳光指导教师金莹 ...

  8. 几种TCP连接中出现RST的情况

    UNIX网络编程上说:产生RST的三个条件是:目的地为某端口的SYN到达,然而在该端口上并没有正在监听 的服务器:TCP想取消一个已有链接:TCP接收到一个根本不存在的连接上的分节. 几种TCP连接中 ...

  9. TCP 连接断连问题剖析

    在官方的正式文档中,TCP/IP 协议簇也称为国际互联网协议簇.TCP/IP 协议簇是目前使用最为广泛的全球互联网技术,其分层结构如图 1 所示: 图 1. TCP/IP 协议簇分层结构  如图 1 ...

最新文章

  1. 分享10个实用的高效办公神器,极大地提高办公效率
  2. 微信小程序开源项目库汇总-持续更新
  3. 字典-字典的统计、合并、清空操作
  4. redo日志写入为什么“俩阶段提交”
  5. mybatis配置properties属性
  6. Hibernate学习之一级缓存
  7. Java 算法 等差数列
  8. 转 android有用代码片段
  9. js保存网络图片至本地
  10. 【个人笔记】OpenCV4 C++ 快速入门 04课
  11. ICD3 - Cannot connect to USB device. Unrecognized endpoint.
  12. 【Linux】进程通信、同步、IO复用代码
  13. dwg格式的计算机图,电脑上怎么打开dwg文件?
  14. 最新一代CAD技术方案------Onshape
  15. Ubuntu图形界面和终端界面切换快捷键
  16. SUSE(Linux操作系统)
  17. leetcode807. 保持城市天际线(java)
  18. 团体程序设计天梯赛-练习集 L1-058 6翻了 (15 分)
  19. 【软件测试必备技能】Linux
  20. 360网站卫士常用前端公共库CDN服务

热门文章

  1. PiMuseum-游戏开发入门级教程-中国象棋-Chapter-3
  2. edx文件打开使用的软件免费下载
  3. python矩阵计算器_【python实用编程之简单矩阵计算器实现】GUI编程
  4. .如何将iso文件刻录成光盘?
  5. 2018-2019-2 网络对抗技术 20165231 Exp3 免杀原理与实践
  6. 中山大学计算机学院官网万海,万海:《计算机网络》课程研修班报告 - 中山大学信息科学与技术学院.doc...
  7. 《游戏学习》Java实现蜘蛛纸牌小游戏源码
  8. 数码照片冲印的象素与分辨率
  9. 用晨曦记账本,设置多功能打印账目
  10. 近两年利用毫米波雷达生成的三维点云进行轨迹追踪相关文献