zoukankan      html  css  js  c++  java
  • Web服务器端程序的实现

     

    Web服务器端程序主要是两个部分,一部分是主函数,一部门是命令处理函数。命令处理函数比较好理解就是针对客户端不同的命令进行处理,与客户端进行通信。主函数也有两个主要的功能,第一是要对程序进行初始化,其中包括创建监听套接字并且绑定到地址和端口上,第二是创建子进程处理对应的连接请求。

    1、主函数

    Web服务器的主函数中第一是初始化程序,第二就是创建子进程。父进程一直监听,子进程进行连接处理,提高服务器端的处理能力,提高效率,流程图如下:

     

    主函数中的代码如下所示:

    #include "common.h"
    int main(void)
    {
        struct sockaddr_in sin; /* 服务器端地址结构 */
        struct sockaddr_in cin; /* 客户端地址结构 */
        int lfd, cfd;
        socklen_t len = sizeof(struct sockaddr_in);
                                /* 十进制点分格式地址的长度,包括字符串结束符 */
        char buf[MAX_LINE];      /* 命令缓冲区,存储发送给客户端的信息 */
        char str[ADDR_LEN];      /* 十进制点分地址缓冲区 */
        int sock_opt = 1;    /* 套接字选项 */
        int n;
        pid_t pid;
        if(init(&sin, &lfd, sock_opt) == -1)
                                /* 初始化程序,得到地址结构和监听套接字描述符 */
            exit(1);
        printf("waiting connections ...
    "); /* 打印提示信息 */
        while(1){ /* 死循环,处理客户端的请求 */
            if( (cfd = accept(listen_fd, (struct sockaddr *)&cin, &len)) ==           -1){ /* 接收请求 */
                perror("fail to accept");
                exit(1);
            }
            if( (pid = fork()) < 0){ /* 创建子进程*/
                perror("fail to fork");
                exit(1);
            }else if(pid == 0){   /* 子进程处理连接请求,父进程继续监听 */
                close(lfd);      /* 关闭监听套接字 */
                while(1){ /* 本程序的客户端是一个交互式程序,服务器端和客户端也是交互的 */
                    if(my_read(cfd, buf, MAX_LINE) == -1) /* 读取客户端的命令 */
                        exit(1);
                    /* 在用户发送的命令串中寻找合法的命令,找到则处理 */
                    if(strstr(buf, "GET") == buf) /* get命令 */
                        if(do_put(cfd, &buf[4]) == -1) /* 调用do_put函数进行处理 */
                            printf("error occours while putting
    ");
                    else if(strstr(buf, "PUT") == buf) /* put命令 */
                        if(do_cd(cfd, &buf[4]) == -1) /* 调用do_put函数进行处理 */
                            printf("error occours while getting
    ");
                    else if(strstr(buf, "CD") == buf) /* cd命令 */
                        if(do_ls(cfd &buf[4]) == -1)
                            printf("error occours while changing directory
    ");
                    else if(strstr(buf, "LS") == buf) /* ls命令 */
                        if(do_ls(&buf[4]) == -1)
                            printf("error occours while listing
    ");
                   else if(strstr(buf, "BYE") == buf) /* bye命令 */
                        break; /* 不调用相关函数处理,直接跳出循环 */
                    else{ /* 未知命令,出错 */
                        printf("wrong command
    ");
                        exit(1);
                    }
                }
                close(cfd); /* 跳出循环后关闭连接套接字描述符,通信结束 */
                exit(0); /* 子进程退出 */
            }else
               close(cfd); /* 父进程关闭连接套接字,继续监听 */
        }
          return 0;            /* 服务器程序很少有退出的时候 */
    }

    在主函数中也可以看出来父进程用来监听,子进程用来处理连接请求。一开始进行LinuxC学习的时候学到了进程的相关的知识,这是一个多进程的程序实例,原理很简单,但是需要注意的是子进程与父进程资源共享,因此要在父进程进行监听的时候要关闭连接套接字,在子进程中要关闭监听套接字,防止父子进程中相互干扰。主函数中进程初始化调用的init函数,函数代码如下:

    int init(struct sockaddr_in *sin, int *lfd, int sock_opt)
    {
        bzero(sin, sizeof(struct sockaddr_in));
        sin->sin_family = AF_INET;
        sin->sin_addr.s_addr = INADDR_ANY;
        sin->sin_port = htons(PORT);
    
        if( (tfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { /* 创建监听套接字 */
            perror("fail to creat socket");
            return -1;
        }
        setsockopt(tfd, SOL_SOCKET, SO_REUSEADDR, &sock_opt, sizeof(int)); /* 设置套接字选项 */
        /* 绑定客户端地址,具体地址没有限制 */
        if( (bind(tfd, (struct sockaddr *)sin, sizeof(struct sockaddr_in))) ==     -1){
            perror("fail to bind");
            return -1;
        }
        if( (listen(tfd, 20)) == -1){
            perror("fail to listen");
        return -1;
        }
        *lfd = tfd;
        return 0;
    }
     

    2、命令处理模块

    根据之前的客户端程序可以知道服务器端有4个函数,分别是处理GET、PUT、CD、LS这四个命令。

    do_put函数处理GET命令,函数成功返回0,失败返回-1。客户端发过来的GET命令的格式为:GET arg1。

    参数说明:

    cfd : 连接套接字的描述符

    file : 客户端请求的文件的路径

    int do_put(int cfd, char *file)
    {
        struct stat statbuf; /* 文件状态缓冲区 */
        int n, fd;
        int res = -1;      /* 返回值 */
        if( (fd = open(file, O_RDONLY)) == -1){ /* 打开客户端请求的本地文件 */
            /* 打开失败则向客户端输出出错信息并将应答码设置为ERR
            * 出错信息格式为:应答码 出错信息
            */
            my_write(cfd, "ERR open server file
    ", strlen("ERR open server    file
    ")); 
            return res;        /* 返回-1 */
        }
        if( (fstat(fd, &statbuf)) == -1){ /* 得到文件状态 */
            my_write(cfd, "ERR stat server file
    ", strlen("ERR stat server  file
    ")      /* 出错则发送错误信息 */
            goto end;
        }
        if(!S_ISREG(statbuf.st_mode)){ /* 如果被请求文件不是普通文件,则出错 */
            if(my_write(cfd, "ERR server path should be a regular file
    ", strlen("ERR server path should be a regular file
    ")) == -1) /*           向客户端发送出错信息 */
                goto end;
            res = 0; /* 成功发送后do_put函数返回0,虽然出现了错误,但还是返回0 */
            goto end;
        }
        sprintf(buf, "OK %d", statbuf.st_size); /* 一切正常,发送应答信息,格式为:OK 发送文件的长度 */
        if(my_write(cfd, buf, strlen(buf)) == -1) /* 发送应答信息 */
            goto end;
        if ( (my_read(cfd, buf, MAX_LINE)) <= 0) /* 等待客户端的应答信息,应答码是RDY */
            goto end;
        while(1){ /* 开始传送文件内容 */
            n = read(fd, buf, MAX_LINE); /* 循环读取文件内容,直到文件结束 */
            if(n > 0)
                if(my_write(cfd, buf, n) == -1) /* 将读取的文件内容发送给客户端 */
                    goto end;
            else if(n == 0) {  /* 文件已经到达结尾 */
                printf("OK
    ");  /* 输出提示信息 */
                break;
            }else   { /* 如果读取的字节数小于0则说明出错 */
                perror("fail to read");
                goto end;  
            }
        }
        res = 0; /* 执行至此一切正常 */
    end:
        close(fd); /* 关闭文件,注意不是关闭套接字 */
        return res;
    }

     do_get函数处理PUT命令,成功返回0,失败返回-1。客户端发过来的 PUT命令的格式为:PUT arg1 arg2

    该命令将客户端的文件传送至服务器,arg1为文件的大小,arg2为传送文件在服务器端的文件路径。

    参数说明:

    cfd : 连接套接字的描述符

    file : 传送过来的文件在服务器端的存储路径

    int do_get(int cfd, char *file)
    {
        struct stat statbuf; /* 文件状态缓冲区 */
        int n, fd, len;
    int res = -1;
    if( (fd = open(file, O_WRONLY|O_CREAT|O_TRUNC, 0644)) == -1){ /* 打开文件 */
        /* 文件的打开方式是覆盖写,也就是说如果该文件已存在则覆盖。
        * 但是如果有一个同名的目录则无法覆盖,下面分支处理这种情况
        */
        if(errno == EISDIR){
            if(my_write(cfd, "ERR server has a dir with the same name
    ",
                strlen("ERR server has a dir with the same name
    ")) == -1) /*
                输出错误信息 */
                goto end;
            res = 0; /* 这种错误属于用户的不正确请求 */
            goto end;
        }else{ /* 其他的原因不能打开文件则是服务器程序的错误 */
            my_write(cfd, "ERR open server file
    ", strlen("ERR open server  file
    ")); /* 输出错误信息 */
            goto end;
            }
    }
    if( (fstat(fd, &statbuf)) == -1){ /* 得到文件状态 */
        my_write(cfd, "ERR stat server file
    ", strlen("ERR stat server file
    "));                       /* 输出错误信息 */
        goto end;
        }
    len = statbuf.st_size;
    if(!S_ISREG(statbuf.st_mode)){    /* 如果该文件不是普通文件则出错 */
        if(my_write(cfd, "ERR server path should be a regular file
    ", strlen("ERR server path should be a regular file
    ")) == -1)         /* 输出错误信息 */
            goto end;
        res = 0;
        goto end;
        }
    if(my_write(cfd, "OK", 2) == -1)   /* 发送正确的应答码 */
        goto end;
        while(1){                        /* 循环读传送过来的文件内容 */
        n = my_read(cfd, buf, MAX_LINE);
        if(n > 0){
            write(fd, buf, n);            /* 写到指定的文件中 */
            len -= n;                    /* 文件长度减少 */
        }else if(len == 0) {
            /* 文件没有剩余,表示传输完毕 */
            printf("OK
    ");
            break;
        }else                           /* 读取的字节数小于0,出错 */
            goto end;
        }
        res = 0;                         /* 正常返回 */
    end:
        close(fd);
        return res;
    }

    do_cd函数处理CD命令,成功返回0,失败返回-1,客户端发送过来的CD命令的格式为:PUT arg1

    该命令进入指定的服务器端目录,arg1为指定目录的路径

    参数说明:

    path:指定目录的路径

    int do_cd(char *path)
    {
        if(chdir(path) == -1){ /* 进入指定的目录 */
            perror("fail to change directory
    ");
            /* 出错则向客户端发送错误应答码和出错原因 */
            my_write(cfd, "ERR can't change directory
    ", strlen("ERR can't change directory
    "));
            return -1;
        }
        my_write(cfd, "OK
    ");
        return 0;
    }

     do_ls函数处理LS命令,成功返回0,失败返回-1。客户端发送过来的LS命令的格式为:LS arg1。

    该命令列出当前服务器端目录下的文件,arg1为指定目录的路径

    参数说明:

    path:指定目录的路径

    int do_ls(char *path)
    {
        char cmd[128];
        char buf[NAME_LEN];
        struct stat statbuf; /* 文件状态缓冲区 */
        int n, fd;
        int res = -1;      /* 返回值 */
        /* 拼接命令“ls path > temp.txt”,将文件列表写在temp.txt文件中 */
        sprintf(cmd, "ls %s > temp.txt ",path);
        system(cmd);       /* 执行该命令 */
        if( (fd = open(“temp.txt”, O_RDONLY)) == -1){ /* 打开客户端请求的本地文件    */
            /* 打开失败则向客户端输出出错信息并将应答码设置为ERR。
            * 出错信息格式为:应答码 出错信息
            */
            my_write(cfd, "ERR ls server file
    ", strlen("ERR ls server  file
    ")); 
            return res;        /* 返回-1 */
        }
        if( (fstat(fd, &statbuf)) == -1){ /* 得到文件状态 */
            my_write(cfd, "ERR stat server file
    ", strlen("ERR stat server   file
    ") /* 出错则发送错误信息 */
            goto end;
        }
        if(!S_ISREG(statbuf.st_mode)){ /* 如果被请求文件不是普通文件,出错 */
            if(my_write(cfd, "ERR server path should be a regular file
    ",  strlen("ERR server path should be a regular file
    ")) == -1)          /* 向客户端发送出错信息 */
                goto end;
            res = 0; /* 成功发送后do_put函数返回0,虽然出现了错误,但还是返回0 */
            goto end;
        }
        sprintf(buf, "OK %d", statbuf.st_size); /* 一切正常发送应答信息,格式为: OK 发送文件的长度 */
        if(my_write(cfd, buf, strlen(buf)) == -1) /* 发送应答信息 */
            goto end;
        if ( (my_read(cfd, buf, MAX_LINE)) <= 0) /* 等待客户端的应答信息,应答码是RDY */
            goto end;
        while(1){ /* 开始传送文件内容 */
            n = read(fd, buf, MAX_LINE);      /* 循环读取文件内容,直到文件结束 */
            if(n > 0)
                if(my_write(cfd, buf, n) == -1)/* 将读取的文件内容发送给客户端 */
                    goto end;
            else if(n == 0) {                 /* 文件已经到达结尾 */
                printf("OK
    ");              /* 输出提示信息 */
                break;
            }else   { /* 如果读取的字节数小于0则说明出错 */
                perror("fail to read");
                goto end;  
            }
        }
        res = 0;     /* 执行至此一切正常 */
    end:
        close(fd); /* 关闭文件,注意不是关闭套接字 */
        return res;
    }
  • 相关阅读:
    Spring Cloud Stream 使用延迟消息实现定时任务(RabbitMQ)
    rocketmq发送消息的三种方式
    windows下RocketMQ安装部署
    idea多设备自动同步配置
    idea复制springboot的maven项目后,修改了maven名称,但maven工具里的maven名称没改变,不生效
    SpringBoot图文教程16—SpringBoot 多模块开发「web」「打包」
    spring-boot-starter-parent的作用
    JDK8 从永久代到元空间
    Spring 生命周期
    mesos Failed to connect to
  • 原文地址:https://www.cnblogs.com/Mr--Yang/p/6885724.html
Copyright © 2011-2022 走看看