跳转至

有限状态机

逻辑单元内部的编程方式

状态独立的有限状态机

状态独立的有限状态机
STATE_MACHINE(Package _pack) {
    PackageType _type = _pack.getType();
    switch(_type) {
        case type_A:
            process_package_A(_pack);
            break;
        case type_B:
            process_package_B(_pack);
            break;
    }
}

带状态转移的有限状态机

带状态转移的有限状态机
STATE_MACHINE() {
    State cur_state = type_A; //先定义一开始的状态
    while(cur_state != type_C) {
        Package _pack = getNewPackage(); // 获得新的数据包
        switch(cur_state) {
            case type_A:
                process_package_A(_pack);
                cur_state = type_B; // 状态转移
                break;
            case type_B:
                process_package_B(_pack);
                cur_state = type_C; // 状态转移
                break;
        }        
    }
} //type_A: 状态机的开始状态;type_C:状态机的结束状态

http请求的读取与分析

  • http头部结束:一对回车换行符
  • 第一次读操作没有读入整个http头部,即没有遇到空行(一对回车换行符):等待客户继续写数据再次读入
  • 每完成一次读操作,分析数据中是否有空行
  • 寻找空行的同时,完成对http头部的分析
http头部读取与分析
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<fcntl.h>

#define BUFFER_SIZE 4096 //读缓冲区大小
//主状态机状态
enum CHECK_STATE {
    CHECK_STATE_REQUESTLINE = 0, //当前正在分析请求行
    CHECK_STATE_HEADER //当前正在分析头部信息
};

//从状态机的状态
enum LINE_STAET {
    LINE_OK = 0, //完整行
    LINE_BAD, //行出错
    LINE_OPEN //行数据尚不完整
};

//服务器http的请求结果
enum HTTP_CODE {
    NO_REQUEST, //请求不完整,继续读客户数据
    GET_REQUEST, //获得一个完整的请求
    BAD_REQUEST, //客户请求有错误
    FORBIDDEN_REQUEST, //客户对资源没有访问权限
    INTERNAL_ERROR, //服务器内部错误
    CLOSED_CONNECTION //客户已关闭连接
}


//接收正确与错误的信息
static const char* szert[] = {"I get a correct result\n", 
                             "Something wrong\n"};



//从状态机:解析一行内容
LINE_STATE parse_line(char* buffer, int& checked_index, int& read_index) {
    char tmp;
    // checked_index 指向buffer 中当前正在分析的字节
    // read_index 指向buffer数据尾部的下一字节
    // 0~checked_index 字节已经分析完
    // checked_index ~ (read_index-1) 字节下个循环分析
    for(; checked_index < read_index; ++checked_index) {
        //获得当前要分析的字节
        tmp = buffer[checked_index];
        //若当前字符是“\r”,可能读到完整的一个行,继续分析
        if(tmp = '\r') {
            //恰巧是读缓冲区中最后一个读入的数据,不是完整的一行
            if(checked_index + 1 == read_index) return LINE_OPEN;
            //如果下一个字符是“\n”,说明是完整的一行
            else if(buffer[checked_index + 1] == '\n')  {
                buffer[checked_index++] = '\0';
                buffer[checked_index++] = '\0';
                return LINE_OK;
            }

            //否则当成出错
            return LINE_BAD;
        }

        //如果读到“\n”,可能读到完整的一行,继续分析
        else if(tmp == '\n') {
            //上一个字节是“\r”,则说明读到完整的一行
            if((checked_index > 1) && buffer[checked - 1] == '\r') {
                buffer[checked - 1] = '\0';
                buffer[checked++] = '0';
                return LINE_OK;
            }

            return LINE_BAD;
        }
    }

    // 所有内容分析完,没有“\r”
    return LINE_OPEN;
}

//分析请求行
HTTP_CODE parse_requestline(char* tmp, CHECK_STATE& checkstate) {
    char* url = strpbrk(tmp, "\t");
    //如果请求行没有空白字符或者“\t”,则http请求有问题
    if(!url) {
        return BAD_REQUEST;
    }

    *url++ = '\0';

    char* methon = tmp;

    if(strcasecmp(methon, "GET") == 0) //仅支持“GET”方法
    {
        printf("The request is GET\n");
    } else {
        return BAD_REQUEST;
    }

    url += strspn(url, "\t");
    char* version = strpbrk(url, "\t");
    if(!version) {
        return BAD_reQUEST;
    }
    *version += '\0';
    version += strspn(version, "\t");

    //仅支持 http/1.1
    if(strcasecmp(version, "HTTP/1.1") != 0) {
        return BAD_REQUEST;
    }

    //检查url是否合法
    if(strncasecmp(url, "http://", 7) == 0) {
        url += 7;
        url = strchr(url, '/');
    }

    if(!url || url[0] != '/') {
        return BAD_REQUEST;
    }

    printf("The request URL is: %s\n", url);

    //http请求行解析结束,状态转移到解析头部字段
    checkstate = CHECK_STATE_HEADER;
    return NO_REQUEST;
}


// 分析头部字段
HTTP_CODE parse_headers(char* tmp) {
    //遇到空行,说明是正确的http请求
    if(tmp[0] == '\0') {
        return GET_REQUEST;
    }
    else if(strncasecmp(tmp, "Host:", 5) == 0) //host头部字段
    {
        tmp += 5;
        tmp += strspn(tmp, '\t');
        printf("The request host is: %s\n", tmp);
    }
    else //其他头部字段不处理
    {
        printf("I can not handle this header\n");
    }

    return NO_REQUEST;
}




//分析http请求的入口函数
HTTP_CODE parse_content(char* buffer, int& checked_index, CHECK_STATE& checkstate, int& read_index, int& start_line) {
    //记录当前行的读取状态
    LINE_STATUS linestatus = LINE_OK;
    //记录http请求的处理如果
    HTTP_CODE rescode = NO_REQUEST;

    //主状态机:从buffer中取出所有完整的行
    while(linestatus = parse_line(buffer, checked_index, read_index) == LINE_OK) {
        char* tmp = buffer + start_line; //行在buffer中的位置(从开头处理)
        start_line = checked_index; //记录下一行的起始位置

        switch(checkstate) {
            case CHECK_STATE_REQUESTLINE:
                {
                    rescode = parse_requestline(tmp, checkstate);
                    if(rescode == BAD_REQUEST) {
                        return BAD_REQUEST;
                    }
                    break;
                }
            case CHECK_STATE_HEADER:
                {
                    rescode = parse_headers(tmp);
                    if(rescode == BAD_REUQEST) {
                        return BAD_REQUEST;
                    } else if (rescode == GET_REQUESt) {
                        return GET_REQUEST;
                    }
                    break;
                }
            default:
                {
                    return INTERNAL_ERROR;
                }
        }
    }

    //没有读到完整的一行,还要继续读取客户数据进行下一步
    if(linestatus == LINE_OPEN) {
        return NO_REUQEST;
    }
    else {
        return BAD_REQUEST;
    }
}


int main(int argc, char* argv[]) {
    if(argc != 2) {
        printf("usage: %s ip_address port_num\n",basename(argv[0]));
        return 1;
    }

    const char* ip = argv[0];
    int port = atoi(argv[1]);

    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    adderss.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &address.sin_addr);
    address.sin_port = htons(port);

    int listenfd = socket(PF_INET, SOCK_STREAM, 0);
    assert(listen == 0);

    int res = bind(listenfd, (struct sockaddr*)&address, sizeof(address));
    assert(ret != -1);

    ret = listen(listenfd, 5);
    assert(ret != -1);

    struct sockaddr_in clientfd;
    socklen_t clen = sizeof(clientfd);
    int fd = accept(listenfd, (struct sockaddr*)&clientfd, &clen);
    if(fd < 0) {
        printf("errno is: %d\n", errno);
    } else {
        char buffer[BUFFER_SIZE];
        memset(buffer, '\0', BUFFEr_SIZE);

        int data_read = 0; //已接收多少字节数据
        int checked_index = 0; //正在解析的位置
        int read_index = 0; //缓冲区末尾数据的位置
        int start_line = 0; //行在buffer中的起始位置

        //主状态机的初始状态:请求行
        CHECK_STATE checkstate = CHECK_STATE_REQUESTLINE;
        while(1) { //循环读取数据
            data_read = recv(fd, buffer + read_index, BUFFER_SIZE - read_index, 0);
            if(data_read == -1) {
                break;
            }
            else if(data_read == 0) { //对端关闭
                printf("remote client has closed\n");
                break;
            }

            read_index += data_read; //更新缓冲区中读到数据的末尾位置
            HTTP_CODE result = parse_content(buffer, checked_index, checkstate, read_index, start_line);
            if(result == NO_REQUEST) { //没有读到完整的一行,继续读取客户数据
                continue;
            }
            //请求到资源
            else if(result == GET_REQUEST) {
                send(fd, szret[0], strlen(szret[0]), 0);
                break;
            }
            //其他错误
            else {
                send(fd, szret[1], strlen(szret[1]), 0);
                break;
            }
        }
        close(fd);
    }
    close(listenfd);

    return 0;
}
  • strspn
size_t strspn ( const char * str1, const char * str2 );
  • 返回str1的初始部分的长度,该长度仅由属于str2的字符组成。

  • 搜索不包括这两个字符串的结束空字符,但在那里结束。

  • strpbrk

const char * strpbrk ( const char * str1, const char * str2 );
      char * strpbrk (       char * str1, const char * str2 );
  • 返回一个指针,指向str1中任何属于str2的字符的第一个匹配项,如果没有匹配项,则返回一个空指针。
  • 搜索不包括这两个字符串的结束空字符,但在那里结束。

  • strcasecmp

int strcasecmp( const char * str1, const char * str2 );
  • 比较参数s1 和s2 字符串,比较时会自动忽略大小写的差异
  • 返回值:若参数s1 和s2 字符串相同则返回0。s1 长度大于s2 长度则返回大于0 的值,s1 长度若小于s2 长度则返回小于0 的值

  • strchr

char *strchr(const char *str, int c);
  • 在参数 str 所指向的字符串中搜索第一次出现字符 c(一个无符号字符)的位置

http报文段

请求报文

  • 请求行
  • 请求头部
  • 空行
  • 请求数据

GET和POST(ep)

GET

GET /562f25980001b1b106000338.jpg HTTP/1.1
Host:img.mukewang.com
User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36
Accept:image/webp,image/*,*/*;q=0.8
Referer:http://www.imooc.com/
Accept-Encoding:gzip, deflate, sdch
Accept-Language:zh-CN,zh;q=0.8
空行
请求数据为空

POST

 POST / HTTP1.1
 Host:www.wrox.com
 User-Agent:Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)
 Content-Type:application/x-www-form-urlencoded
 Content-Length:40
 Connection: Keep-Alive
 空行
 name=Professional%20Ajax&publisher=Wiley

请求行

  • methon
分析请求行函数中
char* url = strpbrk(tmp, "\t");
    //如果请求行没有空白字符或者“\t”,则http请求有问题
    if(!url) {
        return BAD_REQUEST;
    }

    *url++ = '\0';

    char* methon = tmp;

    if(strcasecmp(methon, "GET") == 0) //仅支持“GET”方法
    {
        printf("The request is GET\n");
    } else {
        return BAD_REQUEST;
    }
  • strpbrk使得url指针指向tmp字符串的第一个\t,即GET之后的“\t”;并且通过*url++='\0'将“\t”置为“\0”;则下面使用strcasecmp函数比较mrthon和“GET”是否相同时,strcasecmp函数只会对比到"\0"

  • version

分析请求行函数中
    url += strspn(url, "\t");
    char* version = strpbrk(url, "\t");
    if(!version) {
        return BAD_reQUEST;
    }
    *version += '\0';
    version += strspn(version, "\t");

    //仅支持 http/1.1
    if(strcasecmp(version, "HTTP/1.1") != 0) {
        return BAD_REQUEST;
    }
  • strspn函数使得url直接跳过/562f25980001b1b106000338.jpg来到其后的"\t",因为此时需要获取http的版本号;strpbrk函数同样将HTTP/1.1的版本号获取并将其后的“\t”置为“\0”,以便于后续strcasecmp函数的对比

  • 判断是否为合法的url

分析请求行函数中
//检查url是否合法
    if(strncasecmp(url, "http://", 7) == 0) {
        url += 7;
        url = strchr(url, '/');
    }

    if(!url || url[0] != '/') {
        return BAD_REQUEST;
    }

请求头部

分析请求头函数中
HTTP_CODE parse_headers(char* tmp) {
    //遇到空行,说明是正确的http请求
    if(tmp[0] == '\0') {
        return GET_REQUEST;
    }
    else if(strncasecmp(tmp, "Host:", 5) == 0) //host头部字段
    {
        tmp += 5;
        tmp += strspn(tmp, '\t');
        printf("The request host is: %s\n", tmp);
    }
    else //其他头部字段不处理
    {
        printf("I can not handle this header\n");
    }

    return NO_REQUEST;
}

状态机状态

  • 从状态机:从状态机一开始的状态是LINE_OK,在main函数中首先调用recv函数不断读取客户数据,每次读取成功之后,都会调用parse_content函数取出数据(缓冲区)中所有完整的行;parse_content函数首先调用parse_line函数获取完整的一行,判断每读取的数据中有没有行结束符;如果当前没有行结束符,则从状态机状态置为LINE_OPEN;如果读到完整的一行数据,则将其递交给主状态机处理
  • 主状态机:如果主状态机的当前状态为CHECK_STATE_REQUESTLINE,则表示读到的是请求行,则主状态机调用check_requestline函数来处理;如果主状态机当前状态为CHECK_STATE_HEADER,则表示读到的是请求头部,则主状态机调用check_headers函数来处理。主状态机的初始状态为CHECK_STATE_REQUESTLINE,函数check_requestline调用完成之后会将主状态机状态置为CHECK_STATE_HEADER,以实现状态的转移




参考游双老师的《Linux高性能服务器编程》