有限状态机¶
逻辑单元内部的编程方式
状态独立的有限状态机
状态独立的有限状态机
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_A(_pack);
cur_state = type_B; // 状态转移
break;
case type_B:
process_package_B(_pack);
cur_state = type_C; // 状态转移
break;
}
}
} //type_A: 状态机的开始状态;type_C:状态机的结束状态
http请求的读取与分析¶
- http头部结束:一对回车换行符
- 第一次读操作没有读入整个http头部,即没有遇到空行(一对回车换行符):等待客户继续写数据再次读入
- 每完成一次读操作,分析数据中是否有空行
- 寻找空行的同时,完成对http头部的分析
http头部读取与分析
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<stdio.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 //当前正在分析头部信息
};
//从状态机的状态
enum LINE_STAET {
LINE_OK = 0, //完整行
LINE_BAD, //行出错
LINE_OPEN //行数据尚不完整
};
//服务器http的请求结果
enum HTTP_CODE {
NO_REQUEST, //请求不完整,继续读客户数据
GET_REQUEST, //获得一个完整的请求
BAD_REQUEST, //客户请求有错误
FORBIDDEN_REQUEST, //客户对资源没有访问权限
INTERNAL_ERROR, //服务器内部错误
CLOSED_CONNECTION //客户已关闭连接
};
//接收正确与错误的信息
static const char* szert[] = {"I get a correct result\n",
"Something wrong\n"};
//从状态机:解析一行内容
LINE_STATE parse_line(char* buffer, int& checked_index, int& read_index) {
char tmp;
// checked_index 指向buffer 中当前正在分析的字节
// read_index 指向buffer数据尾部的下一字节
// 0~checked_index 字节已经分析完
// checked_index ~ (read_index-1) 字节下个循环分析
for(; checked_index < read_index; ++checked_index) {
//获得当前要分析的字节
tmp = buffer[checked_index];
//若当前字符是“\r”,可能读到完整的一个行,继续分析
if(tmp = '\r') {
//恰巧是读缓冲区中最后一个读入的数据,不是完整的一行
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;
}
//否则当成出错
return LINE_BAD;
}
//如果读到“\n”,可能读到完整的一行,继续分析
else if(tmp == '\n') {
//上一个字节是“\r”,则说明读到完整的一行
if((checked_index > 1) && buffer[checked - 1] == '\r') {
buffer[checked - 1] = '\0';
buffer[checked++] = '0';
return LINE_OK;
}
return LINE_BAD;
}
}
// 所有内容分析完,没有“\r”
return LINE_OPEN;
}
//分析请求行
HTTP_CODE parse_requestline(char* tmp, CHECK_STATE& checkstate) {
char* url = strpbrk(tmp, "\t");
//如果请求行没有空白字符或者“\t”,则http请求有问题
if(!url) {
return BAD_REQUEST;
}
*url++ = '\0';
char* methon = tmp;
if(strcasecmp(methon, "GET") == 0) //仅支持“GET”方法
{
printf("The request is GET\n");
} else {
return BAD_REQUEST;
}
url += strspn(url, "\t");
char* version = strpbrk(url, "\t");
if(!version) {
return BAD_reQUEST;
}
*version += '\0';
version += strspn(version, "\t");
//仅支持 http/1.1
if(strcasecmp(version, "HTTP/1.1") != 0) {
return BAD_REQUEST;
}
//检查url是否合法
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);
//http请求行解析结束,状态转移到解析头部字段
checkstate = CHECK_STATE_HEADER;
return NO_REQUEST;
}
// 分析头部字段
HTTP_CODE parse_headers(char* tmp) {
//遇到空行,说明是正确的http请求
if(tmp[0] == '\0') {
return GET_REQUEST;
}
else if(strncasecmp(tmp, "Host:", 5) == 0) //host头部字段
{
tmp += 5;
tmp += strspn(tmp, '\t');
printf("The request host is: %s\n", tmp);
}
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请求的处理如果
HTTP_CODE rescode = NO_REQUEST;
//主状态机:从buffer中取出所有完整的行
while(linestatus = parse_line(buffer, checked_index, read_index) == LINE_OK) {
char* tmp = buffer + start_line; //行在buffer中的位置(从开头处理)
start_line = checked_index; //记录下一行的起始位置
switch(checkstate) {
case CHECK_STATE_REQUESTLINE:
{
rescode = parse_requestline(tmp, checkstate);
if(rescode == BAD_REQUEST) {
return BAD_REQUEST;
}
break;
}
case CHECK_STATE_HEADER:
{
rescode = parse_headers(tmp);
if(rescode == BAD_REUQEST) {
return BAD_REQUEST;
} else if (rescode == GET_REQUESt) {
return GET_REQUEST;
}
break;
}
default:
{
return INTERNAL_ERROR;
}
}
}
//没有读到完整的一行,还要继续读取客户数据进行下一步
if(linestatus == LINE_OPEN) {
return NO_REUQEST;
}
else {
return BAD_REQUEST;
}
}
int main(int argc, char* argv[]) {
if(argc != 2) {
printf("usage: %s ip_address port_num\n",basename(argv[0]));
return 1;
}
const char* ip = argv[0];
int port = atoi(argv[1]);
struct sockaddr_in address;
bzero(&address, sizeof(address));
adderss.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(listen == 0);
int res = bind(listenfd, (struct sockaddr*)&address, sizeof(address));
assert(ret != -1);
ret = listen(listenfd, 5);
assert(ret != -1);
struct sockaddr_in clientfd;
socklen_t clen = sizeof(clientfd);
int fd = accept(listenfd, (struct sockaddr*)&clientfd, &clen);
if(fd < 0) {
printf("errno is: %d\n", errno);
} else {
char buffer[BUFFER_SIZE];
memset(buffer, '\0', BUFFEr_SIZE);
int data_read = 0; //已接收多少字节数据
int checked_index = 0; //正在解析的位置
int read_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) {
break;
}
else if(data_read == 0) { //对端关闭
printf("remote client has closed\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;
}
strspn
:
-
返回str1的初始部分的长度,该长度仅由属于str2的字符组成。
-
搜索不包括这两个字符串的结束空字符,但在那里结束。
-
strpbrk
:
const char * strpbrk ( const char * str1, const char * str2 );
char * strpbrk ( char * str1, const char * str2 );
- 返回一个指针,指向str1中任何属于str2的字符的第一个匹配项,如果没有匹配项,则返回一个空指针。
-
搜索不包括这两个字符串的结束空字符,但在那里结束。
-
strcasecmp
:
- 比较参数s1 和s2 字符串,比较时会自动忽略大小写的差异
-
返回值:若参数s1 和s2 字符串相同则返回0。s1 长度大于s2 长度则返回大于0 的值,s1 长度若小于s2 长度则返回小于0 的值
-
strchr
:
- 在参数 str 所指向的字符串中搜索第一次出现字符 c(一个无符号字符)的位置
http报文段¶
请求报文¶
- 请求行
- 请求头部
- 空行
- 请求数据
GET和POST(ep)¶
GET
GET /562f25980001b1b106000338.jpg HTTP/1.1
Host:img.mukewang.com
User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36
Accept:image/webp,image/*,*/*;q=0.8
Referer:http://www.imooc.com/
Accept-Encoding:gzip, deflate, sdch
Accept-Language:zh-CN,zh;q=0.8
空行
请求数据为空
POST
POST / HTTP1.1
Host:www.wrox.com
User-Agent:Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)
Content-Type:application/x-www-form-urlencoded
Content-Length:40
Connection: Keep-Alive
空行
name=Professional%20Ajax&publisher=Wiley
请求行¶
methon
:
分析请求行函数中
char* url = strpbrk(tmp, "\t");
//如果请求行没有空白字符或者“\t”,则http请求有问题
if(!url) {
return BAD_REQUEST;
}
*url++ = '\0';
char* methon = tmp;
if(strcasecmp(methon, "GET") == 0) //仅支持“GET”方法
{
printf("The request is GET\n");
} else {
return BAD_REQUEST;
}
-
strpbrk
使得url
指针指向tmp
字符串的第一个\t
,即GET之后的“\t”;并且通过*url++='\0'
将“\t”置为“\0”;则下面使用strcasecmp
函数比较mrthon
和“GET”是否相同时,strcasecmp
函数只会对比到"\0" -
version
:
分析请求行函数中
url += strspn(url, "\t");
char* version = strpbrk(url, "\t");
if(!version) {
return BAD_reQUEST;
}
*version += '\0';
version += strspn(version, "\t");
//仅支持 http/1.1
if(strcasecmp(version, "HTTP/1.1") != 0) {
return BAD_REQUEST;
}
-
strspn
函数使得url
直接跳过/562f25980001b1b106000338.jpg
来到其后的"\t",因为此时需要获取http的版本号;strpbrk
函数同样将HTTP/1.1
的版本号获取并将其后的“\t”置为“\0”,以便于后续strcasecmp
函数的对比 -
判断是否为合法的
url
:
分析请求行函数中
//检查url是否合法
if(strncasecmp(url, "http://", 7) == 0) {
url += 7;
url = strchr(url, '/');
}
if(!url || url[0] != '/') {
return BAD_REQUEST;
}
请求头部¶
分析请求头函数中
HTTP_CODE parse_headers(char* tmp) {
//遇到空行,说明是正确的http请求
if(tmp[0] == '\0') {
return GET_REQUEST;
}
else if(strncasecmp(tmp, "Host:", 5) == 0) //host头部字段
{
tmp += 5;
tmp += strspn(tmp, '\t');
printf("The request host is: %s\n", tmp);
}
else //其他头部字段不处理
{
printf("I can not handle this header\n");
}
return NO_REQUEST;
}
状态机状态¶
- 从状态机:从状态机一开始的状态是LINE_OK,在main函数中首先调用recv函数不断读取客户数据,每次读取成功之后,都会调用
parse_content
函数取出数据(缓冲区)中所有完整的行;parse_content
函数首先调用parse_line
函数获取完整的一行,判断每读取的数据中有没有行结束符;如果当前没有行结束符,则从状态机状态置为LINE_OPEN;如果读到完整的一行数据,则将其递交给主状态机处理 - 主状态机:如果主状态机的当前状态为CHECK_STATE_REQUESTLINE,则表示读到的是请求行,则主状态机调用
check_requestline
函数来处理;如果主状态机当前状态为CHECK_STATE_HEADER,则表示读到的是请求头部,则主状态机调用check_headers
函数来处理。主状态机的初始状态为CHECK_STATE_REQUESTLINE,函数check_requestline
调用完成之后会将主状态机状态置为CHECK_STATE_HEADER,以实现状态的转移