zoukankan      html  css  js  c++  java
  • Web服务器文件传输程序客户端程序实现

     

    1、 客户端程序--主函数

    客户端主程序的流程图如下:

     

    主程序主要是分析输入的命令,根据不同命令调用不同的函数处理或者进行出错处理,函数代码如下:

    #include "common.h"
    
    int main(void)
    
    {
    
    char cline[COMMAND_LINE];             /* 缓冲区,存储用户输入的命令 */
    
    struct command_line command; /* 命令结构,存储分解后的命令 */
    
    int sock_fd;
    
    struct sockaddr_in sin;                /* 服务器端的地址结构 */
    
    printf("myftp$: ");                       /* 打印提示符 */
    
    fflush(stdout);                             /* 保证提示符显示 */
    
    while(fgets(cline, MAX_LINE, stdin) != NULL){ /* 得到一行命令 */
    
            if(split(command, cline) == -1) /* 将命令拆分为命令名和命令参数的形式*/
    
                   exit(1);
    
            /* 根据命令名作命令处理,比较命令名和每一个合法命令,匹配则处理 */
    
            if(strcasecmp(command.name, "get") == 0){ /* get命令 */
    
                   if(do_get(command.argv[1], command.argv[2], sock_fd) == -1)
    
                          exit(1); /* 命令处理出错则退出 */
    
            }else if(strcasecmp(command.name, "put") == 0){ /* put命令 */
    
                   if(do_put(command.argv[1], command.argv[2], sock_fd) == -1)
    
                          exit(1);
    
            }else if(strcasecmp(command.name, "cd") == 0){ /* cd命令 */
    
                   if(do_cd(command.argv[1]) == -1)
    
                          exit(1);
    
            }else if(strcasecmp(command.name, "!cd") == 0){ /* !cd命令 */
    
                   if(do_serv_cd(command.argv[1], sock_fd) == -1)
    
                          exit(1);
    
            }else if(strcasecmp(command.name, "ls") == 0){ /* ls命令 */
    
                   if(do_ls(command.argv[1]) == -1)
    
                          exit(1);
    
            }else if(strcasecmp(command.name, "!ls") == 0){ /* !ls命令 */
    
                   if(do_serv_ls(command.argv[1], sock_fd) == -1)
    
                          exit(1);
    
            }else if(strcasecmp(command.name, "connect") == 0){ /* connect               命令 */
    
                   if(do_connect(command.argv[1], &sin, &sock_fd) == -1)
    
                          exit(1);
    
            }else if(strcasecmp(command.name, "bye") == 0){ /* bye命令 */
    
                   if(do_bye(sock_fd) == -1)
    
                          exit(1);
    
                   break; /* 向服务器端发送退出信息后,则跳出循环,退出程序 */
    
            }else{ /* 错误命令,打印程序的用法 */
    
                   printf("wrong command
    ");
    
                   printf("usage : command arg1, arg2, ... argn
    ");
    
            }
    
            printf("myftp$: "); /* 再次打印提示符,准备接受新的命令 */
    
            fflush(stdout);
    
    }
    
    if(close(sock_fd) == -1){ /* 执行bye命令后执行到这里,关闭通信用套接字 */
    
            perror("fail to close");
    
            exit(1);
    
    }
    
    return 0; /* 程序正常退出 */
    
    }

    2、客户端程序--命令拆分

    命令拆分程序可以根据之前的程序设计来进行编写,先判断是什么命令,再根据命令类型的不同调用不同的函数,将命令中的参数分离出来作为。主程序中调用该程序,根据不同的命令调用不同的函数,并且将参数传递给处理函数,流程图如下:

     

    程序代码如下:

    #include "common.h"
    
    /*下面的宏将cline串中从pos所表示的位置开始,跳过连续的空格和制表符 */
    
    #define del_blank(pos, cline); { 
    
    while(cline[pos] != '' && (cline[pos] == ' ' || cline[pos] == '	'))
    
    { 
    
            pos++; 
    
    } 
    
    }
    
    /* 下面的宏得到一个命令参数。
    
    * 将cline串中从pos所表示的位置的内容复制到arg缓冲区中,直到遇到空格、制表符或者结束符为止
    
    */
    
    #define get_arg(arg, pos, cline); { 
    
    int i = 0; 
    
    while(cline[pos] != '' && cline[pos] != ' '&& cline[pos] != '/t'){ 
    
            arg[i++] = com[pos++]; 
    
    } 
    
    }
    
    /* 将一个命令字符串分割为命令参数并存储在command_struct结构中
    
     * 成功则返回拆分后的命令参数的个数,失败返回-1
    
    * command : 存储命令和命令参数的结构体
    
    * cline : 命令字符串
    
    */
    
    int split(struct command_line * command, char cline[ ])
    
    {
    
    int i;
    
    int pos = 0;
    
    clien[strlen(cline) - 1] = ''; /* 得到命令字符串的长度,将最后一个‘
    ’                                                               替换为‘’ */
    
    del_blank(pos, cline);                    /* 过滤空格,直到遇到第一个参数 */
    
    i = 0;
    
    while(cline[pos] != ''){ /* 处理整个命令字符串 */
    
            /* 为存储命令参数的数组分配空间 */
    
            if((command->argv[i] = (char *)malloc(MAX_LENGTH)) == NULL){
    
                   perror("fail to malloc");
    
                   return -1;
    
            }
    
            /* 得到一个参数,将两个空格之间的内容复制到存储参数的数组 */
    
            get_arg(command->argv[i], pos, cline);
    
            i++;                                       /* 下一个参数 */
    
            del_blank(pos, cline);     /* 过滤掉空格 */
    
    }
    
    command->argv[i] = NULL;          /* 命令参数数组以NULL结尾 */
    
    comand->name = command->.argv[0]; /* 命令名和第一个命令参数实际上指向同一块内存区域 */
    
    return i;
    
    }

    3、客户端程序—命令模块处理

    命令处理模块是这个程序的核心模块,客户端的命令处理程序有7个。

    (1)do_connect()函数

    do_connect()函数完成了创建套接字并且连接的工作,这个函数返回说明客户端已经和服务器成功连接。do_connect()函数处理connect命令,成功返回0,失败返回-1。connect命令的形式为:connect arg1,参数 arg1是服务器的IP地址。

    参数说明:

    ip : 字符指针,指向服务器的地址,是一个十进制点分字符串;

    sin : 地质结构指针,指向服务器地址结构,在connect函数中填充;

    sock_fd : 整型指针,指向通信用套接字描述符,该套接字在connct函数中设置,将描述符传回。

    int do_connect(char *ip,struct sockaddr_in *sin, int *sock_fd)
    
    {
    
    int sfd; /* 临时的套接字描述符 */
    
    bzero(&sin, sizeof(struct sockaddr_in)); /* 将地址结构清空 */
    
    sin.sin_family = AF_INET;                             /* 使用IPv4地址族 */
    
    /* 将点分十进制形式的IP地址转换成为二进制形式的地址,并且存储在地址结构中 */
    
    if(inet_pton(AF_INET, ip, &sin.sin_addr) == -1){
    
            perror("wrong format of ip address"); /* 如果地址格式为非法则出错 */
    
            return -1;
    
    }
    
    sin.sin_port = htons(PORT); /* 将端口号转换成为网络字节序存储在地址结构中 */
    
    if(sfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){ /* 创建套接字 */
    
            perror("fail to creat socket");
    
            return -1;      
    
    }
    
    /* 使用该套接字,和填充好的地址结构进行连接 */
    
    if(connect(sfd, (struct sockaddr *)sin, sizeof(struct sockaddr_in)) ==     -1){
    
            perror("fail to connect");
    
            return -1;
    
    }
    
    *sock_fd = sfd; /* 将sfd保存的套接字的描述符赋值给sock_fd所指向区域返回 */
    
    return 0;
    
    }

    (2)do_get函数处理get命令,成功返回0,失败返回-1。get命令的形式为:get arg1 arg2。该命令从服务器端取得文件,arg1表示源文件,arg2表示目的路径,如果该文件已存在则覆盖。

    参数说明:

    src : 源文件,是一个绝对路径

    dst : 目的目录,是一个绝对的路径

    sock_fd : 通信用的套接字描述符

    int do_get(const char *src, const char *dst, int sock_fd)
    
    {
    
    char *dst_file;        /* 目的路径 */
    
    char *p;
    
    struct stat statbuf;/* 文件状态缓冲区 */
    
    int n, fd;
    
    char buf[MAX_LINE];/* 命令缓冲区,存储发送给服务器端的命令 */
    
    int len;
    
    int res = -1;          /* 返回值默认的值为-1 */
    
    if(src == NULL || dst == NULL){/* 检查源文件和目的地址是不是空串 */
    
            printf(wrong command
    );  /* 是空串则报错 */
    
            return -1;
    
    }
    
    /* 如果源文件路径的最后一个字符是‘/’,则说明源文件不是一个普通文件,而是一个目录   */
    
      if(src[strlen(src)-1]=='/'){
    
            printf("source file should be a regular file
    ");
    
            return -1;
    
    }
    
    /* 为最终的目的文件路径分配内存空间
    
    * 文件路径由目标目录和源文件的文件名组成
    
    * 所以该缓冲区的大小最大是两个文件路径的长
    
    */
    
      if( (dst_file = (char *)malloc(strlen(dst) + strlen(src))) == NULL){
    
            perror("fail to malloc");
    
            return -1;                       /* 如果内存分配不成功则返回-1 */
    
      }
    
      strcpy(dst_file, dst);         /* 将目标目录复制到dst_file缓冲区中 */
    
      /* 如果目标目录的结尾不是‘/’,则添加‘/’,例如/home/admin—>/home/admin/ */
    
      if(dst_file[strlen(dst_file) - 1] != '/')
    
            strcat(dst_file, "/");
    
      p = rindex(src, '/');              /* 取源文件路径中最后一个‘/’,其后面是文件名 */
    
      strcat(dst_file, p + 1);    /* 跳过‘/’,得到源文件文件名 */
    
    /* 如果目标文件不存在则创建该文件,使用权限字“0644”
    
    * 该权限表示所有者拥有读写权限而组用户和其他用户只有读权限
    
    * 如果该文件存在,则将文件截短为0打开,覆盖原文件
    
    */
    
      if((fd = open(dst_file, O_WRONLY | O_CREAT | O_TRUNC, 0644)) == -1){
    
            perror("fail to open dst-file");
    
            goto end2; /* 打开文件失败,退出时只需要释放dst_file的内存,而不需要关闭            文件 */
    
      }
    
      if(fstat(fd, &statbuf) == -1){ /* 取目标文件的文件状态 */
    
            perror("fail to stat dst-file");
    
            goto end1;
    
      }
    
    /* 如果目标文件已经存在,且不是一个普通文件,则无法传输
    
    * 因为这样会造成将已经存在的目录等其他特殊文件覆盖
    
    */
    
      if(!S_ISREG(statbuf.st_mode)){
    
            printf("dst-file should be a regular file");
    
            goto end1;
    
      }
    
        sprintf(buf, "GET %s", src); /* 准备向服务器端发送GET命令 */
    
      if(my_write(sock_fd, buf, strlen(buf)+1) == -1) /* 发出命令 */
    
            goto end1;                                                               /* 出错则退出 */
    
      if( (n = my_read(sock_fd, buf, MAX_LINE)) <= 0){ /* 接收服务器端发回的确                                                                                       认信息 */
    
            goto end1;
    
      }
    
      if(buf[0] == 'E'){ /* 如果收到的信息是ERR,表示出错 */
    
            write(STDOUT_FILENO, buf, n); /* 向屏幕输出服务器发来的出错信息 */
    
            res = 0;
    
            goto end1;
    
      }
    
    /* 如果对没有出错,服务器反馈的信息格式为“OK 请求文件的文件大小”
    
    * 跳过字符串“OK”和一个空格,从第三个字符开始,取得文件的长度
    
    */
    
      len = atoi(&buf[3]);
    
    /* 向服务器发出准备好的信息,服务器接收到此信息后开始传送文件的内容 */
    
      if(my_write(sock_fd, "RDY", 3) == -1)
    
            goto end1;
    
      while(1){ /* 循环读,直到读完所有的文件内容 */
    
            n = my_read(sock_fd, buf, MAX_LINE); /* 读服务器传送来的内容 */
    
            if(n > 0){ /* 读到的字节数大于0,说明正常 */
    
                   write(fd, buf, n); /* 将读到的内容写到打开的目标文件中去 */
    
                   len -= n; /* 文件总长度减少 */
    
            }else if(len == 0){ /* 读到的字节数等于0,说明通信已经结束 */
    
                   /* 如果请求的文件已经读完,则正常结束此次命令的执行 */
    
                   printf("OK
    ");
    
                   break;
    
            }else /* 读到的字节数小于0,出错 */
    
                   goto end1;
    
    }
    
      res = 0; /* 运行到此,则函数正常返回,返回值为0 */
    
    end1:
    
      free(dst_file); /* 释放动态分配的内存空间 */
    
    end2:
    
      close(fd); /* 关闭文件 */
    
    return res;
    
    }
  • 相关阅读:
    Windows XP下 Android开发环境 搭建
    Android程序的入口点
    在eclipse里 新建android项目时 提示找不到proguard.cfg
    64位WIN7系统 下 搭建Android开发环境
    在eclipse里 新建android项目时 提示找不到proguard.cfg
    This Android SDK requires Android Developer Toolkit version 20.0.0 or above
    This Android SDK requires Android Developer Toolkit version 20.0.0 or above
    Android requires compiler compliance level 5.0 or 6.0. Found '1.4' instead
    Windows XP下 Android开发环境 搭建
    Android程序的入口点
  • 原文地址:https://www.cnblogs.com/Mr--Yang/p/6853684.html
Copyright © 2011-2022 走看看