Linux高性能服务器编程8-3自动状态机:HTTP请求的读取和分析

这个搞了一天才搞通,主要问题是string的函数用法还不太熟练,还得一遍学这个一遍捋逻辑,C++基础还是要打牢。

接收并处理HTTP请求的思路:

由于我们一边需要处理HTTP请求,一边还要判断请求是否正确,所以设置两个状态机,一个状态机负责HTTP的状态,一个状态机负责请求行的状态。

HTTP请求示例如下:
HTTP请求示例

当接收到HTTP请求时,需要首先处理一行。也就是将一行单独摘出来,看看这一行是否正确。从状态机就负责判断这一行是正确的,还是错误的,还是没有读完需要继续读。

随后根据当前主状态机的状态,判断这行内容是请求行的还是头部字段的。

主状态机第一个状态是请求行。请求行需要首先判断是否是GET,其次判断URL是不是正确的(即http://开头,同时后面有一个’/‘),最后判断HTTP版本是不是HTTP/1.1。(这里的字符串操作比较多,我对书上的方法不太熟练,就查了查api自己写了,功能应该一样,不知道有bug没)

随后处理头部字段,头部字段每个都是一行(这个HTTP请求示例是空格,我说怎么和服务器处理逻辑对不上…)。如果头部字段是"\r\n",则代表一个头部字段处理完了。如果是"\0",代表整个请求处理结束(这点很坑,在处理行时需要额外判断,否则会卡住,搞了半天…)。

代码如下:

服务器

#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 BUFFERSIZE 4096

//主状态机状态,一个是检测请求行 另一个是检测头部
enum CHECK_STATE {CHECK_STATE_REQUESTLINE = 0, CHECK_STATE_HEADER};
//从状态机状态,行完整,行不完整,行错误
enum LINE_STATES {LINE_OK = 0, LINE_OPEN, LINE_BAD};
//服务器处理结果,完整请求,不完整,错误请求,没有访问权限,服务器内部错误,客户端关闭
enum HTTP_CODE {GET_REQUEST = 0, NO_REQUEST, BAD_REQUEST, FORBIDEN_REQUEST, INTERNAL_ERROR, CLOSE_CONNECTION};
//应答报文
const char* szret[] = {"I get a correct result. \n", "Something wrong. \n"};

HTTP_CODE parse_header(char* buf){
    printf("parse header:%s\n", buf);
    if(buf[0] == '\0'){//完整结束,得到一个正确的HTTP请求
        printf("finish!!!\n");
        return GET_REQUEST;
    }else if(strncasecmp(buf, "Host:", 5) == 0){
        buf += 5;
        buf += strspn(buf, "\t");
        printf("The request host is : %s \n", buf);
    }else{
        printf("I can't handle this header:%s \n", buf);
    }
    return NO_REQUEST;
}

HTTP_CODE parse_request(char* buf, CHECK_STATE& check_state){
    printf("buf: %s \n", buf);
    char* url = strchr(buf, ' ');
    if(!url){
        return BAD_REQUEST;
    }
    *url++ = '\0';
    char* method = buf;
    if(strcasecmp(method, "GET") == 0){
        printf("The method is GET \n");
    }else{
        return BAD_REQUEST;
    }

    char* version = strchr(url, ' ');
    if(!version){
        return BAD_REQUEST;
    }
    *version++ = '\0';
    if(strcasecmp(version, "HTTP/1.1") != 0){
        return BAD_REQUEST;
    }else{
        printf("version is %s \n", version);
    }
    
    if(strncasecmp(url, "http://", 7) == 0){
        char* t = strchr(url, '/');
        if(!t)
            return BAD_REQUEST;
        printf("url is %s \n", url);
    }else{
        return BAD_REQUEST;
    }

    check_state = CHECK_STATE_HEADER;
    return NO_REQUEST;

}

//从状态机,分析行
LINE_STATES parse_line(char*buf, int& check_index, int& read_index){
    printf("parse line:%s\n", buf + check_index);
    char temp;
    if(check_index == read_index){ //最后的\0
        return LINE_OK;
    }
    for(;check_index < read_index; ++check_index){
        temp = buf[check_index];
        if(temp == '\r'){//如果是\r,则必须后面是\n是一个完整行
            if((check_index + 1) == read_index){//需要继续读取数据
                return LINE_OPEN;
            }else if(buf[check_index + 1] == '\n'){//完整行
                buf[check_index++] = '\0';
                buf[check_index++] = '\0';
                return LINE_OK;
            }else{//错误行
                return LINE_BAD;
            }
        }else if(temp == '\n'){ //前面必须是\r才是完整行
            if(check_index > 1 && buf[check_index - 1] == '\r'){
                buf[check_index - 1] = '\0';
                buf[check_index++] = '\0';
                return LINE_OK;
            }else{//错误行
                return LINE_BAD;
            }
        }
    }
    return LINE_OPEN;
}

//分析内容
HTTP_CODE parse_content(char* buf, int& start_line, int& check_index, int& read_index, CHECK_STATE& check_state){
    LINE_STATES line_state = LINE_OK; //行状态
    HTTP_CODE retcode = NO_REQUEST; //记录http请求的结果
    //读取一行,每读一行就分析一行
    while((line_state = parse_line(buf, check_index, read_index)) == LINE_OK){ //分析行
        char* temp = buf + start_line;//行的起始位置
        start_line = check_index; //之前的行分析过没问题了,下次就前移
        switch(check_state){
            case(CHECK_STATE_REQUESTLINE):{//请求行
                retcode = parse_request(temp, check_state);
                if(retcode == BAD_REQUEST)
                    return retcode;
                break;
            }
            case(CHECK_STATE_HEADER):{//头部字段
                retcode = parse_header(temp);
                if(retcode == BAD_REQUEST){
                    return retcode;
                }else if(retcode == GET_REQUEST){
                    return retcode;
                }
                break;
            }
            default:{
                return INTERNAL_ERROR;
            }
        }
    }
    if(retcode == NO_REQUEST){//还没读完
        return retcode;
    }else{//出错
        return BAD_REQUEST;
    }
}

int main(int argc, char* argv[]){
    if(argc < 3){
        printf("ip & port \n");
        return 0;
    }
    char* ip = argv[1];
    int port = atoi(argv[2]);

    int sockfd = socket(AF_INET,SOCK_STREAM, 0);

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

    int ret = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
    assert(ret != -1);

    ret = listen(sockfd, 5);
    assert(ret != -1);

    struct sockaddr_in client_addr;
    socklen_t client_addrlen;
    int connfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_addrlen);

    if(connfd < 0){
        printf("error is %d \n", errno);
    }else{
        char buf[BUFFERSIZE];
        int data_read = 0;
        int start_index = 0;//行在buf中的起始位置
        int check_index = 0;//检查到的位置
        int read_index = 0;//读取的位置
        CHECK_STATE check_state = CHECK_STATE_REQUESTLINE; //开始需要检查请求行
        while(1){
            //先读取数据
            data_read = recv(connfd, buf + read_index, BUFFERSIZE - read_index, 0);
            if(data_read == -1){
                printf("read failure \n");
                break;
            }else if(data_read == 0){
                printf("client closed \n");
                break;
            }
            //多读取了一段
            read_index += data_read;
            //分析还未分析的客户数据
            HTTP_CODE result = parse_content(buf, start_index, check_index, read_index, check_state);
            
            if(result == GET_REQUEST){//得到一个正确的完整的请求
                send(connfd, szret[0], strlen(szret[0]), 0);//strlen 和 sizeof区别还要再搞清楚,不能乱用
                break;
            }else if(result == NO_REQUEST){//尚未得到完整的请求
                continue;
            }else{//得到错误的请求
                send(connfd, szret[1], strlen(szret[1]), 0);
                break;
            }
            close(connfd);
        }
        close(connfd);
        return 0;
    }
}

客户端

#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#define BUFFER_SIZE 1024
int main(int argc, char* argv[]){
    if(argc <= 2){
        printf("ip and port and size!!!\n");
        return 0;
    }

    char* ip = argv[1];
    int port = atoi(argv[2]);

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);

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

    if((connect(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr))) < 0){
        printf("connect failure \n");
    }else{
        char str[] = "GET http://www.baidu.com/index.html HTTP/1.1\r\nUSER-Agent:Wget/1.12(linux-gnu)\r\nHost:www.baidu.com\r\nConnection:close\r\n";
        printf("%s \n", str); //这里不能用sizeof,sizeof的结果不对
        send(sockfd, str, strlen(str), 0);

        char recvbuf[BUFFER_SIZE];
        memset(recvbuf, '\0', BUFFER_SIZE);
        int ret;
        while((ret = recv(sockfd, recvbuf, BUFFER_SIZE - 1, 0)) != 0){
            printf("recieve %d bytes, content is :%s \n", ret, recvbuf);
        }
        
    }
    close(sockfd);
    return 0;
}

运行结果:

服务器
在这里插入图片描述
客户端

在这里插入图片描述

PS:这个搞了很久,之前就大致看了一下,最后面一章需要写简单web服务器的时候就很迷,还是要搞清楚。最后,这个代码是我根据自己理解参考书上敲的,可能有bug,欢迎大家指正。

  • 21
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值