zoukankan      html  css  js  c++  java
  • Linux高并发web服务器开发——web服务器-2

    在学习Linux高并发web服务器开发总结了笔记,并分享出来。有问题请及时联系博主:Alliswell_WP,转载请注明出处。

    11_服务器开发-第02天(web服务器 - 2)

    目录:
    一、sourceInsight安装及使用
    二、学习目标
    三、复习
    四、服务器端代码实现
    》epoll服务器各部分代码
    问题:中文显示乱码?
    》代码优化
    1)通过文件名获取文件的类型?
    2)如果文件不存在,提示404
    3)浏览器一直请求,一直转圈?
    》程序测试
    》scanf和正则表达式
    》scandir
    》数据转码

    一、sourceInsight安装及使用

    SourceInsight 这款软件,可以用来编辑代码,在嵌入式Linux开发中,很多人用它来修改代码和查看代码,再将代码同步到linux下,用嵌入式(一般是ARM for GNU toolchain)交叉编译工具链来进行代码编译。是一个面向项目开发的程序编辑器和代码浏览器,可支持C/C++或Java等多种语言,还可以创建自己的符号数据库,方便实用。

    》解决的问题:可能你还是不明白用SourceInsight来干嘛,简单举个例子,写过代码的你都知道,你有一个代码工程,有很多个文件,你为了找到一个全局变量或者函数,找老半天没有找到。有了SourceInsight,你就直接双击这个函数或者全局变量,就可以跳到定义的地方。

    》下载:

    从百度云中下载文件,链接:https://pan.baidu.com/s/1ml75LW0ft4bo4s9T3AiQRA
    提取码:oyat

    》安装及破解:

    双击“sourceinsight4085-setup.exe”进行安装,一路默认即可,最后记得安装完那一步把运行先勾选掉。——>然后把“sourceinsight4.exe”替换掉安装目录(默认安装目录:C:Program Files (x86)Source Insight 4.0)下的“sourceinsight4.exe”——>双击“sourceinsight4.exe”打开——>选择“Import a new licence file”,然后使用“si4.pediy.lic”文件来破解。

    》打开其他project目录方法:

    方法一:在菜单栏中的project目录下新建新的工程,然后将自己想看的代码放入这个工程中,就可以在source insight中看这个文件(一般新建的时候都是默认在C盘的base文件夹中,要记得修改好)

    方法二:将想要看的代码从库中复制出来,然后将它作为新建的工程目录,它就可以会生成si4.prooject目录,就可以打开它直接在软件上看代码了

    》界面布局设置:


    1.界面一打开方式:
    在菜单栏选择 View,之后在下面勾选 *Symbol Window *或者直接 Alt+F8
    2.界面二的代码行数显示
    View------>Line Numbers,就可以显示代码行数
    3.界面二的Overview
    View------>Overview
    4.剩下的操作按照图片来操作即可,将打钩的框图打钩好就可以;出现的框图可以拖动,按照自己想要的方式放好即可

    》界面颜色设置:
    按照图片来设置,颜色我是设置为R199 G237 U204

    二、学习目标

    三、复习

    1、html标签

    2、http协议

    四、服务器端代码实现

    epoll服务器各部分代码:

    (1)主体函数:epoll_run

    void epoll_run(int port)
    {
        // 创建一个epoll树的根节点
        int epfd = epoll_create(MAXSIZE);
        if(epfd == -1)
        {
            perror("epoll_create error");
            exit(1);
        }
    
        // 添加要监听的节点
        // 先添加监听lfd
        int lfd = init_listen_fd(port, epfd);
    
        // 委托内核检测添加到树上的节点
        struct epoll_event all[MAXSIZE];
        while(1)
        {
            int ret = epoll_wait(epfd, all, MAXSIZE, -1);
            if(ret == -1)
            {
                perror("epoll_wait error");
                exit(1);
            }
    
            // 遍历发生变化的节点
            for(int i=0; i<ret; ++i)
            {
                // 只处理读事件, 其他事件默认不处理
                struct epoll_event *pev = &all[i];
                if(!(pev->events & EPOLLIN))
                {
                    // 不是读事件
                    continue;
                }
    
                if(pev->data.fd == lfd)
                {
                    // 接受连接请求
                    do_accept(lfd, epfd);
                }
                else
                {
                    // 读数据
                    do_read(pev->data.fd, epfd);
                }
            }
        }
    }
    void epoll_run(int port)

    (2)添加要监听的节点的函数:init_listen_fd

    int init_listen_fd(int port, int epfd)
    {
        // 创建监听的套接字
        int lfd = socket(AF_INET, SOCK_STREAM, 0);
        if(lfd == -1)
        {
            perror("socket error");
            exit(1);
        }
    
        // lfd绑定本地IP和port
        struct sockaddr_in serv;
        memset(&serv, 0, sizeof(serv));
        serv.sin_family = AF_INET;
        serv.sin_port = htons(port);
        serv.sin_addr.s_addr = htonl(INADDR_ANY);
    
        // 端口复用
        int flag = 1;
        setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));
        int ret = bind(lfd, (struct sockaddr*)&serv, sizeof(serv));
        if(ret == -1)
        {
            perror("bind error");
            exit(1);
        }
    
        // 设置监听
        ret = listen(lfd, 64);
        if(ret == -1)
        {
            perror("listen error");
            exit(1);
        }
    
        // lfd添加到epoll树上
        struct epoll_event ev;
        ev.events = EPOLLIN;
        ev.data.fd = lfd;
        ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
        if(ret == -1)
        {
            perror("epoll_ctl add lfd error");
            exit(1);
        }
    
        return lfd;
    }
    int init_listen_fd(int port, int epfd)

    (3)接收连接请求函数:do_accept

    // 接受新连接处理
    void do_accept(int lfd, int epfd)
    {
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        int cfd = accept(lfd, (struct sockaddr*)&client, &len);
        if(cfd == -1)
        {
            perror("accept error");
            exit(1);
        }
    
        // 打印客户端信息
        char ip[64] = {0};
        printf("New Client IP: %s, Port: %d, cfd = %d
    ",
               inet_ntop(AF_INET, &client.sin_addr.s_addr, ip, sizeof(ip)),
               ntohs(client.sin_port), cfd);
    
        // 设置cfd为非阻塞
        int flag = fcntl(cfd, F_GETFL);
        flag |= O_NONBLOCK;
        fcntl(cfd, F_SETFL, flag);
    
        // 得到的新节点挂到epoll树上
        struct epoll_event ev;
        ev.data.fd = cfd;
        // 边沿非阻塞模式
        ev.events = EPOLLIN | EPOLLET;
        int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
        if(ret == -1)
        {
            perror("epoll_ctl add cfd error");
            exit(1);
        }
    }
    void do_accept(int lfd, int epfd)

    (4)读数据函数:do_read

    // 读数据
    void do_read(int cfd, int epfd)
    {
        // 将浏览器发过来的数据, 读到buf中 
        char line[1024] = {0};
        // 读请求行
        int len = get_line(cfd, line, sizeof(line));
        if(len == 0)
        {
            printf("客户端断开了连接...
    ");
            // 关闭套接字, cfd从epoll上del
            disconnect(cfd, epfd);         
        }
        else
        {
            printf("请求行数据: %s", line);
            printf("============= 请求头 ============
    ");
            // 还有数据没读完
            // 继续读
            while(len)
            {
                char buf[1024] = {0};
                len = get_line(cfd, buf, sizeof(buf));
                printf("-----: %s", buf);
            }
            printf("============= The End ============
    ");
        }
    
        // 请求行: get /xxx http/1.1
        // 判断是不是get请求
        if(strncasecmp("get", line, 3) == 0)//strncasecmp比较前3个字符且不区分大小写
        {
            // 处理http请求
            http_request(line, cfd);
            // 关闭套接字, cfd从epoll上del
            disconnect(cfd, epfd);         
        }
    }
    void do_read(int cfd, int epfd)

    (5)解析http请求消息的每一行内容函数:getline

    // 解析http请求消息的每一行内容
    int get_line(int sock, char *buf, int size)
    {
        int i = 0;
        char c = '';
        int n;
        while ((i < size - 1) && (c != '
    '))
        {
            n = recv(sock, &c, 1, 0);
            if (n > 0)
            {
                if (c == '
    ')
                {
                    n = recv(sock, &c, 1, MSG_PEEK);
                    if ((n > 0) && (c == '
    '))
                    {
                        recv(sock, &c, 1, 0);
                    }
                    else
                    {
                        c = '
    ';
                    }
                }
                buf[i] = c;
                i++;
            }
            else
            {
                c = '
    ';
            }
        }
        buf[i] = '';
    
        return i;
    }
    int get_line(int sock, char *buf, int size)

    (6)断开连接的函数:disconnect

    // 断开连接的函数
    void disconnect(int cfd, int epfd)
    {
        int ret = epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
        if(ret == -1)
        {
            perror("epoll_ctl del cfd error");
            exit(1);
        }
        close(cfd);
    }
    void disconnect(int cfd, int epfd)

    (7)http请求处理:http_request(其中sscanf使用正则表达式格式化字符串

    // http请求处理
    void http_request(const char* request, int cfd)
    {
        // 拆分http请求行
        // get /xxx http/1.1
        char method[12], path[1024], protocol[12];
        sscanf(request, "%[^ ] %[^ ] %[^ ]", method, path, protocol);
    
        printf("method = %s, path = %s, protocol = %s
    ", method, path, protocol);
    
        // 转码 将不能识别的中文乱码 - > 中文
        // 解码 %23 %34 %5f
        decode_str(path, path);
            // 处理path  /xx
            // 去掉path中的/
            char* file = path+1;
        // 如果没有指定访问的资源, 默认显示资源目录中的内容
        if(strcmp(path, "/") == 0)
        {
            // file的值, 资源目录的当前位置
            file = "./";
        }
    
        // 获取文件属性
        struct stat st;
        int ret = stat(file, &st);
        if(ret == -1)
        {
            // show 404
            send_respond_head(cfd, 404, "File Not Found", ".html", -1);
            send_file(cfd, "404.html");//需要提前写好一个404.html的网页
        }
    
        // 判断是目录还是文件
        // 如果是目录
        if(S_ISDIR(st.st_mode))
        {
            // 发送头信息
            send_respond_head(cfd, 200, "OK", get_file_type(".html"), -1);
            // 发送目录信息
            send_dir(cfd, file);
        }
        else if(S_ISREG(st.st_mode))
        {
            // 文件
            // 发送消息报头
            send_respond_head(cfd, 200, "OK", get_file_type(file), st.st_size);
            // 发送文件内容
            send_file(cfd, file);
        }
    }
    void http_request(const char* request, int cfd)

    (8)发送http响应头函数:send_respond_head

    // 发送响应头
    void send_respond_head(int cfd, int no, const char* desp, const char* type, long len)
    {
        char buf[1024] = {0};
        // 状态行
        sprintf(buf, "http/1.1 %d %s
    ", no, desp);
        send(cfd, buf, strlen(buf), 0);
        // 消息报头
        sprintf(buf, "Content-Type:%s
    ", type);
        sprintf(buf+strlen(buf), "Content-Length:%ld
    ", len);
        send(cfd, buf, strlen(buf), 0);
        // 空行
        send(cfd, "
    ", 2, 0);
    }
    void send_respond_head(int cfd, int no, const char* desp, const char* type, long len)

    (9)发送普通文件函数:send_file

    // 发送文件
    void send_file(int cfd, const char* filename)
    {
        // 打开文件
        int fd = open(filename, O_RDONLY);
        if(fd == -1)
        {
            // show 404
            return;
        }
    
        // 循环读文件
        char buf[4096] = {0};
        int len = 0;
        while( (len = read(fd, buf, sizeof(buf))) > 0 )
        {
            // 发送读出的数据
            send(cfd, buf, len, 0);
        }
        if(len == -1)
        {
            perror("read file error");
            exit(1);
        }
    
        close(fd);
    }
    void send_file(int cfd, const char* filename)

    (10)发送目录内容的函数:send_dir(包括readdir读目录;更好的是scandir)

    // 发送目录内容
    void send_dir(int cfd, const char* dirname)
    {
        // 拼一个html页面<table></table>
        char buf[4094] = {0};
    
        sprintf(buf, "<html><head><title>目录名: %s</title></head>", dirname);
        sprintf(buf+strlen(buf), "<body><h1>当前目录: %s</h1><table>", dirname);
    
        char enstr[1024] = {0};
        char path[1024] = {0};
        // 目录项二级指针
        struct dirent** ptr;
        int num = scandir(dirname, &ptr, NULL, alphasort);
        // 遍历
        for(int i=0; i<num; ++i)
        {
            char* name = ptr[i]->d_name;
    
            // 拼接文件的完整路径
            sprintf(path, "%s/%s", dirname, name);
            printf("path = %s ===================
    ", path);
            struct stat st;
            stat(path, &st);
    
            encode_str(enstr, sizeof(enstr), name);
            // 如果是文件
            if(S_ISREG(st.st_mode))
            {
                sprintf(buf+strlen(buf), 
                        "<tr><td><a href="%s">%s</a></td><td>%ld</td></tr>",
                        enstr, name, (long)st.st_size);
            }
            // 如果是目录
            else if(S_ISDIR(st.st_mode))
            {
                sprintf(buf+strlen(buf), 
                        "<tr><td><a href="%s/">%s/</a></td><td>%ld</td></tr>",
                        enstr, name, (long)st.st_size);
            }
            send(cfd, buf, strlen(buf), 0);
            memset(buf, 0, sizeof(buf));
            // 字符串拼接
        }
    
        sprintf(buf+strlen(buf), "</table></body></html>");
        send(cfd, buf, strlen(buf), 0);
    
        printf("dir message send OK!!!!
    ");
    #if 0
        // 打开目录
        DIR* dir = opendir(dirname);
        if(dir == NULL)
        {
            perror("opendir error");
            exit(1);
        }
    
        // 读目录
        struct dirent* ptr = NULL;
        while( (ptr = readdir(dir)) != NULL )
        {
            char* name = ptr->d_name;
        }
        closedir(dir);
    #endif
    }
    void send_dir(int cfd, const char* dirname)

    》问题:中文显示乱码?

    分析及处理:处理http协议需要编码和解码的原因:http协议中请求或响应,不支持中文。所以需要转码。

    》编解码函数介绍:解码encode_str,编码decode_str(头文件#include <ctype.h>)

    // 16进制数转化为10进制
    int hexit(char c)
    {
        if (c >= '0' && c <= '9')
            return c - '0';
        if (c >= 'a' && c <= 'f')
            return c - 'a' + 10;
        if (c >= 'A' && c <= 'F')
            return c - 'A' + 10;
    
        return 0;
    }
    
    /*
     *  这里的内容是处理%20之类的东西!是"解码"过程。
     *  %20 URL编码中的‘ ’(space)
     *  %21 '!' %22 '"' %23 '#' %24 '$'
     *  %25 '%' %26 '&' %27 ''' %28 '('......
     *  相关知识html中的‘ ’(space)是&nbsp
     */
    void encode_str(char* to, int tosize, const char* from)
    {
        int tolen;
    
        for (tolen = 0; *from != '' && tolen + 4 < tosize; ++from) 
        {
            if (isalnum(*from) || strchr("/_.-~", *from) != (char*)0) //数字或/_.-~这几个特殊字符不需要转
            {
                *to = *from;
                ++to;
                ++tolen;
            } 
            else 
            {
                sprintf(to, "%%%02x", (int) *from & 0xff);//前面%%相当于一个%,%02x为两位的十六进制,最后得到如:%23,%04
                //把任何一个字符转为一个十六进制数:(int) *from & 0xff
                to += 3;//一个中文占3个字节,指针后移
                tolen += 3;
            }
    
        }
        *to = '';
    }
    
    //“编码”,用作回写浏览器的时候,将除字母数字及/_.-~以外的字符转义后回写。
    void decode_str(char *to, char *from)
    {
        for ( ; *from != ''; ++to, ++from  ) 
        {
            if (from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2])) //isxdigit判断参数是否为16进制数字,是返回非零,否则返回0
            { 
                //依次判断from中 %20 三个字符
                *to = hexit(from[1])*16 + hexit(from[2]);//16进制转为10进制
                //移动已经处理的两个字符(%21指针指向1),表达式3的++from还会再向后移一个字符
                from += 2;                      
            } 
            else
            {
                *to = *from;
    
            }
    
        }
        *to = '';
    
    }
    解码encode_str,编码decode_str

    》代码优化——

    》1)通过文件名获取文件的类型?

    // 通过文件名获取文件的类型
    const char *get_file_type(const char *name)
    {
        char* dot;
    
        // 自右向左查找‘.’字符, 如不存在返回NULL
        dot = strrchr(name, '.');   
        if (dot == NULL)
            return "text/plain; charset=utf-8";
        if (strcmp(dot, ".html") == 0 || strcmp(dot, ".htm") == 0)
            return "text/html; charset=utf-8";
        if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0)
            return "image/jpeg";
        if (strcmp(dot, ".gif") == 0)
            return "image/gif";
        if (strcmp(dot, ".png") == 0)
            return "image/png";
        if (strcmp(dot, ".css") == 0)
            return "text/css";
        if (strcmp(dot, ".au") == 0)
            return "audio/basic";
        if (strcmp( dot, ".wav" ) == 0)
            return "audio/wav";
        if (strcmp(dot, ".avi") == 0)
            return "video/x-msvideo";
        if (strcmp(dot, ".mov") == 0 || strcmp(dot, ".qt") == 0)
            return "video/quicktime";
        if (strcmp(dot, ".mpeg") == 0 || strcmp(dot, ".mpe") == 0)
            return "video/mpeg";
        if (strcmp(dot, ".vrml") == 0 || strcmp(dot, ".wrl") == 0)
            return "model/vrml";
        if (strcmp(dot, ".midi") == 0 || strcmp(dot, ".mid") == 0)
            return "audio/midi";
        if (strcmp(dot, ".mp3") == 0)
            return "audio/mpeg";
        if (strcmp(dot, ".ogg") == 0)
            return "application/ogg";
        if (strcmp(dot, ".pac") == 0)
            return "application/x-ns-proxy-autoconfig";
    
        return "text/plain; charset=utf-8";
    }
    const char *get_file_type(const char *name)

    》2)如果文件不存在,提示404——提前准备好404.html文件

    在http_request更改如下:

    1     if(ret == -1)
    2     {
    3         // show 404
    4         send_respond_head(cfd, 404, "File Not Found", ".html", -1);
    5         send_file(cfd, "404.html");//需要提前写好一个404.html的网页
    6     }

    》3)浏览器一直请求,一直转圈?

    原因请求完数据,还在等待请求小图标。

    解决:在代码目录下放个小的图标,名字固定favicon.ico,就可以显示图标。

    》更改后代码为:(epoll_server.c)

      1 #include <stdio.h>
      2 #include <unistd.h>
      3 #include <stdlib.h>
      4 #include <sys/types.h>
      5 #include <string.h>
      6 #include <sys/epoll.h>
      7 #include <arpa/inet.h>
      8 #include <fcntl.h>
      9 #include <dirent.h>
     10 #include <sys/stat.h>
     11 #include <ctype.h>
     12 #include "epoll_server.h"
     13 
     14 #define MAXSIZE 2000
     15 
     16 void epoll_run(int port)
     17 {
     18     // 创建一个epoll树的根节点
     19     int epfd = epoll_create(MAXSIZE);
     20     if(epfd == -1)
     21     {
     22         perror("epoll_create error");
     23         exit(1);
     24     }
     25 
     26     // 添加要监听的节点
     27     // 先添加监听lfd
     28     int lfd = init_listen_fd(port, epfd);
     29 
     30     // 委托内核检测添加到树上的节点
     31     struct epoll_event all[MAXSIZE];
     32     while(1)
     33     {
     34         int ret = epoll_wait(epfd, all, MAXSIZE, -1);
     35         if(ret == -1)
     36         {
     37             perror("epoll_wait error");
     38             exit(1);
     39         }
     40 
     41         // 遍历发生变化的节点
     42         for(int i=0; i<ret; ++i)
     43         {
     44             // 只处理读事件, 其他事件默认不处理
     45             struct epoll_event *pev = &all[i];
     46             if(!(pev->events & EPOLLIN))
     47             {
     48                 // 不是读事件
     49                 continue;
     50             }
     51 
     52             if(pev->data.fd == lfd)
     53             {
     54                 // 接受连接请求
     55                 do_accept(lfd, epfd);
     56             }
     57             else
     58             {
     59                 // 读数据
     60                 do_read(pev->data.fd, epfd);
     61             }
     62         }
     63     }
     64 }
     65 
     66 // 读数据
     67 void do_read(int cfd, int epfd)
     68 {
     69     // 将浏览器发过来的数据, 读到buf中 
     70     char line[1024] = {0};
     71     // 读请求行
     72     int len = get_line(cfd, line, sizeof(line));
     73     if(len == 0)
     74     {
     75         printf("客户端断开了连接...
    ");
     76         // 关闭套接字, cfd从epoll上del
     77         disconnect(cfd, epfd);         
     78     }
     79     else
     80     {
     81         printf("请求行数据: %s", line);
     82         printf("============= 请求头 ============
    ");
     83         // 还有数据没读完
     84         // 继续读
     85         while(len)
     86         {
     87             char buf[1024] = {0};
     88             len = get_line(cfd, buf, sizeof(buf));
     89             printf("-----: %s", buf);
     90         }
     91         printf("============= The End ============
    ");
     92     }
     93 
     94     // 请求行: get /xxx http/1.1
     95     // 判断是不是get请求
     96     if(strncasecmp("get", line, 3) == 0)//strncasecmp比较前3个字符且不区分大小写
     97     {
     98         // 处理http请求
     99         http_request(line, cfd);
    100         // 关闭套接字, cfd从epoll上del
    101         disconnect(cfd, epfd);         
    102     }
    103 }
    104 
    105 // 断开连接的函数
    106 void disconnect(int cfd, int epfd)
    107 {
    108     int ret = epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
    109     if(ret == -1)
    110     {
    111         perror("epoll_ctl del cfd error");
    112         exit(1);
    113     }
    114     close(cfd);
    115 }
    116 
    117 // http请求处理
    118 void http_request(const char* request, int cfd)
    119 {
    120     // 拆分http请求行
    121     // get /xxx http/1.1
    122     char method[12], path[1024], protocol[12];
    123     sscanf(request, "%[^ ] %[^ ] %[^ ]", method, path, protocol);
    124 
    125     printf("method = %s, path = %s, protocol = %s
    ", method, path, protocol);
    126 
    127     // 转码 将不能识别的中文乱码 - > 中文
    128     // 解码 %23 %34 %5f
    129     decode_str(path, path);
    130         // 处理path  /xx
    131         // 去掉path中的/
    132         char* file = path+1;
    133     // 如果没有指定访问的资源, 默认显示资源目录中的内容
    134     if(strcmp(path, "/") == 0)
    135     {
    136         // file的值, 资源目录的当前位置
    137         file = "./";
    138     }
    139 
    140     // 获取文件属性
    141     struct stat st;
    142     int ret = stat(file, &st);
    143     if(ret == -1)
    144     {
    145         // show 404
    146         send_respond_head(cfd, 404, "File Not Found", ".html", -1);
    147         send_file(cfd, "404.html");//需要提前写好一个404.html的网页
    148     }
    149 
    150     // 判断是目录还是文件
    151     // 如果是目录
    152     if(S_ISDIR(st.st_mode))
    153     {
    154         // 发送头信息
    155         send_respond_head(cfd, 200, "OK", get_file_type(".html"), -1);
    156         // 发送目录信息
    157         send_dir(cfd, file);
    158     }
    159     else if(S_ISREG(st.st_mode))
    160     {
    161         // 文件
    162         // 发送消息报头
    163         send_respond_head(cfd, 200, "OK", get_file_type(file), st.st_size);
    164         // 发送文件内容
    165         send_file(cfd, file);
    166     }
    167 }
    168 
    169 // 发送目录内容
    170 void send_dir(int cfd, const char* dirname)
    171 {
    172     // 拼一个html页面<table></table>
    173     char buf[4094] = {0};
    174 
    175     sprintf(buf, "<html><head><title>目录名: %s</title></head>", dirname);
    176     sprintf(buf+strlen(buf), "<body><h1>当前目录: %s</h1><table>", dirname);
    177 
    178     char enstr[1024] = {0};
    179     char path[1024] = {0};
    180     // 目录项二级指针
    181     struct dirent** ptr;
    182     int num = scandir(dirname, &ptr, NULL, alphasort);
    183     // 遍历
    184     for(int i=0; i<num; ++i)
    185     {
    186         char* name = ptr[i]->d_name;
    187 
    188         // 拼接文件的完整路径
    189         sprintf(path, "%s/%s", dirname, name);
    190         printf("path = %s ===================
    ", path);
    191         struct stat st;
    192         stat(path, &st);
    193 
    194         encode_str(enstr, sizeof(enstr), name);
    195         // 如果是文件
    196         if(S_ISREG(st.st_mode))
    197         {
    198             sprintf(buf+strlen(buf), 
    199                     "<tr><td><a href="%s">%s</a></td><td>%ld</td></tr>",
    200                     enstr, name, (long)st.st_size);
    201         }
    202         // 如果是目录
    203         else if(S_ISDIR(st.st_mode))
    204         {
    205             sprintf(buf+strlen(buf), 
    206                     "<tr><td><a href="%s/">%s/</a></td><td>%ld</td></tr>",
    207                     enstr, name, (long)st.st_size);
    208         }
    209         send(cfd, buf, strlen(buf), 0);
    210         memset(buf, 0, sizeof(buf));
    211         // 字符串拼接
    212     }
    213 
    214     sprintf(buf+strlen(buf), "</table></body></html>");
    215     send(cfd, buf, strlen(buf), 0);
    216 
    217     printf("dir message send OK!!!!
    ");
    218 #if 0
    219     // 打开目录
    220     DIR* dir = opendir(dirname);
    221     if(dir == NULL)
    222     {
    223         perror("opendir error");
    224         exit(1);
    225     }
    226 
    227     // 读目录
    228     struct dirent* ptr = NULL;
    229     while( (ptr = readdir(dir)) != NULL )
    230     {
    231         char* name = ptr->d_name;
    232     }
    233     closedir(dir);
    234 #endif
    235 }
    236 
    237 // 发送响应头
    238 void send_respond_head(int cfd, int no, const char* desp, const char* type, long len)
    239 {
    240     char buf[1024] = {0};
    241     // 状态行
    242     sprintf(buf, "http/1.1 %d %s
    ", no, desp);
    243     send(cfd, buf, strlen(buf), 0);
    244     // 消息报头
    245     sprintf(buf, "Content-Type:%s
    ", type);
    246     sprintf(buf+strlen(buf), "Content-Length:%ld
    ", len);
    247     send(cfd, buf, strlen(buf), 0);
    248     // 空行
    249     send(cfd, "
    ", 2, 0);
    250 }
    251 
    252 // 发送文件
    253 void send_file(int cfd, const char* filename)
    254 {
    255     // 打开文件
    256     int fd = open(filename, O_RDONLY);
    257     if(fd == -1)
    258     {
    259         // show 404
    260         return;
    261     }
    262 
    263     // 循环读文件
    264     char buf[4096] = {0};
    265     int len = 0;
    266     while( (len = read(fd, buf, sizeof(buf))) > 0 )
    267     {
    268         // 发送读出的数据
    269         send(cfd, buf, len, 0);
    270     }
    271     if(len == -1)
    272     {
    273         perror("read file error");
    274         exit(1);
    275     }
    276 
    277     close(fd);
    278 }
    279 
    280 // 解析http请求消息的每一行内容
    281 int get_line(int sock, char *buf, int size)
    282 {
    283     int i = 0;
    284     char c = '';
    285     int n;
    286     while ((i < size - 1) && (c != '
    '))
    287     {
    288         n = recv(sock, &c, 1, 0);
    289         if (n > 0)
    290         {
    291             if (c == '
    ')
    292             {
    293                 n = recv(sock, &c, 1, MSG_PEEK);
    294                 if ((n > 0) && (c == '
    '))
    295                 {
    296                     recv(sock, &c, 1, 0);
    297                 }
    298                 else
    299                 {
    300                     c = '
    ';
    301                 }
    302             }
    303             buf[i] = c;
    304             i++;
    305         }
    306         else
    307         {
    308             c = '
    ';
    309         }
    310     }
    311     buf[i] = '';
    312 
    313     return i;
    314 }
    315 
    316 // 接受新连接处理
    317 void do_accept(int lfd, int epfd)
    318 {
    319     struct sockaddr_in client;
    320     socklen_t len = sizeof(client);
    321     int cfd = accept(lfd, (struct sockaddr*)&client, &len);
    322     if(cfd == -1)
    323     {
    324         perror("accept error");
    325         exit(1);
    326     }
    327 
    328     // 打印客户端信息
    329     char ip[64] = {0};
    330     printf("New Client IP: %s, Port: %d, cfd = %d
    ",
    331            inet_ntop(AF_INET, &client.sin_addr.s_addr, ip, sizeof(ip)),
    332            ntohs(client.sin_port), cfd);
    333 
    334     // 设置cfd为非阻塞
    335     int flag = fcntl(cfd, F_GETFL);
    336     flag |= O_NONBLOCK;
    337     fcntl(cfd, F_SETFL, flag);
    338 
    339     // 得到的新节点挂到epoll树上
    340     struct epoll_event ev;
    341     ev.data.fd = cfd;
    342     // 边沿非阻塞模式
    343     ev.events = EPOLLIN | EPOLLET;
    344     int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
    345     if(ret == -1)
    346     {
    347         perror("epoll_ctl add cfd error");
    348         exit(1);
    349     }
    350 }
    351 
    352 int init_listen_fd(int port, int epfd)
    353 {
    354     // 创建监听的套接字
    355     int lfd = socket(AF_INET, SOCK_STREAM, 0);
    356     if(lfd == -1)
    357     {
    358         perror("socket error");
    359         exit(1);
    360     }
    361 
    362     // lfd绑定本地IP和port
    363     struct sockaddr_in serv;
    364     memset(&serv, 0, sizeof(serv));
    365     serv.sin_family = AF_INET;
    366     serv.sin_port = htons(port);
    367     serv.sin_addr.s_addr = htonl(INADDR_ANY);
    368 
    369     // 端口复用
    370     int flag = 1;
    371     setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));
    372     int ret = bind(lfd, (struct sockaddr*)&serv, sizeof(serv));
    373     if(ret == -1)
    374     {
    375         perror("bind error");
    376         exit(1);
    377     }
    378 
    379     // 设置监听
    380     ret = listen(lfd, 64);
    381     if(ret == -1)
    382     {
    383         perror("listen error");
    384         exit(1);
    385     }
    386 
    387     // lfd添加到epoll树上
    388     struct epoll_event ev;
    389     ev.events = EPOLLIN;
    390     ev.data.fd = lfd;
    391     ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
    392     if(ret == -1)
    393     {
    394         perror("epoll_ctl add lfd error");
    395         exit(1);
    396     }
    397 
    398     return lfd;
    399 }
    400 
    401 // 16进制数转化为10进制
    402 int hexit(char c)
    403 {
    404     if (c >= '0' && c <= '9')
    405         return c - '0';
    406     if (c >= 'a' && c <= 'f')
    407         return c - 'a' + 10;
    408     if (c >= 'A' && c <= 'F')
    409         return c - 'A' + 10;
    410 
    411     return 0;
    412 }
    413 
    414 /*
    415  *  这里的内容是处理%20之类的东西!是"解码"过程。
    416  *  %20 URL编码中的‘ ’(space)
    417  *  %21 '!' %22 '"' %23 '#' %24 '$'
    418  *  %25 '%' %26 '&' %27 ''' %28 '('......
    419  *  相关知识html中的‘ ’(space)是&nbsp
    420  */
    421 void encode_str(char* to, int tosize, const char* from)
    422 {
    423     int tolen;
    424 
    425     for (tolen = 0; *from != '' && tolen + 4 < tosize; ++from) 
    426     {
    427         if (isalnum(*from) || strchr("/_.-~", *from) != (char*)0) //数字或/_.-~这几个特殊字符不需要转
    428         {
    429             *to = *from;
    430             ++to;
    431             ++tolen;
    432         } 
    433         else 
    434         {
    435             sprintf(to, "%%%02x", (int) *from & 0xff);//前面%%相当于一个%,%02x为两位的十六进制,最后得到如:%23,%04
    436             //把任何一个字符转为一个十六进制数:(int) *from & 0xff
    437             to += 3;//一个中文占3个字节,指针后移
    438             tolen += 3;
    439         }
    440 
    441     }
    442     *to = '';
    443 }
    444 
    445 //“编码”,用作回写浏览器的时候,将除字母数字及/_.-~以外的字符转义后回写。
    446 void decode_str(char *to, char *from)
    447 {
    448     for ( ; *from != ''; ++to, ++from  ) 
    449     {
    450         if (from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2])) //isxdigit判断参数是否为16进制数字,是返回非零,否则返回0
    451         { 
    452             //依次判断from中 %20 三个字符
    453             *to = hexit(from[1])*16 + hexit(from[2]);//16进制转为10进制
    454             //移动已经处理的两个字符(%21指针指向1),表达式3的++from还会再向后移一个字符
    455             from += 2;                      
    456         } 
    457         else
    458         {
    459             *to = *from;
    460 
    461         }
    462 
    463     }
    464     *to = '';
    465 
    466 }
    467 
    468 // 通过文件名获取文件的类型
    469 const char *get_file_type(const char *name)
    470 {
    471     char* dot;
    472 
    473     // 自右向左查找‘.’字符, 如不存在返回NULL
    474     dot = strrchr(name, '.');   
    475     if (dot == NULL)
    476         return "text/plain; charset=utf-8";
    477     if (strcmp(dot, ".html") == 0 || strcmp(dot, ".htm") == 0)
    478         return "text/html; charset=utf-8";
    479     if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0)
    480         return "image/jpeg";
    481     if (strcmp(dot, ".gif") == 0)
    482         return "image/gif";
    483     if (strcmp(dot, ".png") == 0)
    484         return "image/png";
    485     if (strcmp(dot, ".css") == 0)
    486         return "text/css";
    487     if (strcmp(dot, ".au") == 0)
    488         return "audio/basic";
    489     if (strcmp( dot, ".wav" ) == 0)
    490         return "audio/wav";
    491     if (strcmp(dot, ".avi") == 0)
    492         return "video/x-msvideo";
    493     if (strcmp(dot, ".mov") == 0 || strcmp(dot, ".qt") == 0)
    494         return "video/quicktime";
    495     if (strcmp(dot, ".mpeg") == 0 || strcmp(dot, ".mpe") == 0)
    496         return "video/mpeg";
    497     if (strcmp(dot, ".vrml") == 0 || strcmp(dot, ".wrl") == 0)
    498         return "model/vrml";
    499     if (strcmp(dot, ".midi") == 0 || strcmp(dot, ".mid") == 0)
    500         return "audio/midi";
    501     if (strcmp(dot, ".mp3") == 0)
    502         return "audio/mpeg";
    503     if (strcmp(dot, ".ogg") == 0)
    504         return "application/ogg";
    505     if (strcmp(dot, ".pac") == 0)
    506         return "application/x-ns-proxy-autoconfig";
    507 
    508     return "text/plain; charset=utf-8";
    509 }

    >epoll_server.h

     1 #ifndef _EPOLL_SERVER_H
     2 #define _EPOLL_SERVER_H
     3 
     4 int init_listen_fd(int port, int epfd);
     5 void epoll_run(int port);
     6 void do_accept(int lfd, int epfd);
     7 void do_read(int cfd, int epfd);
     8 int get_line(int sock, char *buf, int size);
     9 void disconnect(int cfd, int epfd);
    10 void http_request(const char* request, int cfd);
    11 void send_respond_head(int cfd, int no, const char* desp, const char* type, long len);
    12 void send_file(int cfd, const char* filename);
    13 void send_dir(int cfd, const char* dirname);
    14 void encode_str(char* to, int tosize, const char* from);
    15 void decode_str(char *to, char *from);
    16 const char *get_file_type(const char *name);
    17 
    18 #endif

    >main.c

     1 #include <stdio.h>
     2 #include <unistd.h>
     3 #include <stdlib.h>
     4 #include "epoll_server.h"
     5 
     6 int main(int argc, const char* argv[])
     7 {
     8     if(argc < 3)
     9     {
    10         printf("eg: ./a.out port path
    ");
    11         exit(1);
    12     }
    13 
    14     // 端口
    15     int port = atoi(argv[1]);
    16     // 修改进程的工作目录, 方便后续操作
    17     int ret = chdir(argv[2]);
    18     if(ret == -1)
    19     {
    20         perror("chdir error");
    21         exit(1);
    22     }
    23     
    24     // 启动epoll模型 
    25     epoll_run(port);
    26 
    27     return 0;
    28 }

    》程序测试:

    >gcc server.c main.c -o server

    >./server 8989 /home/wang/Documents

    (在win10端打开浏览器,输入192.168.5.103:8989,此处192.168.5.103为服务器端IP)


    》scanf和正则表达式:

    》scanf函数:

    》正则表达式:

    学习网址:

    1)https://deerchao.cn/tutorials/regex/regex.htm

    2)https://www.jb51.net/tools/regexsc.htm

    》正则表达式速查表:

    》常用正则表达式:


    》scandir:

    》数据转码:

    》安装:

    >sudo apt-get install unicode

    安装完成后

    >unicode 中

    在学习Linux高并发web服务器开发总结了笔记,并分享出来。有问题请及时联系博主:Alliswell_WP,转载请注明出处。

  • 相关阅读:
    第02组 Alpha冲刺 (6/6)
    面向对象分析设计考试复习【历年卷】
    第02组 Alpha冲刺 (5/6)
    第02组 Alpha冲刺 (4/6)
    第02组 Alpha冲刺 (3/6)
    第02组 Alpha冲刺 (2/6)
    第02组 Alpha冲刺 (1/6)
    linux内核结构
    从别人的博客学习
    递归的认识
  • 原文地址:https://www.cnblogs.com/Alliswell-WP/p/CPlusPlus_Linux_18.html
Copyright © 2011-2022 走看看