zoukankan      html  css  js  c++  java
  • 游双Linux高性能服务器编程第八章代码解读

    游双Linux高性能服务器编程第八章代码解读

    功能:Http请求的读取和分析

    代码模块:

    代码模块一:http.c

      1 #include <stdio.h>
      2 #include <stdlib.h>
      3 #include <string.h>
      4 #include <libgen.h>
      5 #include <sys/types.h>
      6 #include <sys/socket.h>
      7 #include <netinet/in.h>
      8 #include <arpa/inet.h>
      9 #include <unistd.h>
     10 #include <assert.h>
     11 #include <errno.h>
     12 
     13 #include "http.h"
     14 
     15 /* 读缓冲区大小 */
     16 #define BUFSIZE 4096
     17 /* 为了简化问题,我们没有给客户端发送一个完整的HTTP应答报文,而只是根据服务器的处理结果发送如下成功或失败的信息 */
     18 static const char *szret[] = {"I get a correct result\n", "Something wrong\n"};
     19 
     20 /* main函数 */
     21 int main(int argc, char *argv[]){
     22     if (argc < 3) {
     23         fprintf(stderr, "Usage: %s <ip_address> <port_number>\n", basename(argv[0]));
     24         return 1;
     25     }
     26     const char *ip = argv[1];
     27     int port = atoi(argv[2]);
     28     struct sockaddr_in address;
     29     address.sin_family = AF_INET;
     30     inet_pton(AF_INET, ip, &address.sin_addr);
     31     address.sin_port = htons(port);
     32 
     33     int sock = socket(AF_INET, SOCK_STREAM, 0);
     34     assert(sock >= 0);
     35 
     36     int ret = bind(sock, (struct sockaddr *)&address, sizeof(address));
     37     assert(ret != -1);
     38 
     39     ret = listen(sock, 5);
     40     assert(ret != -1);
     41 
     42     struct sockaddr_in client;
     43     socklen_t client_addrlength = sizeof(client);
     44     int connfd = accept(sock, (struct sockaddr *)&client, &client_addrlength);
     45     if (connfd < 0) {
     46         perror("accept()");
     47     }
     48     else {
     49         char buffer[BUFSIZE];  /* 读缓存区,大小为4096 */
     50         memset(buffer, '\0', sizeof(char) * BUFSIZE);
     51         int data_read = 0;
     52         int read_index = 0;    /* 当前已经读取了多少个字节的客户数据 */
     53         int checked_index = 0; /* 当前已经分析完了多少字节的客户数据 */
     54         int start_line = 0;    /* 行在buffer中的起始位置 */
     55         /* 设置主状态机的初始状态 */
     56         enum CHECK_STATE checkstate = CHECK_STATE_REQUESTLINE;
     57         while (1) { /* 循环读取客户数据并分析之 */
     58             data_read = recv(connfd, buffer + read_index, BUFSIZE - read_index, 0);
     59             if (data_read == -1) {
     60                 perror("recv()");
     61                 break;
     62             } else if (data_read == 0) {
     63                 printf("remote client has closed the connection\n");
     64                 break;
     65             }
     66             read_index += data_read;
     67             /* 分析目前已经获得的所有客户数据 */
     68             enum HTTP_CODE result = parse_content(buffer, &checked_index, &checkstate, read_index, &start_line);
     69             if (result == NO_REQUEST) { /* 尚未得到一个完整的HTTP请求 */
     70                 continue;
     71             } else if (result == GET_REQUEST) { /* 得到一个完整的、正确的HTTP请求 */
     72                 send(connfd, szret[0], strlen(szret[0]), 0);
     73                 break;
     74             } else { /* 其他情况表示发生错误 */
     75                 send(connfd, szret[1], strlen(szret[1]), 0);
     76                 break;
     77             }
     78         }
     79         close(connfd);
     80     }
     81     close(sock);
     82     return 0;
     83 }
     84 
     85 /* 分析HTTP请求的入口函数 */
     86 enum HTTP_CODE parse_content(char *buffer, int *checked_index, enum CHECK_STATE *checkedstate, int read_index, int *start_line){
     87     enum LINE_STATUS linestatus = LINE_OK; /* 记录当前行的读取状态 */
     88     enum HTTP_CODE retcode = NO_REQUEST;   /* 记录HTTP请求的处理结果 */
     89     /* 主状态机,用于从buffer中取出所有完整的行 */
     90     while ((linestatus = parse_line(buffer, checked_index, read_index)) == LINE_OK) {
     91         char *temp = buffer + (*start_line); /* start_line是行在buffer中的起始位置 */
     92         (*start_line) = (*checked_index);    /* 记录下一行的起始位置 */
     93         switch ((*checkedstate)) {
     94             case CHECK_STATE_REQUESTLINE: { /* 第一个状态,分析请求行 */
     95                 retcode = parse_requestline(temp, checkedstate);
     96                 if (retcode == BAD_REQUEST) return BAD_REQUEST;
     97                 break;
     98             }
     99             case CHECK_STATE_HEADER: {      /* 第二个状态,分析头部字段 */
    100                 retcode = parse_headers(temp);
    101                 if (retcode == BAD_REQUEST) {
    102                     return BAD_REQUEST;
    103                 } else if (retcode == GET_REQUEST) {
    104                     return GET_REQUEST;
    105                 }
    106                 break;
    107             }
    108             default: {
    109                 return INTERNAL_ERROR;
    110             }
    111         }
    112     }
    113     /* 若没有读取到一个完整的行,则表示还需要继续读取客户数据才能进一步分析 */
    114     if (linestatus == LINE_OPEN) return NO_REQUEST;
    115 
    116     return BAD_REQUEST;
    117 }
    118 
    119 /* 从状态机,用于解析出一行内容 */
    120 enum LINE_STATUS parse_line(char *buffer, int *checked_index, int read_index){
    121     char temp;
    122     /* checked_index 指向buffer(应用程序的读缓冲区)中当前正在分析的字节,read_index指向buffer中客户数据的尾部的下一个字节
    123      * buffer中第0~checked_index字节都已经分析完毕
    124      */
    125     for (;(*checked_index) < read_index; ++(*checked_index)) {
    126         /* 获取当前需要分析的字节 */
    127         temp = buffer[(*checked_index)];
    128         /* 如果当前的字节是'\r',即回车符,则说明可能读取到一个完整的行 */
    129         if (temp == '\r') {
    130             /* 如果'\r'字符碰巧是目前buffer中的最后一个已经被读入的客户数据,
    131              * 那么这次分析没有读取到一个完整的行,返回LINE_OPEN表示还需要继续读取客户数据才能进一步分析
    132             */
    133             if ((*checked_index) + 1 == read_index) {
    134                 return LINE_OPEN;
    135             } else if (buffer[(*checked_index) + 1] == '\n') { /* 如果下一个字符是'\n',则说明我们成功读取到了一个完整的行 */
    136                 buffer[(*checked_index)++] = '\0';
    137                 buffer[(*checked_index)++] = '\0';
    138                 return LINE_OK;
    139             }
    140             /* 否则的话,说明客户发送的HTTP请求存在语法问题 */
    141             return LINE_BAD;
    142         } else if (temp == '\n') {
    143             /* 如果当前的字节是'\n',即换行符,则也说明可能读取到一个完整的行 */
    144             if (((*checked_index) > 1) && buffer[(*checked_index) - 1] == '\r') {
    145                 buffer[(*checked_index) - 1] = '\0';
    146                 buffer[(*checked_index)++] = '\0';
    147                 return LINE_OK;
    148             }
    149             return LINE_BAD;
    150         }
    151     }
    152     return LINE_OPEN;
    153 }
    154 
    155 enum HTTP_CODE parse_requestline(char *temp, enum CHECK_STATE *checkstate){
    156     char *url = strpbrk(temp, " \t");
    157     /* 如果请求行中没有空白字符或者'\t'字符,则HTTP请求必有问题 */
    158     if (!url) {
    159         return BAD_REQUEST;
    160     }
    161     /* 后缀++优先级更高,意味着运算符用于pt,而不是*pt,因此对指针递增
    162      * 然而后缀运算符意味着将对原来的地址而不是递增后的新地址解除引用
    163      */
    164     *url++ = '\0';
    165 
    166     char *method = temp;
    167     /* strcasecmp比较时忽略大小写 */
    168     if (strcasecmp(method, "GET") == 0) { /* 仅支持GET方法 */
    169         printf("The request method is GET\n");
    170     } else {
    171         return BAD_REQUEST;
    172     }
    173     /* strspn() 函数用来计算字符串 str 中连续有几个字符都属于字符串 accept */
    174     url += strspn(url, " \t");
    175     /* C语言strpbrk()函数:返回两个字符串中首个相同字符的位置 */
    176     char *version = strpbrk(url, " \t");
    177     if (!version) {
    178         return BAD_REQUEST;
    179     }
    180     *version++ = '\0';
    181     version += strspn(version, " \t");
    182     /* 仅支持HTTP/1.1 */
    183     if (strcasecmp(version, "HTTP/1.1") != 0) {
    184         return BAD_REQUEST;
    185     }
    186     /* 检查URL是否合法
    187      * 例如GET https://blog.csdn.net/u010256388/article/details/68491509 HTTP/1.1
    188      * 我的理解是,先去掉"http://"(如果有的话),再定位到"/u010256388/article/details/68491509"
    189     */
    190     if (strncasecmp(url, "http://", 7) == 0) {
    191         url += 7;
    192         url = strchr(url, '/');
    193     }
    194     if (!url || url[0] != '/') {
    195         return BAD_REQUEST;
    196     }
    197     printf("The request URL is: %s\n", url);
    198     /* 请求行处理完毕,状态转移到头部字段的解析 */
    199     *checkstate = CHECK_STATE_HEADER;
    200     return NO_REQUEST;
    201 }
    202 
    203 /* 分析头部字段 */
    204 enum HTTP_CODE parse_headers(char *temp){
    205     /* 遇到一个空行,说明我们得到一个正确的HTTP请求 */
    206     if (temp[0] == '\0') {
    207         return GET_REQUEST;
    208     } else if (strncasecmp(temp, "Host:", 5) == 0) { /* 处理"HOST"头部字段 */
    209         temp += 5;
    210         temp += strspn(temp, " \t");
    211         printf("The request host is: %s\n", temp);
    212     } else { /* 其他头部字段都不处理 */
    213         printf("I can not handle this header\n");
    214     }
    215     return NO_REQUEST;
    216 }
    View Code

    代码模块二:http.h

    #ifndef HTTP_H__
    #define HTTP_H__
    /* 主状态机的两种可能状态,分别表述:当前正在分析请求行,当前正在分析头部字段 */
    enum CHECK_STATE {CHECK_STATE_REQUESTLINE = 0, CHECK_STATE_HEADER};
    /* 从状态机的三种可能状态,即行的读取状态,分别表示:读取到一个完整的行、行出错和行数据尚且不完整 */
    enum LINE_STATUS {LINE_OK = 0, LINE_BAD, LINE_OPEN};
    /* 服务器处理HTTP请求的结果:NO_REQUEST表示请求不完整,需要继续读取客户数据;GET_REQUEST表示获得了一个完整的客户请求;
     * BAD_REQUEST表示客户请求有语法错误;FORBIDDEN_REQUEST表示客户对资源没有足够的访问权限;INTERNAL_ERROR表示服务器内部错误;
     * CLOSED_CONNECTION表示客户端已经关闭连接了 
     */
    enum HTTP_CODE {NO_REQUEST, GET_REQUEST, BAD_REQUEST, FORBIDDEN_REQUEST, INTERNAL_ERROR, CLOSED_CONNECTION};
    /* 分析HTTP请求的入口函数 */
    enum HTTP_CODE parse_content(char *buffer, int *checked_index, enum CHECK_STATE *checkedstate, int read_index, int *start_line);
    /* 从状态机,用于解析出一行内容 */
    enum LINE_STATUS parse_line(char *buffer, int *checked_index, int read_index);
    /* 分析请求行 */
    enum HTTP_CODE parse_requestline(char *temp, enum CHECK_STATE *checkstate);
    /* 分析头部字段 */
    enum HTTP_CODE parse_headers(char *temp);
    #endif

     

    NO8《http读取和分析》的运行命令:

    第一个终端:

     

    第二个终端:

     

    数据:test.html

    GET / HTTP/1.1 
    Host: hackr.jp
    User-Agent: Moazilla/5.0
    雪儿言
  • 相关阅读:
    微信小程序入门
    rem js相关
    移动端rem.js使用方法
    手机访问PC网站自动跳转到手机版
    当 return 遇到 try
    Ubuntu apt 使用代理
    shell 十进制数字转十六进制字符串并将结果保存到变量
    (二)一起学 Java Collections Framework 源码之 AbstractCollection
    (一)一起学 Java Collections Framework 源码之 概述
    解决 meld 出现 locale.setlocale(locale.LC_ALL,'') 失败的问题
  • 原文地址:https://www.cnblogs.com/weixq351/p/15745834.html
Copyright © 2011-2022 走看看