Linux网络编程——有限状态机

news2024/10/6 6:43:05

在逻辑单元内部的一种高效的编程方法:有限状态机。

有的应用层协议头部包含数据包类型字段,每种类型可以映射为逻辑单元的一种执行状态,服务器可以根据它来编写相应的处理逻辑,下面代码展示的是状态独立的有限状态机

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_state_A(_pack);
            cur_State=type_B;
            break;
            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_State变量的值判断如何处理该数据包。数据包处理完之后,状态机通过给cur_State变量传递目标状态值来实现状态转移。那么当状态机进入下一趟循环时, 它将执行新的状态对应的逻辑。

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

#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/*读缓冲区大小*/
/*主状态机的两种可能状态,分别表示:当前正在分析请求行,当前正在分析头部字段*/
enum CHECK_STATE { CHECK_STATE_REQUESTLINE = 0, CHECK_STATE_HEADER, CHECK_STATE_CONTENT };
/*从状态机的三种可能状态,即行的读取状态,分别表示:读取到一个完整的行、行出错和行数据尚且不完整*/
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应答报文,而只是根据服务器的处理结果发送如下成功或失败信息*/
static const char* szret[] = { "I get a correct result\n", "Something wrong\n" };

/*从状态机,用于解析出一行内容*/
LINE_STATUS parse_line( char* buffer, int& checked_index, int& read_index )
{
    char temp;
/*checked_index指向buffer(应用程序的读缓冲区)中当前正在分析的字节,read_index指向buffer中客户数据的尾部的下一字节。buffer中第0~checked_index字节都已分析完毕,第checked_index~(read_index-1)字节由下面的循环挨个分析*/
    for ( ; checked_index < read_index; ++checked_index )
    {
        /*获得当前要分析的字节*/
        temp = buffer[ checked_index ];
        /*如果当前的字节是“\r”,即回车符,则说明可能读取到一个完整的行*/
        if ( temp == '\r' )
        {
            /*如果“\r”字符碰巧是目前buffer中的最后一个已经被读入的客户数据,那么这次分析没有读        取到一个完整的行,返回LINE_OPEN以表示还需要继续读取客户数据才能进一步分析*/
            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;
            }
            /*否则的话,说明客户发送的HTTP请求存在语法问题*/
            return LINE_BAD;
        }

        /*如果当前的字节是“\n”,即换行符,则也说明可能读取到一个完整的行*/
        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;
        }
    }
    /*如果所有内容都分析完毕也没遇到“\r”字符,则返回LINE_OPEN,表示还需要继续读取客户数据才能进一步分析*/
    return LINE_OPEN;
}

/*分析请求行*/
HTTP_CODE parse_requestline( char* szTemp, CHECK_STATE& checkstate )
{
    char* szURL = strpbrk( szTemp, " \t" );
    /*如果请求行中没有空白字符或“\t”字符,则HTTP请求必有问题*/
    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请求行处理完毕,状态转移到头部字段的分析*/
    checkstate = CHECK_STATE_HEADER;
    return NO_REQUEST;
}

/*分析头部字段*/
HTTP_CODE parse_headers( char* szTemp )
{
    /*遇到一个空行,说明我们得到了一个正确的HTTP请求*/
    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请求的入口函数*/
HTTP_CODE parse_content( char* buffer, int& checked_index, CHECK_STATE& checkstate, int& read_index, int& start_line )
{
    LINE_STATUS linestatus = LINE_OK;/*记录当前行的读取状态*/
    HTTP_CODE retcode = NO_REQUEST;/*记录HTTP请求的处理结果*/
    /*主状态机,用于从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记录主状态机当前的状态*/
        switch ( checkstate )
        {
            case CHECK_STATE_REQUESTLINE:/*第一个状态,分析请求行*/
            {
                retcode = parse_requestline( szTemp, checkstate );
                if ( retcode == BAD_REQUEST )
                {
                    return BAD_REQUEST;
                }
                break;
            }
            case CHECK_STATE_HEADER:/*第二个状态,分析头部字段*/
            {
                retcode = parse_headers( szTemp );
                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;
    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 ];/*读缓冲区*/
        memset( buffer, '\0', BUFFER_SIZE );
        int data_read = 0;
        int read_index = 0;/*当前已经读取了多少字节的客户数据*/
        int checked_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" );
                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, checked_index, checkstate,read_index, start_line );
            if( result == NO_REQUEST )/*尚未得到一个完整的HTTP请求*/
            {
                continue;
            }
            else if( result == GET_REQUEST )/*得到一个完整的、正确的HTTP请求*/
            {
                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;
}

这里面有一个关于"\n","\r"的小知识,建议看这篇博客:(69条消息) \r,\n与\r\n有什么区别?_阿牛哥818的博客-CSDN博客

代码中的两个有限状态机分别称为主状态机和从状态机,这体现了它们之间的关系:主状态机在内部调用从状态机。下面先分析从状态机,即parse_line函数,它从buffer中解析出一个行。下图描述了其可能的状态及状态转移过程:

这个状态机的初始状态是LINE_OK,其原始驱动力来自于buffer中新到达的客户数据。在main函数中,我们循环调用recv函数往buffer中读入客户数据。每次成功读取数据后,我们就调用parse_content函数来分析新读入的数据。parse_content函数首先要做的就是调用parse_line函数 来获取一个行。现在假设服务器经过一次recv调用之后,buffer的内容以及部分变量的值如图(a)所示。

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/573612.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python中模块的使用3

在运行Python程序时&#xff0c;总会用到Python的标准库模块。一些标准库模块被内嵌到Python解释器中&#xff0c;通过调用这些模块提供的函数&#xff0c;可以实现特殊的功能。sys模块就是Python的一个标准库模块&#xff0c;该模块被被内嵌到Python解释器中。 1 sys模块的导…

阿里云备案服务码申请方法流程

阿里云备案服务码是什么&#xff1f;ICP备案服务码怎么获取&#xff1f;阿里云备案服务码分为免费和付费两种&#xff0c;申请备案服务码是有限制条件的&#xff0c;需要你的阿里云账号下有可用于申请备案服务码的云产品&#xff0c;如云服务器、建站产品、虚拟主机等&#xff…

计算机组成原理-指令系统-指令格式及寻址方式

目录 一、指令的定义 1.1 扩展操作码指令格式 二、指令寻址方式 2.1 顺序寻址 2.2 跳跃寻址 三、 数据寻址 3.1 直接寻址 3.2 间接寻址 3.3 寄存器寻址 ​ 3.4 寄存器间接寻址 3.5 隐含寻址 3.6 立即寻址 3.7 偏移地址 3.7.1 基址寻址 3.7.2 变址寻址 3.7.3 相对寻址…

ICLR2023 | Mole-BERT: 对分子GNN预训练的反思

原文标题&#xff1a;MOLE-BERT: RETHINKING PRE-TRAINING GRAPH NEURAL NETWORKS FOR MOLECULES 原文链接&#xff1a;Mole-BERT: Rethinking Pre-training Graph Neural Networks for Molecules | OpenReview https://github.com/junxia97/Mole-BERT 一、Introduction At…

TreeMap(1):TreeMap介绍

1 TreeMap的特点 概念&#xff1a; TreeMap是一个双列集合&#xff0c;是Map的子类。底层由红黑树结构构成。 特点&#xff1a; 元素中键不能重复元素会按照大小顺序排序 2 TreeMap的数据结构 2.1二叉查找树 2.1.1二叉查找树的定义 特点&#xff1a; 若左子树不空&#…

分享五个前端WebGis地图框架(优缺点以及该如何选择)附地址

如何选择一个合适的前端GIS框架时&#xff0c;需要考虑以下几点&#xff1a; 1&#xff0c;功能和特性 前端GIS框架的主要目的是在 Web 环境下使用&#xff0c;以可视化地理数据。需要考虑所需功能和特性&#xff0c;例如应用需不需要数据编辑、地图标注、3D 可视化、如何实现…

五、Spring从入门到改行

一、Spring概述 Spring是一个轻量级的控制反转(IOC)和面向切面(AOP)的容器&#xff08;框架&#xff09;。 二、Spring基础程序 1、pom.xml中导入依赖 <dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifact…

【特征选择】基于二进制粒子群算法的特征选择方法(KNN分类器)【Matlab代码#28】

文章目录 【可更换其他算法&#xff0c;获取资源请见文章第5节&#xff1a;资源获取】1. 基于群智能算法的特征选择2. 二进制粒子群算法3. 部分代码展示4. 仿真结果展示5. 资源获取 【可更换其他算法&#xff0c;获取资源请见文章第5节&#xff1a;资源获取】 1. 基于群智能算法…

ollvm反混淆实战

[原创]记一次基于unidbg模拟执行的去除ollvm混淆-Android安全-看雪-安全社区|安全招聘|kanxue.com 参考上面的博客进行操作。这里记录操作细节 首先配置unidbg框架 git clone --recursive https://github.com/zhaoboy9692/unidbgweb.git 然后在unidbg-android中添加自定义类…

Vue3.x的设计理念-Vue3导读

目录 VUE-NEXT【vue3】 VUE-NEXT最核心的变更 Why not SFC&#xff1f;【单文件组件】 Composition API 生命周期钩子变化 什么是响应式&#xff08;Reactivity&#xff09; Reactive值 Proactive vs Reactive 声明式程序 声明式程序&#xff1a;创造语言 声明式程序…

MATLAB串口通信使STM32出现复位情况分析

前言 这几天&#xff0c;在使用matlab进行串口读取的操作&#xff0c;一次读几百个数据后&#xff0c;对数据进行操作分析&#xff0c;打印图片。但是遇到一个奇怪的问题&#xff0c;每次串口操作完毕后&#xff0c;STM32就会出现复位状况。 matlab串口操作 下面直接附上正常…

【特征选择】基于二进制蝗虫优化算法的特征选择方法(KNN分类器)【Matlab代码#29】

文章目录 【可更换其他算法&#xff0c;获取资源请见文章第5节&#xff1a;资源获取】1. 基于群智能算法的特征选择2. 二进制蝗虫优化算法3. 部分代码展示4. 仿真结果展示5. 资源获取 【可更换其他算法&#xff0c;获取资源请见文章第5节&#xff1a;资源获取】 1. 基于群智能算…

【社区图书馆】读《一本书读懂AIGC:ChatGPT、AI绘画、智能文明与生产力变革》所感

文章目录 《一本书读懂AIGC&#xff1a;ChatGPT、AI绘画、智能文明与生产力变革》目录作者简介我的体会&#xff1a; AI带来的挑战和机遇是不可避免的 《一本书读懂AIGC&#xff1a;ChatGPT、AI绘画、智能文明与生产力变革》 作者&#xff1a;a15a 著 贾雪丽 0xAres 张炯 主编 …

百度统计是什么?百度统计数据可以自动同步吗?

百度统计是什么&#xff1f; 百度统计是一款稳定、专业、安全的数据分析产品&#xff0c;提供数据看板、行为分析、用户管理、转化归因、营销管理、AB测试等多个板块的高阶分析能力&#xff0c;帮助提升客户各职能角色工作效能&#xff0c;以数据分析助力企业达成用户全生命周…

算法|5.快速排序相关

算法|5.快速排序相关 1.荷兰国旗问题 题意&#xff1a;给定一个数组arr&#xff0c;以arr[R]为划分值&#xff0c;请把arr[R]的数放在数组的左边&#xff0c;等于arr[R]的数放在数组的中间&#xff0c;大于arr[R]的数放在数组的右边&#xff0c;返回等于arr[R]的区间。要求额…

MSP432笔记8:定时器A_PWM驱动舵机

开发板型号&#xff1a;MSP432P401r 今日得以继续我的MSP432电赛速通之路&#xff0c;文首提供本次学习实践项目文件。 注&#xff1a;我笔记实践都是从原始空项目工程文件开始配置的。 有道是 —_—_—_—_— “山无重数周遭碧&#xff0c;花不知名分外娇” “曲…

Linux驱动入门(三)——源码下载阅读、分析和嵌入式文件系统介绍

文章目录 从内核出发获取内核源码使用Git安装内核源码使用补丁 阅读Linux内核源码Source Insight简介阅读源码 内核开发的特点无libc库抑或无标准头文件GNU C没有内存保护机制不要轻易在内核中使用浮点数容积小而固定的栈同步和并发可移植性的重要性 Linux源码分析Linux源码结构…

每日一题——用两个队列实现栈

每日一题 用两个队列实现栈 题目链接 思路 这里主要讲怎么实现出栈StackPop操作做完用两个栈实现队列&#xff0c;我们可能会想当然的认为&#xff0c;这一题也是一个主队列&#xff0c;一个辅助队列&#xff0c;当要出队时&#xff0c;首先判断辅助队列是否为空&#xff0c;…

代码线程安全

线程生命周期 synchronized synchronized会自动释放锁 synchronized同步代码块 synchronized后面括号里obj是锁对象(保证唯一)&#xff1b;static修饰的obj对象是自定义MyThread线程类的静态成员变量&#xff0c;该自定义线程类所有实例共享保证锁对象唯一性&#xff1b;另一…

广度优先搜索

注&#xff1a;最近有些事所以请大家原谅 那么废话讲完了┏ (゜ω゜)☞ 目录 一&#xff1a;认识广搜 广度优先搜索算法的搜索步骤一般是&#xff1a; 二&#xff1a;导入 广度优先搜索一般可以回答两类问题&#xff1a; 三&#xff1a;基础应用 例题1&#xff1a;寻宝…