1. 服务器基本框架
IO处理单元 一个专门的接入服务器,他实现负载均衡,从所有的逻辑服务器中选取负荷最小的一台来为新客户服务。
逻辑单元 一个逻辑单元通常是一个进程或者线程,它分析并处理客户数据。
网络存储单元 数据库或者缓存(非必须)
2. IO模型
同步IO想应用程序通知的是IO就绪事件,异步IO向应用程序通的是完成事件,以食堂打饭为例。
- 阻塞IO 与 非阻塞 IO
- 阻塞IO 你去食堂打饭,饭还没好,你就在那里一直等。饭好了,你等着阿姨把饭给你打好。
- 非阻塞IO 你去打饭,饭还没好,你走了。再过十分钟,你又去问,还没好,你又走了。一直循环,直到你一次去问,阿姨告诉你饭好了。你等着阿姨把饭给你打好
- 同步IO 与 异步IO 阻塞IO,IO复用,信号驱动IO都是同步IO模型 异步IO:你让阿姨把饭打好,放在饭盒里,送到你面前。所有的过程你都不需要等待。
3. 两种高效的事件处理模式
1. Reactor(同步IO)
1
非阻塞同步网络模式,感知的是就绪可读写事件。
2. Proactor(异步IO)
1
异步网络模式,感知的是已完成的读写事件。
因此,Reactor 可以理解为「来了事件操作系统通知应用进程,让应用进程来处理」,而 Proactor 可以理解为「来了事件操作系统来处理,处理完再通知应用进程」。这里的「事件」就是有新连接、有数据可读、有数据可写的这些 I/O 事件这里的「处理」包含从驱动读取到内核以及从内核读取到用户空间。
4. 有限状态机
1
主要针对逻辑单元内部处理。其应用如下:有点应用层协议头部包含数据类型字段,每中类型可以映射为逻辑单元的一种执行状态,服务器可以根据它来编写相应的逻辑处理。
- 独立状态的有限状态机
每个状态都是独立的。
1
2
3
4
5
6
7
8
9
10
11
STATE_MACHINE(Package _pack){
PackageType _type = _pack.GetType();
sticth(_type){
case type_A:
process_package_A(_pack);
break;
case type_B:
process_package_B(_pack);
break;
}
}
- 带状态转移的有限状态机
状态间的转移是需要状态机内部驱动的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
STATE_MACHINE(Package _pack){
State cur_State = type_A;
while(cur_State != type_C){
PackageType _pack = getNewPackage();
sticth(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;
}
}
}
5. 状态机 与 http
本文为了方便,下将称HTTP请求的一行(包括请求行和头部字段)为行。
状态机的状态说明
1 2 3 4 5 6
// 主状态机的两种状态 正在分析请求行 正在分析头部字段 enum CHECK_STATE { CHECK_STATE_REQUESTLINE = 0, CHECK_STATE_HEADER }; // 从状态机的3种状态 1. 读取到一个完整的行 2. 行出错 3. 行数据尚不完整 enum LINE_STATUS { LINE_OK = 0, LINE_BAD, LINE_OPEN }; // 服务器处理HTTP请求的结果 1. 请求不完整 2. 获得一个完整的客户请求 3. 请求语法错误 4. 没有访问权限 5. 服务器内部错误 6. 客户端已经关闭连接 enum HTTP_CODE { NO_REQUEST, GET_REQUEST, BAD_REQUEST, FORBIDDEN_REQUEST, INTERNAL_ERROR, CLOSED_CONNECTION };
几个buffer读取参数
1 2 3 4
int data_read = 0; int read_index = 0; // 当前已经读取了多少字节的客户数据 int checked_index = 0; // 当前已经分析了多少字节的客户数据 int start_line = 0; // 行在buffer中的起始位置
6.http 服务器代码解析
为了方便阅读逻辑 次为代码精简
main
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
char buffer[ BUFFER_SIZE ]; // 读缓冲区
int data_read = 0;
int read_index = 0; // 当前已经读取了多少字节的客户数据
int checked_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 );
// 分析目前已经获得的客户端数据
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;
}
主状态机的转换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 分析http请求的入口函数
HTTP_CODE parse_content( char* buffer, int& checked_index, CHECK_STATE& checkstate, int& read_index, int& start_line )
{
switch ( checkstate )
{
case CHECK_STATE_REQUESTLINE: // 状态一:分析请求行
{
retcode = parse_requestline( szTemp, checkstate );
}
case CHECK_STATE_HEADER: // 状态二:分析头部字段
{
retcode = parse_headers( szTemp );
}
default:
{
return INTERNAL_ERROR;
}
}
}
}
7. 提高服务器性能的几点建议
池技术
数据复制
上下文切换