CC BY 4.0 (除特别声明或转载文章外)
如果这篇博客帮助到你,可以请我喝一杯咖啡~
有限状态机
有的应用层协议头部包含数据包类型字段,每种类型可以映射为逻辑单元的一种执行状态,服务器可以根据它来编写相应的处理逻辑。
感觉这个状态机就像是一个大模拟…
例子:HTTP请求的读取和分析:
很多网络协议,包括TCP协议和IP协议,都在其头部中提供头部长度字段。程序根据该字段的值就可以知道是否接收到一个完整的协议 头部。
但HTTP协议并未提供这样的头部长度字段,并且其头部长度变 化也很大,可以只有十几字节,也可以有上百字节。根据协议规定, 我们判断HTTP头部结束的依据是遇到一个空行,该空行仅包含一对回 车换行符(<CR><LF>)。
如果一次读操作没有读入HTTP请求的 整个头部,即没有遇到空行,那么我们必须等待客户继续写数据并再次读入。因此,我们每完成一次读操作,就要分析新读入的数据中是否有空行。不过在寻找空行的过程中,我们可以同时完成对整个HTTP 请求头部的分析(记住,空行前面还有请求行和头部域),以提高解 析HTTP请求的效率。
#include <cstdio>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <assert.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
using namespace std;
#define BUFFER_SIZE 4096
/*
主状态机:分析请求行;分析请求头部字段
*/
enum CHECK_STATE{
CHECK_STATE_REQUESTLINE = 0,CHECK_STATE_HEADER
};
/*
从状态机:行的读取状态:读到一个完整的行、行出错、行数据尚不完整
*/
enum LINE_STATUS{
LINE_OK = 1,LINE_BAD,LINE_OPEN
};
/*
服务器处理HTTP请求的结果
*/
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){
// puts("####line");
char temp;
/*
checked_index指向buffer(应用程序的读缓冲区)中当前正在分析的字节,
read_index指向buffer中客户数据的尾部的下一字节。
buffer中第0~checked_index 字节都已分析完毕,第checked_index~(read_index-1)字节由下面的循环挨个分析
*/
// printf("%d %d\n",check_index,read_index);
for(;check_index<read_index;++check_index) {
temp = buffer[check_index];
// printf("%c\n",temp);
/*如果当前的字节是“\r”,即回车符,则说明可能读取到一个完整的行*/
if(temp=='\r') {
/*如果“\r”字符碰巧是目前buffer中的最后一个已经被读入的客户数据,
那么这次分 析没有读取到一个完整的行,返回LINE_OPEN以表示还需要继续读取客户数据才能进一步分 析*/
if((check_index+1)==read_index) {
// puts("####liner");
return LINE_OPEN;
}
/*如果下一个字符是“\n”,则说明我们成功读取到一个完整的行*/
else if(buffer[check_index+1]=='\n') {
buffer[check_index++]='\0';
buffer[check_index++]='\0';
// puts("####linerok");
return LINE_OK;
}
/*否则的话,说明客户发送的HTTP请求存在语法问题*/
return LINE_BAD;
}
/*如果当前的字节是“\n”,即换行符,则也说明可能读取到一个完整的行*/
else if(temp=='\n') {
if((check_index>1)&&buffer[check_index-1]=='\r') {
buffer[check_index-1]='\0';
buffer[check_index++]='\0';
// puts("####linenok");
return LINE_OK;
}
return LINE_BAD;
}
}
/*如果所有内容都分析完毕也没遇到“\r”字符,则返回LINE_OPEN,表示还需要继续读 取客户数据才能进一步分析*/
return LINE_OPEN;
}
/*分析请求行*/
HTTP_CODE parse_requestline(char *temp,CHECK_STATE &checkstate) {
// puts("####");
// printf("%s\n",temp);
char *url = strpbrk(temp," \t");
// printf("%s\n",url);
if(!url) {
return BAD_REQUEST;
}
*url++ = '\0';
char *method = temp;
if(strcasecmp(method,"GET")==0) {
printf("The request method is GET!\n");
}
else {
puts("verssget");
return BAD_REQUEST;
}
url+=strspn(url," \t");
char *version = strpbrk(url," \t");
if(!version) {
// puts("versss1");
return BAD_REQUEST;
}
*version++ ='\0';
version+=strspn(version," \t");
if(strcasecmp(version,"HTTP/1.1")!=0) {
puts("versss");
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)
{
/*遇到一个空行,说明我们得到了一个正确的HTTP请求*/
if(temp[0]=='\0') {
return GET_REQUEST;
}
else if(strncasecmp(temp,"Host:",5)==0)
/*处理“HOST”头部字段*/
{
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请求的入口函数*/
HTTP_CODE parse_content(char*buffer,int & checked_index,CHECK_STATE & checkstate,int & read_index,int & start_line)
{
// puts("####");
LINE_STATUS linestatus=LINE_OK;/*记录当前行的读取状态*/
HTTP_CODE retcode=NO_REQUEST;/*记录HTTP请求的处理结果*/
while ((linestatus=parse_line(buffer,checked_index,read_index))==LINE_OK)
{
/* code */
puts("####while");
char*temp=buffer+start_line;/*start_line是行在buffer中的起始位置*/
start_line = checked_index;/*记录下一行的起始位置*/
/*checkstate记录主状态机当前的状态*/
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(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;
address.sin_port = htons(port);
inet_pton(AF_INET,ip,&address.sin_addr);
int sockfd = socket(PF_INET,SOCK_STREAM,0);
assert(sockfd>=0);
int ret = bind(sockfd,(struct sockaddr*)&address,sizeof(address));
assert(ret!=-1);
ret = listen(sockfd,5);
assert(ret!=-1);
struct sockaddr_in client_address;
socklen_t client_address_length = sizeof(client_address);
int connfd = accept(sockfd,(struct sockaddr*)&client_address,&client_address_length);
if(connfd<0) {
printf("error is:%d\n",errno);
}
else {
char buffer[BUFFER_SIZE];/*读缓冲区*/
memset(buffer,'\0',BUFFER_SIZE);
int data_read=0;
int read_index = 0;
int check_index = 0;
int start_line = 0;
CHECK_STATE checkstate = CHECK_STATE_REQUESTLINE;
while (1)
{
data_read = recv(connfd,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,check_index,checkstate,read_index,start_line);
printf("%s\n",buffer);
if(result==NO_REQUEST) {
// puts("NO_REQUEST");
continue;
}
else if(result==GET_REQUEST) {
puts("GET_REQUEST");
send(connfd,szret[0],strlen(szret[0]),0);
break;
}
else {
puts("BAD_REQUEST");
send(connfd,szret[1],strlen(szret[1]),0);
break;
}
}
close(connfd);
}
close(sockfd);
return 0;
}