Advertisement

基于UDP协议的接收和发送实验

阅读量:

基于UDP协议的接收和发送实验

学习了《TCP/IP协议分析与应用》课程并进行了若干次小型实验,在这里分享了这次学习中的理论基础、编码实现细节以及踩过的坑,并附上相关的代码供参考。

请将实验的全部代码存储于 GitHub 平台上的 gzxultra/Programs-TCPIP 仓库中,并请使用 Fork 和 Pull Request 方法来协助项目的改进。

  • 基于UDP协议的接收和发送实验

    • 导语

    • 一实验内容

    • 二UDP编程框架

      • 1 UDP服务器编程框架
      • 2 UDP客户端编程框架
  • 在三UDP协议程序设计中常用的函数

      1. 实现套接字创建及绑定操作
      1. 实现数据接收功能
      1. 实现数据传输操作
      • 四实验代码实现

        • 1 UDP客户端 dupcli01cpp
        • 2 UDP服务器端 udpserv01cpp
      • 五程序扩展实现

      • 六程序运行结果

导语:

UDP缩写为User Datagram Protocol(UDP),它是一种无连接、不可靠的网络通信方式。

本次实验的目标是利用互联网提供的UDP传输协议来开发一个简单的客户/服务器应用。

了解应用层与传输层之间的软件接口风格,并掌握socket机制以及基于UDP的客户端与服务器架构。

本文不仅阐述了UDP收发技术的基本原理,并附带了相关代码示例;同时分享了笔者在调试过程中积累的经验以及遇到的问题和解决方案。

一,实验内容

开发一个UDP echo客户端和服务端程序来实现以下功能:
当客户端从标准输入端口接收并读取一行文本后,将该文本数据发送到服务器端;
服务器从网络接口读取接收到的数据,并向客户端发送相同的Echo数据;
客户端接收后反馈的Echo数据,并将其输出到标准输出端。
进一步扩展功能包含:

  1. 服务端提供文件目录信息供客户端显示;
  2. 定位并获取服务器指定路径下的文件,并执行下载操作;
  3. 通过HTTP或其他协议传输指定文件至服务端。

二,UDP编程框架

采用UDP协议进行程序设计可划分为客户端与服务器端两大模块。在服务器端的主要步骤包括创建套接字、对套接字与地址结构实施绑定(注册)、接收与发送数据以及关闭套接字等几个环节。在客户端的主要操作涉及创建新连接(设置初始连接)、接收与发送数据以及关闭连接等步骤。两者的核心区别体现在如何处理本地计算机和服务对象之间的关联关系(即绑定),而本地计算机无需主动管理本地IP地址及相应端口的绑定问题。

2.1 UDP服务器编程框架

上图中对UDP协议服务器程序架构进行了详细阐述。主要包含以下六个步骤:建立套接字、设置本地主机IP地址和端口号、配置安全参数、接收数据报文、发送数据包、关闭套接字资源。

复制代码
    int s = socket(AF_INET, SOCK_DGRAM, 0);
    
      
    
    代码解读

生成一个AF_INET数据包套接字家族,并配置UDP协议的套接字为SOCK_DGRAM选项。

配置服务器地址以及监听端口,并初始化绑定网络地址结构,请参考以下示例:

复制代码
    struct sockaddr addr_serv;
    addr_serv.sin_family = AF_INET;                     //地址类型为AF_INET
    addr_serv.sin.addr.s_addr = htonl(INADDR_ANY);      //任意本机地址
    addr_serv.sin_port = htons(PORT_SERV);              //服务器端口
    
      
      
      
      
    
    代码解读

该系统采用AF_INEF类型的地址结构; 其中IP地址支持任意本地IP配置; 服务器运行于指定的自定义端口上; 需特别注意字段sin_addr.s_addr与sin_port均为网络字节序数据。

通过bind()函数将套接字文件描述符与指定地址类型参数结合实现监听功能

复制代码
    bind(s,(struct sockaddr*) &addr_serv,sizeof(addr_serv));    //绑定地址
    
      
    
    代码解读
  1. 接收客户端传输的数据,并通过recvfrom()函数接收客户端的网络数据。
  2. 将数据发送给客户端的过程是应用sendto()函数将数据发送给服务器主机的处理。
  3. 关闭套接字的操作包括调用close()函数来释放相关资源。

2.2 UDP客户端编程框架

在上图中, 同样也对UDP协议的客户端流程进行了描述, 如图所示, UDP协议的客户端流程包括以下五个方面: 套接字建立, 设置目标地址及端口号, 向服务器发送数据, 接收服务器返回的数据, 以及关闭套接字。与之相比, 服务器框架多了一个绑定(bind)环节; 客户端程序中的端口号及本地IP地址通常由系统根据实际需求自动指定; 当采用sendto()recvfrom()函数时, 网络协议栈会在运行过程中临时指定本地IP地址及对应端口号, 具体流程如下:
1. 使用socket函数创建套接字描述符;
2. 配置目标机器IP地址及对应端口号;
3. 使用sendto函数发送数据给目标机器;
4. 利用recvfrom函数接收目标机器返回的数据包;
5. 通过close函数释放未使用的资源

三,UDP协议程序设计的常用函数

UDP协议中常使用的功能模块包括recv()recvform()send()sendto()等接口;此外,在TCP协议的应用开发中也可以使用这些功能模块

3.1 建立套接字socket() 和绑定套接字bind()

UDP协议采用与TCP类似的套接字建立方式,并通过调用socket()函数实现。其区别在于,在UDP中使用的类型描述符为SOCK_DGRAM,这与TCP中的参数设置不同。例如,请参考以下代码示例来实现一个UDP套接字文件描述符。

复制代码
    int s;
    s = socket(AF_INEF, SOCK_DGRAM, 0);
    
      
      
    
    代码解读

UDP协议采用了bind()函数的方法与TCP没有明显差异,并将一个套接字描述符与一个地址结构连接到一起。以下代码实现了本地地址与套接字文件描述符的连接。

复制代码
    struct sockaddr_in local;                               //本地的地址信息
    int from_len = sizeof(from);                            //地址结构的长度
    local. sin _family = AF_INET;                           //协议簇
    local. sin _port = htons(8888);                         //本地端口
    local. sin _addr.s_addr = htonl(INADDR_ANY);            //任意本机地址
    s = socket(AF_INET, SOCK_DGRAM, 0);         //初始化一个IPv4族的数据包套接字
    if (s == -1) {                              //检查是否正常初始化socket
    perror("socket");
    exit(EXIT_FAILURE);
    }
    bind(s, (struct sockaddr*) &local, sizeof(local));      //套接字绑定
    
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

该函数被用来决定绑定的时间点,在网络通信中非常重要。
其主要功能是将一个套接字文件描述符与本地地址绑定了起来。
这种操作是为了确保在发送数据的时候能够正确地指定端口地址和IP地址。
例如,在没有进行绑定的情况下,在发送数据时系统会选择一个临时的随机端口。

3.2 接收函数recvfrom()/recv()

一旦客户端成功创建了一个适合的strct sockaddr结构并建立了有效的套接字文件描述符,则可采用recv()函数与之配合接收来自该套接字文件描述符的数据包,并处理之前在此前等待的数据。

recv()函数和recvfrom()函数的原型如下:

复制代码
    #include <sys/types.h>
    #include <sys/socket.h>
    ssize_t recv(int s, void *buff, size_t len, int flags);
    ssize_t recvfrom(int s, void *buff,size_t len, int flags, struct sockaddr *from, socklen_t *from)
    
      
      
      
      
    
    代码解读

3.3 发送函数sendto()/send()

当客户端实现了套接字文件描述符的建立,并构造了相应的struct sockaddr结构后

send()函数和sendto()函数的原型如下:

复制代码
    #include <sys/types.h>
    #include <sys/socket.h>
    ssize_t send(int s, const void *buff, size_t len, int flags);
    ssize_t sento(int s, const void *buff, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);
    
      
      
      
      
    
    代码解读

四,实验代码实现

4.1 UDP客户端 dupcli01.cpp

  • 首先给出udp client端的代码,标准的实现方法,比较简单。
复制代码
    /* * udpcli01.cpp
     * *  Created on: 2015年4月23日
     *      Author: gzxultra
     */
    
    #include        <netinet/in.h>
    #include        <errno.h>
    #include        <stdio.h>
    #include        <stdlib.h>
    #include        <sys/socket.h>
    #include        <string.h>
    #include        <arpa/inet.h>
    #define MAXLINE                  4096
    #define LISTENQ                  1024    /* 2nd argument to listen() */
    #define SERV_PORT                9877
    #define SA      struct sockaddr
    
    void dg_cli(FILE *, int, const SA *, socklen_t);
    
    int
    main(int argc, char **argv)
    {
        int sockfd;
        struct sockaddr_in servaddr;
    
        if (argc != 2) {
            printf("usage:udpcli01sigio <IPaddress>\n");
            exit(1);
        }
    
        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(SERV_PORT);
    
        inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
    
        if((sockfd = socket(AF_INET, SOCK_DGRAM, 0))<0) {
            printf("socket error.\n");
            exit(1);
        }
    
        dg_cli(stdin, sockfd, (SA *)&servaddr, sizeof(servaddr));
    
        exit(0);
    }
    
    void
    dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
    {
    int  n;
    char sendline[MAXLINE], recvline[MAXLINE + 1];
    
    while (fgets(sendline, MAXLINE,fp) != NULL) {
        sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
        n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
        recvline[n] = 0; /* null terminate */
        fputs(recvline, stdout);
    }
    }
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

4.2 UDP服务器端 udpserv01.cpp

  • 在开发服务器代码的过程中我确实遭遇了不少挑战。
    • 按照之前所述的方式进行操作即"建立套接字、将套接字与地址结构进行绑定、读写数据、关闭套接字"这几个步骤相信很快便能完成server的编写工作。
    • 然而实际情况并非如此我陷入了困境花了整整三天时间才最终完成了代码编写工作并且遇到了诸多问题。
Alt text
Alt text

如图所示, 由于客户端持续返回异常数据串(包括缓冲区输入字符前缀及乱码信息), 我决定向服务器增加控制台日志输出, 以便排查 recvfrom 函数接收数据的问题。令人遗憾的是, 服务器完全没有控制台日志输出, 这表明 UDP 两端未能建立正常通信连接, 导致 server 阻塞于 recvfrom 函数执行中

  • 在反复调试的过程中, 我发现每次重启环境后, 第一次输出总是正确的, 接下来的输出就出现了混乱的状态。
    • 我通常会使用Sublime Text来快速测试代码. 按下了快捷键Command+B后, 又打开了终端, 并运行了./udpserv01命令. 实际上, 在执行这一操作时, Sublime Text自身也启动了一个服务进程.
      这样一来, 在同一时间段内就会开启两个服务器进程. 因此, 客户端无法正常接收数据!
      与其费时费力地通过这种方式进行测试和调试(虽然效率低下且处理的是低级问题), 不如直接使用终端进行验证.
Alt text
  • 基于个人经历分享的是一个宝贵的经验教训:不必过分担心学习阶段中的困难与挑战。
  • 感谢v2ex的carto同学指正了这一问题,在客户端传递数据时需要注意的是:字符串在传输过程中可能缺少前导零,在服务端获取其长度之前应当进行相应的处理。
复制代码
    /* * udpserv01.cpp
     * *  Created on: 2015年4月23日
     *      Author: gzxultra
     */
    
    #include        <netinet/in.h>
    #include        <errno.h>
    #include        <stdio.h>
    #include        <stdlib.h>
    #include        <sys/socket.h>
    #include        <arpa/inet.h>
    #define MAXLINE                  4096
    #define LISTENQ                  1024    /* 2nd argument to listen() */
    #define SERV_PORT                9877
    #define SA      struct sockaddr
    #include    <string.h>
    
    
    static int sockfd;
    void dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen, char *mesg);
    void GetList(int sockfd, SA *pcliaddr, socklen_t clilen) ;
    
    int main(int argc, char ** argv) {
    
    struct sockaddr_in servaddr, cliaddr;
    
    //使用socket()函数生成套接字文件描述符
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        printf("socket error.\n");
        exit(1);
    }
    
    //设置服务器地址和侦听端口,初始化要绑定的网络地址结构
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;                      //地址类型为AF_INET
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);       //任意本地地址
    servaddr.sin_port = htons(SERV_PORT);               //服务器端口
    
    //绑定侦听端口
    bind(sockfd, (SA *) &servaddr, sizeof(servaddr));
    
    char instruction[3][10] = {
        "getlist\n",
        "download\n",
        "upload\n"
    };
    
    int op = 0;
    int i = 0,j = 0;
    int n = 0;
    char mesg[MAXLINE];
    socklen_t len = sizeof(cliaddr);
    //printf("running here!\n");
    while(1){
        //len = clilen;
        memset(mesg,0,sizeof(mesg));
        //recvfrom接收客户端的网络数据
        n = recvfrom(sockfd, mesg, MAXLINE, 0, (SA *) &cliaddr,  &len);
    
    
        op = 0;
    
        //处理接受到的回车符
        for(j=0;j<=n;j++){
            if(mesg[j] == '\n') mesg[j] = '\0';
            break;
        }
        for(i = 1;i<=3;i++)
            if(strcmp(mesg,instruction[i-1])== 0){
                op = i;
                break;
            }   //choose operation
        for(j=0;j<=n;j++){
            if(mesg[j] == '\0') mesg[j] = '\n'; 
            break;
        }
    
        printf("op = %d\n",op);
        switch(op){
            case 1: 
                GetList(sockfd, (SA *) &cliaddr, sizeof(cliaddr));
                break;
            //case 2:DownLoad();break;
            //case 3:UpLoad();break;
            default:{
                dg_echo(sockfd, (SA *) &cliaddr, sizeof(cliaddr), mesg);
                break;
            }
        }
        //dg_echo(sockfd, (SA *) &cliaddr, sizeof(cliaddr), mesg);
        //printf("echo done!\n");
    }
    }
    
    void GetList(int sockfd, SA *pcliaddr, socklen_t clilen) {
    int n;
    socklen_t len;
    
    printf("catch\n");
    
    //char mesg[MAXLINE];
    
    char strlin[100][MAXLINE];  //一百行缓冲区
    char sendline[MAXLINE]={};  //最终发送的字符串
    
    int i = 0;
    FILE *fp;
    
    
            system("ls -l>filelog.txt");    //输出重定向
    
            //printf("2done!\n");
    
            ssize_t read;
            //char c;
            //int count=0;
            fp = fopen("filelog.txt", "r");
    
            if (!fp) {
                printf("get ls order failed!");
            } else {
                //printf("4done!\n");
                //printf("%p\n", fp);
                while (1) {
                    //printf("2done!!!!\n");
                    if (NULL==fgets(strlin[i], MAXLINE, fp))
                        break;  //如果读到文件尾,结束读取
                    //strlin[i][strlen(strlin[i] - 1)] = '\0';  //每行加\0结尾
                    i++;    //写成strlin[i++]提示i未定义,gcc的bug??
                }
                /*
                 ++i;
                 memcpy(strlin[i],"EOF",sizeof("EOF"));
                 */
                for (int n = 0; n < i; n++){
                    strcat(sendline, strlin[n]);
                    //strcat(sendline,"\n");
                }
                //sendto(sockfd, strlin, sizeof(strlin), 0, pcliaddr, len);
                fputs(sendline,stdout);
                sendto(sockfd, sendline, strlen(sendline)+1, 0, pcliaddr, clilen);
            }
            //strcat(sendline, '\0');
    
            fclose(fp);
    
            printf("FileList Get!\n");
    
    }
    
    void dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen, char *mesg) {
    //fputs(mesg,stdout);
    //printf("\n");
    sendto(sockfd, mesg, strlen(mesg)+1, 0, pcliaddr, clilen);
    
    }
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

五,程序扩展实现

本题包含三个主要功能需求:建立服务器目录获取机制、设计文件下载流程以实现客户端接收指定文件以及构建客户端上传机制以支持文件同步至服务器。

三个扩展题的实现思路在本质上是相同的。通过在服务器和客户机之间打开同一个文档,并将文档中的每一行内容依次读取至尾部,并将其打包成套接字的形式发送出去;客户端和服务器端接收后将数据逐行写入本地文件中。其中第一个问题要求将命令system("ls -l")的标准输出重定向至本地的一个文本文件中,并完成读取操作。

附上完整的实现代码。

复制代码
    /* * udpcli01.cpp
     * *  Created on: 2015年4月23日
     *      Author: gzxultra
     *  Finished on: 2015年4月30日
     */
    
    #include        <netinet/in.h>
    #include        <errno.h>
    #include        <stdio.h>
    #include        <stdlib.h>
    #include        <sys/socket.h>
    #include        <string.h>
    #include        <arpa/inet.h>
    #define MAXLINE                  4096
    #define LISTENQ                  1024    /* 2nd argument to listen() */
    #define SERV_PORT                9877
    #define SA      struct sockaddr
    
    void dg_cli(FILE *, int, const SA *, socklen_t);
    
    int main(int argc, char **argv) {
    int sockfd;
    struct sockaddr_in servaddr;
    
    if (argc != 2) {
        printf("usage:udpcli01sigio <IPaddress>\n");
        exit(1);
    }
    
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    
    inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
    
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        printf("socket error.\n");
        exit(1);
    }
    
    dg_cli(stdin, sockfd, (SA *) &servaddr, sizeof(servaddr));
    
    exit(0);
    }
    
    void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
    {
    int n;
    char sendline[MAXLINE], recvline[MAXLINE + 1];
    char instruction[3][10] = {
        "getlist\n",
        "download\n",
        "upload\n"
    };
    int i=0;
    int op =0;
    FILE *CopyToLocal;
    FILE *Uploadfp;
    
    while (fgets(sendline, MAXLINE,fp) != NULL) {
        sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
        n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
        recvline[n] = 0; /* null terminate */
        //fputs(recvline,stdout);
        op = 0;
    
        for(i = 1;i<=3;i++)
        if(strcmp(sendline,instruction[i-1])== 0) {
            op = i;
            break;
        }   //choose operation
        printf("op=%d\n",op);
    
        switch(op) {
            case 1: {
                fputs(recvline, stdout);
                break;
            }
            case 2: {
                fputs(recvline, stdout);
                fgets(sendline, MAXLINE,fp);        //给出上传文件的文件名
                sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
                memset(recvline,0,sizeof(recvline));
                n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
                recvline[n] = 0; /* null terminate */
    
                char path[MAXLINE] = "/Users/gzxultra/downloads/";
                strcat(path,sendline);
                fputs(path,stdout);
                printf("\n");
                CopyToLocal = fopen(path,"w");
                if(!CopyToLocal)
                printf("download file failed!");
                //fputs(recvline,stdout);
                fputs(recvline, CopyToLocal);
                //fputs(recvline,stdout);
                //printf("download !");
                fclose(CopyToLocal);
                break;
            }
    
            case 3: {
                fputs(recvline, stdout);        //应该是提示输入文件名
                fgets(sendline, MAXLINE,fp);//应该是输入要上传的文件名字吧?
                sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
                memset(recvline,0,sizeof(recvline));
                //recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL); //接收一個okay
                //recvline[n] = 0;// null terminate 
    
                //fputs(sendline,stdout);
                //fputs("\n",stdout);
    
                for(int j=0;j<=n;j++) {
                    if(sendline[j] == '\n') sendline[j] = '\0';
                    //break;
                }
                //fputs(sendline,stdout);
                //printf("\n");
                Uploadfp = fopen(sendline, "r");
                int i =0;
                char strlin[1000][MAXLINE];
                //printf("don't\n");
                if (!Uploadfp) {
                    printf("get file failed!\n");
    
                } else {
                    //printf("4done!\n");
                    //printf("%p\n", fp);
                    while (1) {
                        //printf("2done!!!!\n");
                        if (NULL==fgets(strlin[i], MAXLINE, Uploadfp))
                        break;//如果读到文件尾,结束读取
                        //strlin[i][strlen(strlin[i] - 1)] = '\0';  //每行加\0结尾
                        i++;//写成strlin[i++]提示i未定义,gcc的bug??
                    }
    
                    memset(sendline,0,sizeof(sendline));
                    for (int n = 0; n < i; n++) {
                        strcat(sendline, strlin[n]);
                        //strcat(sendline,"\n");
                    }
                    //sendto(sockfd, strlin, sizeof(strlin), 0, pcliaddr, len);
                    //fputs(sendline,stdout);
                    sendto(sockfd, sendline, strlen(sendline)+1, 0, pservaddr, servlen);
    
                    //recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
                    //fputs(recvline,stdout);//path
                    //fputs("\n",stdout);
                }
                //strcat(sendline, '\0');
    
                fclose(Uploadfp);
            }
    
            default: {
                fputs(recvline, stdout);
                break;
            }
            //to spilit up the output by ***
    
        }
        for(int i=0;i<25;i++) {
            printf("*");
        }
        printf("\n");
    }
    
    }
    
    /* * udpserv01.cpp
     * *  Created on: 2015年4月23日
     *      Author: gzxultra
     *  Finished on: 2015年4月30日
     */
    
    #include        <netinet/in.h>
    #include        <errno.h>
    #include        <stdio.h>
    #include        <stdlib.h>
    #include        <sys/socket.h>
    #include        <arpa/inet.h>
    #define MAXLINE                  4096
    #define LISTENQ                  1024    /* 2nd argument to listen() */
    #define SERV_PORT                9877
    #define SA      struct sockaddr
    #include    <string.h>
    
    static int sockfd;
    void dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen, char *mesg);
    void GetList(int sockfd, SA *pcliaddr, socklen_t clilen);
    void DownLoad(int sockfd, SA *pcliaddr, socklen_t clilen, char *mesg);
    void UpLoad(int sockfd, SA *pcliaddr, socklen_t clilen, char *mesg);
    
    int main(int argc, char ** argv) {
    
    struct sockaddr_in servaddr, cliaddr;
    
    //使用socket()函数生成套接字文件描述符
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        printf("socket error.\n");
        exit(1);
    }
    
    //设置服务器地址和侦听端口,初始化要绑定的网络地址结构
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;                      //地址类型为AF_INET
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);       //任意本地地址
    servaddr.sin_port = htons(SERV_PORT);               //服务器端口
    
    //绑定侦听端口
    bind(sockfd, (SA *) &servaddr, sizeof(servaddr));
    
    char instruction[3][10] = { "getlist\n", "download\n", "upload\n" };
    
    int op = 0;
    int i = 0, j = 0;
    int n = 0;
    char mesg[MAXLINE];
    socklen_t len = sizeof(cliaddr);
    //printf("running here!\n");
    while (1) {
        //len = clilen;
        memset(mesg, 0, sizeof(mesg));
        //recvfrom接收客户端的网络数据
        n = recvfrom(sockfd, mesg, MAXLINE, 0, (SA *) &cliaddr, &len);
    
        op = 0;
    
        //处理接受到的回车符
    
        for (i = 1; i <= 3; i++)
            if (strcmp(mesg, instruction[i - 1]) == 0) {
                op = i;
                break;
            }   //choose operation
    
        printf("op = %d\n", op);
        switch (op) {
            case 1:{
                GetList(sockfd, (SA *) &cliaddr, sizeof(cliaddr));
                break;
            }
            case 2: {
                DownLoad(sockfd, (SA *) &cliaddr, sizeof(cliaddr), mesg);
                break;
            }
            case 3: {
                UpLoad(sockfd, (SA *) &cliaddr, sizeof(cliaddr), mesg);
                break;
            }
            default: {
                dg_echo(sockfd, (SA *) &cliaddr, sizeof(cliaddr), mesg);
                break;
            }
        }//switch(op)
    }//while
    }//main
    
    void dg_echo(int sockfd, SA *pcliaddr, socklen_t clilen, char *mesg) {
    //fputs(mesg,stdout);
    //printf("\n");
    sendto(sockfd, mesg, strlen(mesg) + 1, 0, pcliaddr, clilen);
    
    }
    
    void GetList(int sockfd, SA *pcliaddr, socklen_t clilen) {
    int n;
    socklen_t len;
    
    printf("catch\n");
    
    char strlin[100][MAXLINE];  //一百行缓冲区
    char sendline[MAXLINE] = { };   //最终发送的字符串
    
    int i = 0;
    FILE *fp;
    
    system("ls -l>filelog.txt");    //输出重定向
    
    ssize_t read;
    
    fp = fopen("filelog.txt", "r");
    
    if (!fp) {
        printf("get ls order failed!");
    } else {
        while (1) {
            //printf("2done!!!!\n");
            if (NULL == fgets(strlin[i], MAXLINE, fp))
                break;  //如果读到文件尾,结束读取
            //strlin[i][strlen(strlin[i] - 1)] = '\0';  //每行加\0结尾
            i++;    //写成strlin[i++]提示i未定义,gcc的bug??
        }
    
        for (int n = 0; n < i; n++) {
            strcat(sendline, strlin[n]);
            //strcat(sendline,"\n");
        }
        //sendto(sockfd, strlin, sizeof(strlin), 0, pcliaddr, len);
        fputs(sendline, stdout);
        sendto(sockfd, sendline, strlen(sendline) + 1, 0, pcliaddr, clilen);
    }
    //strcat(sendline, '\0');
    
    fclose(fp);
    
    printf("FileList Get!\n");
    
    }
    
    void DownLoad(int sockfd, SA *pcliaddr, socklen_t clilen, char *mesg) {
    char text[MAXLINE] = "Please input the file name you'd like to download\n";
    char sendline[100 * MAXLINE];
    char instr[] = "download\n";
    int n = 0;
    //fputs(mesg,stdout);
    while (strcmp(mesg, instr) == 0) {
    
        printf("start download\n");
        sendto(sockfd, text, strlen(text) + 1, 0, pcliaddr, clilen);
        n = recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &clilen);
    }
    FILE *Downloadfp;
    fputs(mesg, stdout);
    //fputs("\n",stdout);
    
    for (int j = 0; j <= n; j++) {
        if (mesg[j] == '\n')
            mesg[j] = '\0';
        //break;
    }
    Downloadfp = fopen(mesg, "r");
    int i = 0;
    char strlin[1000][MAXLINE];
    //printf("don't\n");
    if (!Downloadfp) {
        printf("get file failed!\n");
    
    } else {
        while (1) {
            //printf("2done!!!!\n");
            if (NULL == fgets(strlin[i], MAXLINE, Downloadfp))
                break;  //如果读到文件尾,结束读取
            //strlin[i][strlen(strlin[i] - 1)] = '\0';  //每行加\0结尾
            i++;    //写成strlin[i++]提示i未定义,gcc的bug??
        }
        memset(sendline, 0, sizeof(sendline));
        for (int n = 0; n < i; n++) {
            strcat(sendline, strlin[n]);
            //strcat(sendline,"\n");
        }
        //sendto(sockfd, strlin, sizeof(strlin), 0, pcliaddr, len);
        fputs(sendline, stdout);
        sendto(sockfd, sendline, strlen(sendline) + 1, 0, pcliaddr, clilen);
    }
    //strcat(sendline, '\0');
    
    fclose(Downloadfp);
    
    printf("file download!\n");
    }
    
    void UpLoad(int sockfd, SA *pcliaddr, socklen_t clilen, char *mesg) {
    char text[MAXLINE] = "Please input the file name you'd like to upload\n";
    char recvline[100 * MAXLINE];
    char instr[] = "upload\n";
    int n = 0;
    //fputs(mesg,stdout);
    
    FILE *CopyToServer;
    
    while (strcmp(mesg, instr) == 0) {
        printf("start upload\n");
        sendto(sockfd, text, strlen(text) + 1, 0, pcliaddr, clilen);
        n = recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &clilen);
    }   //上面应该没问题了~
    
    char path[MAXLINE] = "/Users/gzxultra/";
    strcat(path, mesg);
    CopyToServer = fopen(path, "w");
    if (!CopyToServer)
        printf("Upload file failed!\n");
    else {
        recvfrom(sockfd, recvline, 100 * MAXLINE, 0, pcliaddr, &clilen);
        fputs(recvline, CopyToServer);
        //sendto(sockfd, path, strlen(path) + 1, 0, pcliaddr, clilen);
    }
    //fputs(recvline,stdout);
    
    //fputs(recvline,stdout);
    //printf("download !");
    fclose(CopyToServer);
    printf("file Uploadload!\n");
    }
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

六,程序运行结果

通过执行命令getlist来获取服务器目录

从服务器下载文件filelog.txt

将客户机文件daytimecli.c上传至服务器

Alt text

全部评论 (0)

还没有任何评论哟~