在邏輯單元內(nèi)部的一種高效的編程方法:有限狀態(tài)機(jī)。
有的應(yīng)用層協(xié)議頭部包含數(shù)據(jù)包類型字段,每種類型可以映射為邏輯單元的一種執(zhí)行狀態(tài),服務(wù)器可以根據(jù)它來編寫相應(yīng)的處理邏輯,下面代碼展示的是狀態(tài)獨(dú)立的有限狀態(tài)機(jī)
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;
}
}
這是一個(gè)簡單的有限狀態(tài)機(jī),只不過該狀態(tài)機(jī)的每個(gè)狀態(tài)都是相互獨(dú)立的,即狀態(tài)之間沒有相互轉(zhuǎn)移。
狀態(tài)之間的轉(zhuǎn)移是需要狀態(tài)機(jī)內(nèi)部驅(qū)動(dòng)的,這種被稱作帶狀態(tài)轉(zhuǎn)移的有限狀態(tài)機(jī)
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);
cur_State=type_B;
break;
case type_B:
process_package_state_B(_pack);
cur_State=type_C;
break;
}
}
}
該狀態(tài)機(jī)包含三種狀態(tài):type_A、type_B和type_C,其中type_A是狀態(tài)機(jī)的開始狀態(tài),type_C是狀態(tài)機(jī)的結(jié)束狀態(tài)。狀態(tài)機(jī)的當(dāng)前狀態(tài)記錄在cur_State變量中。在一趟循環(huán)過程中,狀態(tài)機(jī)先通過 getNewPackage方法獲得一個(gè)新的數(shù)據(jù)包,然后根據(jù)cur_State變量的值判斷如何處理該數(shù)據(jù)包。數(shù)據(jù)包處理完之后,狀態(tài)機(jī)通過給cur_State變量傳遞目標(biāo)狀態(tài)值來實(shí)現(xiàn)狀態(tài)轉(zhuǎn)移。那么當(dāng)狀態(tài)機(jī)進(jìn)入下一趟循環(huán)時(shí), 它將執(zhí)行新的狀態(tài)對(duì)應(yīng)的邏輯。
有限狀態(tài)機(jī)應(yīng)用實(shí)例:HTTP請(qǐng)求的讀取和分析。很多網(wǎng)絡(luò)協(xié)議,包括TCP協(xié)議和IP協(xié)議,都在其頭部中提供頭部長度字段。程序根據(jù)該字段的值就可以知道是否接收到一個(gè)完整的協(xié)議頭部。但HTTP協(xié)議并未提供這樣的頭部長度字段,并且其頭部長度變化也很大,可以只有十幾字節(jié),也可以有上百字節(jié)。根據(jù)協(xié)議規(guī)定,我們判斷HTTP頭部結(jié)束的依據(jù)是遇到一個(gè)空行,該空行僅包含一對(duì)回車換行符(<CR><LF>)。如果一次讀操作沒有讀入HTTP請(qǐng)求的整個(gè)頭部,即沒有遇到空行,那么我們必須等待客戶繼續(xù)寫數(shù)據(jù)并再次讀入。 因此,我們每完成一次讀操作,就要分析新讀入的數(shù)據(jù)中是否有空行。 不過在尋找空行的過程中,我們可以同時(shí)完成對(duì)整個(gè)HTTP請(qǐng)求頭部的分析(記住,空行前面還有請(qǐng)求行和頭部域),以提高解析HTTP請(qǐng)求的效率。下面代碼使用主、從兩個(gè)有限狀態(tài)機(jī)實(shí)現(xiàn)了最簡單的HTTP 請(qǐng)求的讀取和分析。為了使表述簡潔,我們約定,直接稱HTTP請(qǐng)求的一行(包括請(qǐng)求行和頭部字段)為行。
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#define BUFFER_SIZE 4096/*讀緩沖區(qū)大小*/
/*主狀態(tài)機(jī)的兩種可能狀態(tài),分別表示:當(dāng)前正在分析請(qǐng)求行,當(dāng)前正在分析頭部字段*/
enum CHECK_STATE { CHECK_STATE_REQUESTLINE = 0, CHECK_STATE_HEADER, CHECK_STATE_CONTENT };
/*從狀態(tài)機(jī)的三種可能狀態(tài),即行的讀取狀態(tài),分別表示:讀取到一個(gè)完整的行、行出錯(cuò)和行數(shù)據(jù)尚且不完整*/
enum LINE_STATUS { LINE_OK = 0, LINE_BAD, LINE_OPEN };
/*服務(wù)器處理HTTP請(qǐng)求的結(jié)果:NO_REQUEST表示請(qǐng)求不完整,需要繼續(xù)讀取客戶數(shù)據(jù);GET_REQUEST表示獲得了一個(gè)完整的客戶請(qǐng)求;BAD_REQUEST表示客戶請(qǐng)求有語法錯(cuò)誤;FORBIDDEN_REQUEST表示客戶對(duì)資源沒有足夠的訪問權(quán)限;INTERNAL_ERROR表示服務(wù)器內(nèi)部錯(cuò)誤;CLOSED_CONNECTION表示客戶端已經(jīng)關(guān)閉連接了*/
enum HTTP_CODE { NO_REQUEST, GET_REQUEST, BAD_REQUEST, FORBIDDEN_REQUEST, INTERNAL_ERROR, CLOSED_CONNECTION };
/*為了簡化問題,我們沒有給客戶端發(fā)送一個(gè)完整的HTTP應(yīng)答報(bào)文,而只是根據(jù)服務(wù)器的處理結(jié)果發(fā)送如下成功或失敗信息*/
static const char* szret[] = { "I get a correct result\n", "Something wrong\n" };
/*從狀態(tài)機(jī),用于解析出一行內(nèi)容*/
LINE_STATUS parse_line( char* buffer, int& checked_index, int& read_index )
{
char temp;
/*checked_index指向buffer(應(yīng)用程序的讀緩沖區(qū))中當(dāng)前正在分析的字節(jié),read_index指向buffer中客戶數(shù)據(jù)的尾部的下一字節(jié)。buffer中第0~checked_index字節(jié)都已分析完畢,第checked_index~(read_index-1)字節(jié)由下面的循環(huán)挨個(gè)分析*/
for ( ; checked_index < read_index; ++checked_index )
{
/*獲得當(dāng)前要分析的字節(jié)*/
temp = buffer[ checked_index ];
/*如果當(dāng)前的字節(jié)是“\r”,即回車符,則說明可能讀取到一個(gè)完整的行*/
if ( temp == '\r' )
{
/*如果“\r”字符碰巧是目前buffer中的最后一個(gè)已經(jīng)被讀入的客戶數(shù)據(jù),那么這次分析沒有讀 取到一個(gè)完整的行,返回LINE_OPEN以表示還需要繼續(xù)讀取客戶數(shù)據(jù)才能進(jìn)一步分析*/
if ( ( checked_index + 1 ) == read_index )
{
return LINE_OPEN;
}
/*如果下一個(gè)字符是“\n”,則說明我們成功讀取到一個(gè)完整的行*/
else if ( buffer[ checked_index + 1 ] == '\n' )
{
buffer[ checked_index++ ] = '\0';
buffer[ checked_index++ ] = '\0';
return LINE_OK;
}
/*否則的話,說明客戶發(fā)送的HTTP請(qǐng)求存在語法問題*/
return LINE_BAD;
}
/*如果當(dāng)前的字節(jié)是“\n”,即換行符,則也說明可能讀取到一個(gè)完整的行*/
else if( temp == '\n' )
{
if( ( checked_index > 1 ) && buffer[ checked_index - 1 ] == '\r' )
{
buffer[ checked_index-1 ] = '\0';
buffer[ checked_index++ ] = '\0';
return LINE_OK;
}
return LINE_BAD;
}
}
/*如果所有內(nèi)容都分析完畢也沒遇到“\r”字符,則返回LINE_OPEN,表示還需要繼續(xù)讀取客戶數(shù)據(jù)才能進(jìn)一步分析*/
return LINE_OPEN;
}
/*分析請(qǐng)求行*/
HTTP_CODE parse_requestline( char* szTemp, CHECK_STATE& checkstate )
{
char* szURL = strpbrk( szTemp, " \t" );
/*如果請(qǐng)求行中沒有空白字符或“\t”字符,則HTTP請(qǐng)求必有問題*/
if ( ! szURL )
{
return BAD_REQUEST;
}
*szURL++ = '\0';
char* szMethod = szTemp;
if ( strcasecmp( szMethod, "GET" ) == 0 )/*僅支持GET方法*/
{
printf( "The request method is GET\n" );
}
else
{
return BAD_REQUEST;
}
szURL += strspn( szURL, " \t" );
char* szVersion = strpbrk( szURL, " \t" );
if ( ! szVersion )
{
return BAD_REQUEST;
}
*szVersion++ = '\0';
szVersion += strspn( szVersion, " \t" );
/*僅支持HTTP/1.1*/
if ( strcasecmp( szVersion, "HTTP/1.1" ) != 0 )
{
return BAD_REQUEST;
}
/*檢查URL是否合法*/
if ( strncasecmp( szURL, "http://", 7 ) == 0 )
{
szURL += 7;
szURL = strchr( szURL, '/' );
}
if ( ! szURL || szURL[ 0 ] != '/' )
{
return BAD_REQUEST;
}
//URLDecode( szURL );
printf( "The request URL is: %s\n", szURL );
/*HTTP請(qǐng)求行處理完畢,狀態(tài)轉(zhuǎn)移到頭部字段的分析*/
checkstate = CHECK_STATE_HEADER;
return NO_REQUEST;
}
/*分析頭部字段*/
HTTP_CODE parse_headers( char* szTemp )
{
/*遇到一個(gè)空行,說明我們得到了一個(gè)正確的HTTP請(qǐng)求*/
if ( szTemp[ 0 ] == '\0' )
{
return GET_REQUEST;
}
else if ( strncasecmp( szTemp, "Host:", 5 ) == 0 )/*處理“HOST”頭部字段*/
{
szTemp += 5;
szTemp += strspn( szTemp, " \t" );
printf( "the request host is: %s\n", szTemp );
}
else/*其他頭部字段都不處理*/
{
printf( "I can not handle this header\n" );
}
return NO_REQUEST;
}
/*分析HTTP請(qǐng)求的入口函數(shù)*/
HTTP_CODE parse_content( char* buffer, int& checked_index, CHECK_STATE& checkstate, int& read_index, int& start_line )
{
LINE_STATUS linestatus = LINE_OK;/*記錄當(dāng)前行的讀取狀態(tài)*/
HTTP_CODE retcode = NO_REQUEST;/*記錄HTTP請(qǐng)求的處理結(jié)果*/
/*主狀態(tài)機(jī),用于從buffer中取出所有完整的行*/
while( ( linestatus = parse_line( buffer, checked_index, read_index ) ) == LINE_OK )
{
char* szTemp = buffer + start_line;/*start_line是行在buffer中的起始位置*/
start_line = checked_index;/*記錄下一行的起始位置*/
/*checkstate記錄主狀態(tài)機(jī)當(dāng)前的狀態(tài)*/
switch ( checkstate )
{
case CHECK_STATE_REQUESTLINE:/*第一個(gè)狀態(tài),分析請(qǐng)求行*/
{
retcode = parse_requestline( szTemp, checkstate );
if ( retcode == BAD_REQUEST )
{
return BAD_REQUEST;
}
break;
}
case CHECK_STATE_HEADER:/*第二個(gè)狀態(tài),分析頭部字段*/
{
retcode = parse_headers( szTemp );
if ( retcode == BAD_REQUEST )
{
return BAD_REQUEST;
}
else if ( retcode == GET_REQUEST )
{
return GET_REQUEST;
}
break;
}
default:
{
return INTERNAL_ERROR;
}
}
}
/*若沒有讀取到一個(gè)完整的行,則表示還需要繼續(xù)讀取客戶數(shù)據(jù)才能進(jìn)一步分析*/
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;
inet_pton( AF_INET, ip, &address.sin_addr );
address.sin_port = htons( port );
int listenfd = socket( PF_INET, SOCK_STREAM, 0 );
assert( listenfd >= 0 );
int ret = bind( listenfd, ( struct sockaddr* )&address, sizeof( address ) );
assert( ret != -1 );
ret = listen( listenfd, 5 );
assert( ret != -1 );
struct sockaddr_in client_address;
socklen_t client_addrlength = sizeof( client_address );
int fd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
if( fd < 0 )
{
printf( "errno is: %d\n", errno );
}
else
{
char buffer[ BUFFER_SIZE ];/*讀緩沖區(qū)*/
memset( buffer, '\0', BUFFER_SIZE );
int data_read = 0;
int read_index = 0;/*當(dāng)前已經(jīng)讀取了多少字節(jié)的客戶數(shù)據(jù)*/
int checked_index = 0;/*當(dāng)前已經(jīng)分析完了多少字節(jié)的客戶數(shù)據(jù)*/
int start_line = 0;/*行在buffer中的起始位置*/
/*設(shè)置主狀態(tài)機(jī)的初始狀態(tài)*/
CHECK_STATE checkstate = CHECK_STATE_REQUESTLINE;
while( 1 )/*循環(huán)讀取客戶數(shù)據(jù)并分析之*/
{
data_read = recv( fd, 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;
/*分析目前已經(jīng)獲得的所有客戶數(shù)據(jù)*/
HTTP_CODE result = parse_content( buffer, checked_index, checkstate,read_index, start_line );
if( result == NO_REQUEST )/*尚未得到一個(gè)完整的HTTP請(qǐng)求*/
{
continue;
}
else if( result == GET_REQUEST )/*得到一個(gè)完整的、正確的HTTP請(qǐng)求*/
{
send( fd, szret[0], strlen( szret[0] ), 0 );
break;
}
else/*其他情況表示發(fā)生錯(cuò)誤*/
{
send( fd, szret[1], strlen( szret[1] ), 0 );
break;
}
}
close( fd );
}
close( listenfd );
return 0;
}
這里面有一個(gè)關(guān)于"\n","\r"的小知識(shí),建議看這篇博客:(69條消息) \r,\n與\r\n有什么區(qū)別?_阿牛哥818的博客-CSDN博客
代碼中的兩個(gè)有限狀態(tài)機(jī)分別稱為主狀態(tài)機(jī)和從狀態(tài)機(jī),這體現(xiàn)了它們之間的關(guān)系:主狀態(tài)機(jī)在內(nèi)部調(diào)用從狀態(tài)機(jī)。下面先分析從狀態(tài)機(jī),即parse_line函數(shù),它從buffer中解析出一個(gè)行。下圖描述了其可能的狀態(tài)及狀態(tài)轉(zhuǎn)移過程:
這個(gè)狀態(tài)機(jī)的初始狀態(tài)是LINE_OK,其原始驅(qū)動(dòng)力來自于buffer中新到達(dá)的客戶數(shù)據(jù)。在main函數(shù)中,我們循環(huán)調(diào)用recv函數(shù)往buffer中讀入客戶數(shù)據(jù)。每次成功讀取數(shù)據(jù)后,我們就調(diào)用parse_content函數(shù)來分析新讀入的數(shù)據(jù)。parse_content函數(shù)首先要做的就是調(diào)用parse_line函數(shù) 來獲取一個(gè)行?,F(xiàn)在假設(shè)服務(wù)器經(jīng)過一次recv調(diào)用之后,buffer的內(nèi)容以及部分變量的值如圖(a)所示。
parse_line函數(shù)處理后的結(jié)果如圖(b)所示,它挨個(gè)檢查圖(a)所示的buffer中checked_index到(read_index-1)之間的字節(jié),判斷是否存在行結(jié)束符,并更新checked_index的值。當(dāng)前buffer中不存在行結(jié)束符,所以parse_line返回LINE_OPEN。接下來,程序繼續(xù)調(diào)用recv以讀取更多客戶數(shù)據(jù),這次讀操作后buffer中的內(nèi)容以及部分變量的值如圖 (c)所示。然后parse_line函數(shù)就又開始處理這部分新到來的數(shù)據(jù),如圖(d)所示。這次它讀取到了一個(gè)完整的行,即“HOST:localhost\r\n”。 此時(shí),parse_line函數(shù)就可以將這行內(nèi)容遞交給parse_content函數(shù)中的主狀態(tài)機(jī)來處理了。
文章來源:http://www.zghlxwxcb.cn/news/detail-465102.html
主狀態(tài)機(jī)使用checkstate變量來記錄當(dāng)前的狀態(tài)。如果當(dāng)前的狀態(tài)是CHECK_STATE_REQUESTLINE,則表示parse_line函數(shù)解析出的行是請(qǐng)求行,于是主狀態(tài)機(jī)調(diào)用parse_requestline來分析請(qǐng)求行;如果當(dāng)前的狀態(tài)是CHECK_STATE_HEADER,則表示parse_line函數(shù)解析出的是頭部字段,于是主狀態(tài)機(jī)調(diào)用parse_headers來分析頭部字段.checkstate變量的初始值是CHECK_STATE_REQUESTLINE,parse_requestline函數(shù)在成功地分析完請(qǐng)求行之后將其設(shè)置為CHECK_STATE_HEADER,從而實(shí)現(xiàn)狀態(tài)轉(zhuǎn)移。文章來源地址http://www.zghlxwxcb.cn/news/detail-465102.html
到了這里,關(guān)于Linux網(wǎng)絡(luò)編程——有限狀態(tài)機(jī)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!