2022-4-19 HTTP头部有限状态机《Linux高性能服务器》笔记

服务端

#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};
//从状态机的三种可能的状态:1)请求行完整,2)行出错和行数据尚不完整
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};

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){
    
    
    char temp;
    for(;check_index < read_index;++check_index){
    
    
        temp = buffer[check_index];
        if(temp == '\r'){
    
    
            //如果读取回车符号的时候已经表示到了末尾,
            //那就说明这个符号没有读完
            if((check_index + 1) == read_index){
    
    
                return LINE_OPEN;
            //判断是否读取到了完整的一句话    
            }else if(buffer[check_index+1]=='\n'){
    
    
                buffer[check_index++] = '\0';
                //将当前的'\r'置为'\0'之后再向后移动一位
                buffer[check_index++] = '\0';
                //将当前的'\n'置为'\0'之后再向后移动一位
                //为啥读到结尾要修改,是为了防止输入错误向前读取的时候出问题了吗?
                return LINE_OK;
            }
            //否则说明这里面有语法错误
            return LINE_BAD;
            //我有个疑惑,为啥判断的时候不是先读取了'\r'再读取'\n'的吗?
            //判断了'\r'的存在之后依然需要特别判断'\n'的存在,难道好巧不巧这个是分两次读取到的吗?
        }else if(temp == '\n'){
    
    
            if((check_index > 1)&&buffer[check_index-1] == '\r'){
    
    
                buffer[check_index-1] = '\0';
                buffer[check_index++] = '\0';
                return LINE_OK;
            }
            return LINE_BAD;
        }
    }
    return LINE_OPEN;
    //表明所有的内容都读取完了也没有遇到终结字符,还需要继续读取,所以返回提示。
}
HTTP_CODE parse_requestline(char *temp,CHECK_STATE & checkstate)
{
    
    
    //strpbrk是在源字符串(s1)中找出最先含有
    //搜索字符串(s2)中任一字符的位置并返回,若找不到则返回空指针。
    //如果请求行中没有'\t'则请求行有问题
    char*url = strpbrk(temp,"\t");
    if(!url){
    
    
        return BAD_REQUEST;
    }
    *url++ = '\0';
    char *method = temp;
    //因为仅仅支持get方法
    if(strcasecmp(method,"GET") == 0){
    
    
        printf("The request method is GET\n");
    }else {
    
    
        return BAD_REQUEST;
    }
    url += strspn(url,"\t");
    //strspn该函数返回 str1 中第一个不在字符串 str2 中出现的字符下标。
    //也就是跳过空白字符,好像是http当中允许存在多个线性空格
    //此时url为网址的开头
    char *version = strpbrk(url,"\t");
    if(!version){
    
    
        return BAD_REQUEST;
    }
    *version++ = '\0';
    //阶段网址
    version += strspn(version,"\t");
    //这个是version的正式开头
    //忽略大小写差异的比较函数
    if(strcasecmp(version,"HTTP/1.1") != 0){
    
    
        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){
    
    
    if(temp[0]=='\0'){
    
    
        return GET_REQUEST;
    }else if(strncasecmp(temp,"Host:",5) == 0){
    
    
        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_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;
    //使用从状态机来读取一行
    while ((linestatus = parse_line(buffer,checked_index,read_index))==LINE_OK)
    {
    
    
        char*temp = buffer + start_line;
        start_line = checked_index;
    
    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(void){
    
    
    struct sockaddr_in address;
    bzero(&address,sizeof(address));
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = ntohs(9999);
    int listenfd = socket(PF_INET,SOCK_STREAM,0);
    if(listenfd == -1){
    
    
        perror("socket");
        return -1;
    }
    int ret = bind(listenfd,(struct sockaddr*)&address,sizeof(address));
    if(ret == -1){
    
    
        perror("bind");
        return -1;
    }
    ret = listen(listenfd,5);
    if(ret == -1){
    
    
        perror("listen");
        return -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 == -1){
    
    
        perror("accept");
        return -1;
    }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;
        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){
    
    
                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(listenfd);
    return 0;

}

客户端

#include<stdio.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>

int main(void){
    
    
    //1.创建套接字
    int fd = socket(AF_INET,SOCK_STREAM,0);
    if(fd == -1){
    
    
        perror("socket");
        exit(0);
    }

    //2.连接服务器端
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    inet_pton(AF_INET,"192.168.131.138",&serveraddr.sin_addr.s_addr);
    serveraddr.sin_port = htons(9999);
    int ret = connect(fd,(struct sockaddr*)&serveraddr,sizeof(serveraddr));
    if(ret == -1){
    
    
        perror("connect");
        exit(0);
    }

    //3.通信
    //char *data = "hello , i am client.";
    char *data ="GET\thttp:///www.baidu.com\tHTTP/1.1\r\nHost:\twww.baidu.com\t\r\n\r\n";
    write(fd,data,strlen(data));

    char recvBuf[1024] = {
    
    0};
    while(read(fd,recvBuf,sizeof(recvBuf)) > 0){
    
    
        printf("recv data :%s\n",recvBuf);
    }

    close(fd);
    return 0;
}

最大的收获是知道了HTTP的头部报文是咋写的。
还有状态机的转化

服务端运行结果
在这里插入图片描述
客户端运行结果

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_51187533/article/details/124264460