Linux高性能服务器之高效编写方法(12)

追求适度,才能走向成功;人在顶峰,迈步就是下坡;身在低谷,抬足既是登高;弦,绷得太紧会断;人,思虑过度会疯;水至清无鱼,人至真无友,山至高无树;适度,不是中庸,而是一种明智的生活态度。

导读:本篇文章讲解 Linux高性能服务器之高效编写方法(12),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

有限状态机

STATE_MACHINE()
{
    State cur_state=type_A;
    while(cur_State!=type_c)
    {
        Package _pack=getNewPackage();
        switch(cur_State)
        {
            case type_A:
            process _package_state_A(_pack);
            case type_B:
            process _package_state_B(_pack);
            cur_State=type_c;
            break;
        }
    }
}

该状态机包含三种状态:type_A、type_B和 type_C,其中 type_A是状态机的开始状态,type_C是状态机的结束状态。状态机的当前状态记录在cur_State变量中。在一趟循环过程中,状态机先通过getNewPackage方法获得一个新的数据包,然后根据cur_Statc变量的值判断如何处理该数据包。数据包处理完之后,状态机通过给cur_State变量传递目标状态值来实现状态转移。那么当状态机进入下一趟循环时,它将执行新的状态对应的逻辑。

有限状态机实例

下面我们考虑有限状态机应用的一个实例:HTTP请求的读取和分析。
很多网络协议,包括TCP协议和IP协议,都在其头部中提供头部长度字段。程序根据该字段的值就可以知道是否接收到一个完整的协议头部。但HTTP协议并未提供这样的头部长度字段,并且其头部长度变化也很大,可以只有十几字节,也可以有上百字节。根据协议规定,我们判断HTTP头部结束的依据是遇到一个空行,该空行仅包含一对回车换行符()。如果一次读操作没有读人HTTP请求的整个头部,即没有遇到空行,那么我们必须等待客户继续写数据并再次读入。因此,我们每完成一次读操作,就要分析新读入的数据中是否有空行。不过在寻找空行的过程中,我们可以同时完成对整个HTTP请求头部的分析(记住,空行前面还有请求行和头部域),以提高解析HTTP请求的效率 ,下列代码使用主、从两个有限状态机实现了最简单的HTTP请求的读取和分析。为了使表述简洁,我们约定,直接称HTTP请求的一行(句括请求行和头部字段)为行。

#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<string.h>
#include<fcntl.h>
#include<libgen.h>
#include<assert.h>
#include<errno.h>
#define BUFFER_SIZE 4096
/*读缓存区大小*/
/*
主状态机的两种可能:
当前正在分析请求行
当前正在分析头部字节
*/
enum CHECK_STATE
{
CHECK_STATE_REQUESTLINE=0,
CHECH_STATE_HEAD
};
/*从状态机三种可能
读取一个完整的行
行出错
行数据不完整*/
enum LINE_STATUS
{
    LINE_OK=1,
    LINE_BAN,
    LINE_OPEN
};

/*服务器处理HTTP的结果 
NO_REQUEST 表示请求不完整 继续读取
GET_REQUEST 表示已经活得一个完整的客户请求
BAN_REQUEST表示客户请求有语法错误
FORBINDEN_REQUEST表示客户对资源没有权限过问
INTERANL_ERROR表示服务器内部错误
CLOSE_CONNECTION   表示客户端已经关闭连接啦
*/
enum HTTP_CODE
{
NO_REQUEST=1,
GET_REQUEST ,
BAN_REQUEST,
FORBINDEN_REQUEST,
INTERANL_ERROR,
CLOSE_CONNECTION   
};
/*简化问题
我们没有给client发送一个完整的报文
只是根据服务器处理发送 ok no
*/
static const char* szret[]={"I get a correct result\n","Something wrong\n"};


/*从状态机 用于解析一行的内容
 check_index指向buffer(应用程序读缓存区)当前正在分析的字节
 read_index指向buffer中客户数据的尾部下一个字节
 buffer 0~check_index已经都分析完整 
 check_index~(read_index-1)下面的循环换个分析
*/
LINE_STATUS parse_line(char* buffer,int& checked_index,int& read_index)
{
 char temp;
for(;checked_index<read_index;++checked_index)
{
    /* 如果当前的字节是"\r",就是回车符 则说明可能读到了完整的一行*/
    temp= buffer[checked_index];
    if(temp=='\r')
    {
        /*/*如果“\r”字符碰巧是目前buffer中的最后一个已经被读入的客户数据,
        那么这次分析没有读取到一个完整的行,
        返回LINE_oPEN以表示还需要继续读取客户数据才能进一步分析*/
        if((checked_index+1)==read_index)
        {
            return LINE_OPEN;
        }
        /* 如果下一个字符是\n 则说明我们成功读取一行*/
        else if(buffer[checked_index+1]=='\n')
        {
            /* '\0'是结束标志*/
            buffer[checked_index++]='\0';// '\n'
            buffer[checked_index++]='\0';//结束
            return LINE_OK;
        }
        /*否则就是 客户发送的HTTP请求有语法问题*/
        return LINE_BAN;

        
    }
    else if(temp=='\n')
    {
        if((checked_index>1)&&buffer[checked_index-1]=='\r')
        {

            buffer[checked_index-1]='\0';//'\r'
            buffer[checked_index++]='\0';//结束
            return LINE_OK;
        }
        return LINE_BAN;

    }
return LINE_OPEN;
}

}


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

}
*url++='\0';
char *method=temp;
if(strcasecmp(method,"GET")==0)//只支持GET方法

{
    printf("the request method is GET\n");
    
}
else{
    return BAN_REQUEST;
}
url+=strspn(url," \t");
char* version=strpbrk(url," \t");
if(!version)
{
   return BAN_REQUEST;
}
*version++='\0';
version+=strspn(version ," \t");
/*只支持HTTP/1.1*/ 
if(strcasecmp(version,"HTTP/1.1")!=0)
{
    return BAN_REQUEST;
}
if(strncasecmp(url,"http://",7)==0)
{
    url+=7;
    url=strchr(url,'/');
}
if(!url||url[0]!='/')
{
    return BAN_REQUEST;
}
printf("the request URL is: %s\n",url);
checkstate=CHECH_STATE_HEAD;
return NO_REQUEST;


}

/* 分析头部字段*/
HTTP_CODE parse_header(char* temp)
{
/*遇到一个空行,说明我们得到一个正确的HTTP请求*/
if(temp[0]=='\0')
{
    return GET_REQUEST;
}
else if(strncasecmp(temp,"HOST:",50))
{
    temp+=5;
    temp+=strspn(temp," \t");
    printf("thee 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& chect_index,CHECK_STATE&checkstate,
int& read_index,int& start_line)
{
    LINE_STATUS linestatus=LINE_OK;
    HTTP_CODE retcode=NO_REQUEST;//记录HTTP请求的结果
    while((linestatus=parse_line(buffer,chect_index,read_index))==LINE_OK);
    {
        char* temp=buffer+start_line;
        start_line=chect_index;//记录下一行的起始位置
        /* check_state记录主状态机的状态*/
        switch (checkstate)
        {
        case CHECK_STATE_REQUESTLINE:
        {//分析请求行
            retcode=parse_requestline(temp,checkstate);
            if(retcode==BAN_REQUEST)
            {
                return BAN_REQUEST;
            }
            break;
        }
        case CHECH_STATE_HEAD:
        {
            retcode=parse_header(temp);
            if(retcode==BAN_REQUEST)
            {
                return BAN_REQUEST;
            }
            else if(retcode==GET_REQUEST)
            {
                return GET_REQUEST;
            }
           break;
        }
    
        default:
        {
            return INTERANL_ERROR;
        }
            break;
        }
    }
}
int main(int argc,char* argv[])
{
    if(argc<=2)
    {
        printf("Usage :%s ip_Address port_number\n",basename(argv[0]));
        return 0;     
    }
    char* ip=argv[1];
    int port=atoi(argv[2]);

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

    int sockfd=socket(PF_INET,SOCK_STREAM,0);
    assert(sockfd!=-1);

    int ret=bind(sockfd,(struct sockaddr*)&address,sizeof(address));
    assert(ret!=-1);
    ret=listen(sockfd,5);
    assert(ret!=-1);
    struct sockaddr_in client;
    socklen_t client_length=sizeof(client);
    int fd=accept(sockfd,(struct sockaddr*)&client,&client_length);
    if(fd<0)
    {
        printf("errno 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;//行在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)
          {
            printf("reading failed\n");
          }
          else if(data_read==0)
          {
             printf("the client has closed the connection");
             break;
          }
          read_index+=data_read;
          HTTP_CODE result=parse_content(buffer,check_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(sockfd);
return 0;
}

我们将代上面码清单中的两个有限状态机分别称为主状态机和从状态机,这体现了它们之间的关系:主状态机在内部调用从状态机。下面先分析从状态机,即parse_line函数,它从buffer 中解析出一个行
在这里插入图片描述
这个状态机的初始状态是LINE_OK,其原始驱动力来自于buffer中新到达的客户数据。在main函数中,我们循环调用recv函数往buffer中读入客户数据。每次成功读取数据后,我们就调用parse_content函数来分析新读入的数据。parse_content函数首先要做的就是调用parse_line函数来获取一个行 buffer内容如图a

parse_line函数处理后的结果如图8-16b所示,它挨个检查图8-16a所示的buffer中checked_index到(read_index-1)之间的字节,判断是否存在行结束符,并更新checked_index 的值。当前 buffer中不存在行结束符,所以parse_line返回LINE_OPEN。接下来,程序继续调用recv以读取更多客户数据,这次读操作后 buffer中的内容以及部分变量的值如图8-16c所示。然后parse_line函数就又开始处理这部分新到来的数据,如图8-16d所示。这次它读取到了一个完整的行,即“HOST:localhostrin”。此时,parse_line函数就可以将这行内容递交给parse_content函数中的主状态机来处理了。

在这里插入图片描述
主状态机使用checkstate变量来记录当前的状态。如果当前的状态是CHECK_STATE_REQUESTLINE,则表示parse_line函数解析出的行是请求行,于是主状态机调用parse_requestline来分析请求行﹔如果当前的状态是CHECK_STATE_HEADER,则表示 parse_line函数解析出的是头部字段,于是主状态机调用parse_headers来分析头部字段。checkstatc变量的初始值是CHECK_STATE_REQUESTLINE,parse_requestline函数在成功地分析完请求行之后将其设置为CHECK_STATE_HEADER,从而实现状态转移。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/129707.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!