有限状态机

有限状态机

有的应用层协议头部包含数据包类型字段,每种类型可以映射为逻辑单元的一种执行状态,服务器可以根据它来编写相应的处理逻辑。

感觉这个状态机就像是一个大模拟…

例子:HTTP请求的读取和分析:

很多网络协议,包括TCP协议和IP协议,都在其头部中提供头部长度字段。程序根据该字段的值就可以知道是否接收到一个完整的协议 头部。

HTTP协议并未提供这样的头部长度字段,并且其头部长度变 化也很大,可以只有十几字节,也可以有上百字节。根据协议规定, 我们判断HTTP头部结束的依据是遇到一个空行,该空行仅包含一对回 车换行符(<CR><LF>)

如果一次读操作没有读入HTTP请求的 整个头部,即没有遇到空行,那么我们必须等待客户继续写数据并再次读入。因此,我们每完成一次读操作,就要分析新读入的数据中是否有空行。不过在寻找空行的过程中,我们可以同时完成对整个HTTP 请求头部的分析(记住,空行前面还有请求行和头部域),以提高解 析HTTP请求的效率。

#include <cstdio>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <assert.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
using namespace std;

#define BUFFER_SIZE 4096

/*
主状态机:分析请求行;分析请求头部字段
*/
enum CHECK_STATE{
    CHECK_STATE_REQUESTLINE = 0,CHECK_STATE_HEADER
};

/*
从状态机:行的读取状态:读到一个完整的行、行出错、行数据尚不完整
*/
enum LINE_STATUS{
    LINE_OK = 1,LINE_BAD,LINE_OPEN
};
/*
服务器处理HTTP请求的结果
*/
enum HTTP_CODE{
    NO_REQUEST,GET_REQUEST,BAD_REQUEST,FORBIDDEN_REQUEST,INTERNAL_ERROR,CLOSED_CONNECTION
};

static const char* szret[] = {
    "I get a correct result\n","Something wrong!\n"
};

LINE_STATUS parse_line(char *buffer,int &check_index,int &read_index){
    // puts("####line");
    char temp;
/*
    checked_index指向buffer(应用程序的读缓冲区)中当前正在分析的字节, 
    read_index指向buffer中客户数据的尾部的下一字节。
    buffer中第0~checked_index 字节都已分析完毕,第checked_index~(read_index-1)字节由下面的循环挨个分析
*/
    // printf("%d %d\n",check_index,read_index);
    for(;check_index<read_index;++check_index) {
        temp = buffer[check_index];
        // printf("%c\n",temp);
        /*如果当前的字节是“\r”,即回车符,则说明可能读取到一个完整的行*/
        if(temp=='\r') {
            /*如果“\r”字符碰巧是目前buffer中的最后一个已经被读入的客户数据,
            那么这次分 析没有读取到一个完整的行,返回LINE_OPEN以表示还需要继续读取客户数据才能进一步分 析*/
            if((check_index+1)==read_index) {
                // puts("####liner");
                return LINE_OPEN;
            }
            /*如果下一个字符是“\n”,则说明我们成功读取到一个完整的行*/
            else if(buffer[check_index+1]=='\n') {
                buffer[check_index++]='\0';
                buffer[check_index++]='\0';
                // puts("####linerok");
                return LINE_OK;
            }
            /*否则的话,说明客户发送的HTTP请求存在语法问题*/
            return LINE_BAD;
        }
        /*如果当前的字节是“\n”,即换行符,则也说明可能读取到一个完整的行*/
        else if(temp=='\n') {
            if((check_index>1)&&buffer[check_index-1]=='\r') {
                buffer[check_index-1]='\0';
                buffer[check_index++]='\0';
                // puts("####linenok");
                return LINE_OK;
            }
            return LINE_BAD;
        }
    }
    /*如果所有内容都分析完毕也没遇到“\r”字符,则返回LINE_OPEN,表示还需要继续读 取客户数据才能进一步分析*/
    return LINE_OPEN;
}
/*分析请求行*/ 
HTTP_CODE parse_requestline(char *temp,CHECK_STATE &checkstate) {
    // puts("####");
    // printf("%s\n",temp);
    char *url = strpbrk(temp," \t");
    // printf("%s\n",url);
    if(!url) {
        return BAD_REQUEST;
    }
    *url++ = '\0';
    char *method = temp;
    if(strcasecmp(method,"GET")==0) {
        printf("The request method is GET!\n");
    }
    else {
        puts("verssget");
        return BAD_REQUEST;
    }
    url+=strspn(url," \t");
    char *version = strpbrk(url," \t");
    if(!version) {
        // puts("versss1");
        return BAD_REQUEST;
    }
    *version++ ='\0';
    version+=strspn(version," \t");
    if(strcasecmp(version,"HTTP/1.1")!=0) {
        puts("versss");
        return BAD_REQUEST;
    }
    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);
    checkstate = CHECK_STATE_HEADER;
    return NO_REQUEST;
}

/*分析头部字段*/ 
HTTP_CODE parse_headers(char*temp) 
{ 
    /*遇到一个空行,说明我们得到了一个正确的HTTP请求*/ 
    if(temp[0]=='\0') { 
        return GET_REQUEST; 
    } 
    else if(strncasecmp(temp,"Host:",5)==0)
    /*处理“HOST”头部字段*/ 
    { 
        temp+=5; 
        temp+=strspn(temp,"\t"); 
        printf("the request host is:%s\n",temp); 
    } 
    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)
{
    // puts("####");
    LINE_STATUS linestatus=LINE_OK;/*记录当前行的读取状态*/
    HTTP_CODE retcode=NO_REQUEST;/*记录HTTP请求的处理结果*/
    while ((linestatus=parse_line(buffer,checked_index,read_index))==LINE_OK)
    {
        /* code */
        puts("####while");
        char*temp=buffer+start_line;/*start_line是行在buffer中的起始位置*/
        start_line = checked_index;/*记录下一行的起始位置*/
        /*checkstate记录主状态机当前的状态*/ 
        switch(checkstate) { 
            case CHECK_STATE_REQUESTLINE:/*第一个状态,分析请求行*/ 
            { 
                retcode=parse_requestline(temp,checkstate); 
                if(retcode==BAD_REQUEST) { 
                    return BAD_REQUEST; 
                } 
                break; 
            } 
            case CHECK_STATE_HEADER:/*第二个状态,分析头部字段*/ 
            { 
                retcode=parse_headers(temp);
                if(retcode==BAD_REQUEST) { 
                    return BAD_REQUEST; 
                } 
                else if(retcode==GET_REQUEST) { 
                    return GET_REQUEST; 
                } 
                break; 
            } 
            default: { 
                return INTERNAL_ERROR; 
            } 
        } 
    }
    /*若没有读取到一个完整的行,则表示还需要继续读取客户数据才能进一步分析*/
    if(linestatus==LINE_OPEN) {
        return NO_REQUEST;
    }
    else {
        return BAD_REQUEST;
    }
}
int main(int argc,char *argv[])
{
    if(argc<=2) {
        printf("usage:%s ip_address port_number\n",basename(argv[0])); 
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi(argv[2]);
    struct sockaddr_in address;
    bzero(&address,sizeof(address));
    address.sin_family = AF_INET;
    address.sin_port = htons(port);
    inet_pton(AF_INET,ip,&address.sin_addr);
    int sockfd = socket(PF_INET,SOCK_STREAM,0);
    assert(sockfd>=0);
    int ret = bind(sockfd,(struct sockaddr*)&address,sizeof(address));
    assert(ret!=-1);
    ret = listen(sockfd,5);
    assert(ret!=-1);
    struct sockaddr_in client_address;
    socklen_t client_address_length = sizeof(client_address);
    int connfd = accept(sockfd,(struct sockaddr*)&client_address,&client_address_length);
    if(connfd<0) {
        printf("error is:%d\n",errno);
    }
    else {
        char buffer[BUFFER_SIZE];/*读缓冲区*/
        memset(buffer,'\0',BUFFER_SIZE);
        int data_read=0;
        int read_index = 0;
        int check_index = 0;
        int start_line = 0;
        CHECK_STATE checkstate = CHECK_STATE_REQUESTLINE;
        while (1)
        {
            data_read = recv(connfd,buffer+read_index,BUFFER_SIZE-read_index,0);
            if(data_read==-1) {
                printf("reading failed\n");
                break;
            }
            else if(data_read==0) {
                printf("remote client has closed the connection\n");
                break;
            }
            read_index+=data_read;
            HTTP_CODE result = parse_content(buffer,check_index,checkstate,read_index,start_line);
            printf("%s\n",buffer);
            if(result==NO_REQUEST) {
                // puts("NO_REQUEST");
                continue;
            }
            else if(result==GET_REQUEST) {
                puts("GET_REQUEST");
                send(connfd,szret[0],strlen(szret[0]),0);
                break;
            }
            else {
                puts("BAD_REQUEST");
                send(connfd,szret[1],strlen(szret[1]),0);
                break; 
            }
        }
        close(connfd);
    }
    close(sockfd);
    return 0;
}