第5章 项目实战与总结5.7

实现请求头的读:
在这里插入图片描述

http_conn.h

#ifndef HTTP_CONN_H
#define HTTP_CONN_H

#include<sys/epoll.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
#include<fcntl.h>
#include<arpa/inet.h>
#include<sys/stat.h>
#include<sys/mman.h>
#include<stdarg.h>
#include<errno.h>
#include"locker.h"




class http_conn{
public:
    //所有的socket上的事件都被注册到同一个epoll对象上
    static int m_epollfd;
    //统计用户的数量
    static int m_user_count;
    //读缓冲区的大小
    static const int READ_BUFFER_SIZE = 2048;
    //写缓冲区的大小
    static const int WRITE_BUFFER_SIZE = 1024;



    http_conn(){}
    ~http_conn(){}

    void process();//处理客户端请求
    void init(int sockfd,const sockaddr_in & addr);//初始化新接受的连接
    void close_conn();//关闭连接
    bool read();//非阻塞地读
    bool write();//非阻塞的写

private:
    int m_sockfd;//该http连接的socket
    sockaddr_in m_address;//通信的socket地址
    char m_read_buf[READ_BUFFER_SIZE];//读缓冲区
    //标识读缓冲区中以及读入的客户端数据的最后一个直接的下一个位置
    int m_read_index;
};



#endif

http_conn.cpp

#include"http_conn.h"


//所有的socket上的事件都被注册到同一个epoll对象上
int http_conn::m_epollfd=-1;
//统计用户的数量
int http_conn::m_user_count=0;



//设置文件描述符非阻塞
int setnonblocking( int fd ) {
    int old_option = fcntl( fd, F_GETFL );
    int new_option = old_option | O_NONBLOCK;
    fcntl( fd, F_SETFL, new_option );
    return old_option;
}

//将监听的文件描述符到epoll中
void addfd(int epollfd,int fd ,bool one_shot){
    epoll_event event;
    event.data.fd=fd;
    //event.events=EPOLLIN|EPOLLRDHUP;
    event.events=EPOLLIN|EPOLLRDHUP|EPOLLET;


    if(one_shot){
        event.events|EPOLLONESHOT;
    }
    epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event);
    //设置文件描述符非阻塞
    setnonblocking(fd);
}

//从epoll中删除文件描述符
void removefd(int epollfd,int fd){
    epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,0);
    close(fd);
}



//从epoll中修改文件描述符,重置socket上EPOLLONESHOT事件,以确保下一次可读时,epollin事件能被触发
void modfd(int epollfd,int fd,int ev){
    epoll_event event;
    event.data.fd=fd;
    event.events =ev|EPOLLONESHOT|EPOLLRDHUP;
    epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,&event);
}


//初始化新接受的连接
void http_conn::init(int sockfd,const sockaddr_in & addr){
    m_sockfd=sockfd;
    m_address=addr;

    //端口复用
    int reuse = 1;
    setsockopt(m_sockfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));

    //添加到epoll对象中'
    addfd(m_epollfd,sockfd,true);
    m_user_count++;
}

//关闭连接
void http_conn::close_conn(){
    if(m_sockfd!=-1){
        removefd(m_epollfd,m_sockfd);
        m_sockfd=-1;
        m_user_count--;//关闭一个连接,各户总数量-1

    }
}

//非阻塞地读:循环读取客户数据,知道无数据刻度或者对方关闭连接
bool http_conn::read(){
    
    if(m_read_index>=READ_BUFFER_SIZE){
        //读缓冲区已满,等待下一次再读
        return false;
    }

    //读取到的字节
    int bytes_read=0;
    while (true)
    {
        bytes_read=recv(m_sockfd,m_read_buf+m_read_index,READ_BUFFER_SIZE-m_read_index,0);
        if(bytes_read == -1){
            if(errno == EAGAIN || errno == EWOULDBLOCK){
                //没有数据
                break;
            }
            return false;
        }else if(bytes_read == 0){
            //对方关闭连接
            return false;
        }
        m_read_index+=bytes_read;
    }
    printf("读取到数据:%s\n",m_read_buf);
    return true;
}

//非阻塞的写
bool http_conn::write(){
    printf("一次性读完数据\n");
    return true;
}


//处理客户端请求:有线程池中的工作线程调用,这是处理http请求的入口函数
void http_conn::process(){
    //解析HTTP请求
    printf("parse request, create reponse \n");
    //生成响应
}

有限状态机

逻辑单元内部的一种高效编程方法:有限状态机(finite state machine)。有的应用层协议头部包含数据包类型字段,每种类型可以映射为逻辑单元的一种执行状态,服务器可以根据它来编写相应的处理逻辑。如下是一种状态独立的有限状态机:

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 变量传递目标状态值来实现状态转移。那么当状态机进入下一趟循环时,它将执行新的状态对应的逻辑。


版权声明:本文为IS_MOKE原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。