高性能服务器程序框架

高性能服务器程序框架

C/S模型

工作流程:

服务器启动后首先创建一个(或者多个)监听socket,并调用bind函数将其绑定在服务器感兴趣的端口上,然后调用listen函数等待客户连接。服务器稳定后客户端可以调用connect函数向服务器发起连接。由于客户连接请求是随机到达的异步事件,服务器需要使用某种I/O模型监听这一事件。下图是其中一种使用I/O复用技术的select系统调用

TCP服务器和TCP客户端工作流程

p2p模型

2种服务器模型

服务器基本框架

服务器基本框架

I/O处理单元是服务器管理客户连接的模块,用于等待并接受新的客户连接,接受客户数据,将服务器响应数据返回给客户端。对于服务器集群而言,i/O处理单元是一个专门的接入服务器,可以用于实现负载均衡

一个逻辑单元通常是一个进程或者线程,用于分析并处理客户的数据将结果传递给I/O处理单元或者直接俄发给客户端。

请求队列是各单元通信方式的抽象,请求队列通常实现为池的一部分。

I/O模型

阻塞I/O执行的系统调用可能因为无法立即完成被操作系统挂起,例如accept,send,recv,connect可能被阻塞

非阻塞I/O执行系统调用总数立即返回,如果事件没有立即发生则返回-1.

reactor 模式

要求主线程(i/O处理单元)只负责监听文件描述上是否有事件发生,有的话就立刻将事件通知工作线程(逻辑单元),除此之外主线程不做任何实质性的工作

reactor模式

主线程网epoll内核事件表种主存socket上的读就绪事件

主线程调用epoll_wait等待socket上有数据可读,并放入请求队列,请求队列唤醒睡眠工作线程,读取socket数据,处理客户请求并往epoll内核事件中注册该socket上的写就绪事件。

proactor模式

与reactor不同,将所有的I/O操作都交给主线程和内核来处理,工作线程仅负责业务逻辑。

proactor模式
同步I/O模拟proactor

服务器的主要的两种并发编程模式

半同步/半异步模式

同步:程序代码顺序执行

异步:需要有系统事件驱动(例如中断,信号等)

同步和异步
半同步半异步工作流程

同步线程用于处理用户逻辑,异步线程用于处理I/O事件,异步线程监听到客户请求后,将其封装成请求对象并插入队列,请求队列通知某个工作在同步模式的工作线程来读取并处理该对象。

变体:

半同步半反应堆模式

只有一个异步线程,由主线程来充当,负责监听所有socket上的事件,睡眠的工作线程在任务到来时通过竞争获得任务管辖权。

更高效的半同步/半异步模式

领导者/追随者模式

多个工作线程轮流获得事件源集合,轮流监听,分发并处理事件。

领导者/追随者模式组件

Handle:句柄用于表示I/O资源,

线程集:所有工作进程,每个线程可能存在的状态如下所示:

三种状态及转移关系

工作流程: 工作流程图

HTTP请求读取和分析

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
#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//读缓冲区大小
//主机可能的状态
// CHECK_STATE_REQUESTLINE表示正在分析请求行, CHECK_STATE_HEADER表示正在分析头部字段
enum CHECK_STATE { CHECK_STATE_REQUESTLINE = 0, CHECK_STATE_HEADER, CHECK_STATE_CONTENT };
//从状态机三种状态,LINE_OK行可读取状态读取到一个完整的行 LINE_BAD 行出错 LINE_OPEN 行数据尚且不完善
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 };
//为了简化问题,服务器应答报文不是完整http报文,只是返回了下面处理的结果
static const char* szret[] = { "I get a correct result\n", "Something wrong\n" };
//从状态机用于解析一行内容
LINE_STATUS parse_line( char* buffer, int& checked_index, int& read_index )
{
char temp;
//checked_index 指向buffer中当前正在分析的字节,read_index指向buffer中客户数据尾部的下一字节
//buffer中0~checked_index的字节都已经分析完毕,下面的循环分析checked_indx~(read_index-1)字节的内容
for ( ; checked_index < read_index; ++checked_index )
{
temp = buffer[ checked_index ];//获取当前要分析的字节
if ( temp == '\r' )//如果当前的字节是回车,则说明可能读取到完整的一行
{
if ( ( checked_index + 1 ) == read_index )
{
return LINE_OPEN;//当前不是完整的行,还需要继续读取客户数据
}
//成功读取到一行
else if ( buffer[ checked_index + 1 ] == '\n' )
{
buffer[ checked_index++ ] = '\0';
buffer[ checked_index++ ] = '\0';
return LINE_OK;
}
//否则说明客户端语法有问题
return LINE_BAD;
}
else if( temp == '\n' )
{
if( ( checked_index > 1 ) && buffer[ checked_index - 1 ] == '\r' )
{
buffer[ checked_index-1 ] = '\0';
buffer[ checked_index++ ] = '\0';
return LINE_OK;
}
return LINE_BAD;
}
}
return LINE_OPEN;
}
//分析请求行
HTTP_CODE parse_requestline( char* szTemp, CHECK_STATE& checkstate )
{
//strpbrk函数,其作用是在字符串 szTemp 中查找包含在字符串 " \t" 中的任何字符的第一个匹配项,
//并返回指向该位置的指针。接着,通过条件判断语句检查返回的指针是否为空。
char* szURL = strpbrk( szTemp, " \t" );
if ( ! szURL )
{
return BAD_REQUEST;//如果请求行中没有空白字符或者回车,说明http请求存在问题
}
*szURL++ = '\0';

char* szMethod = szTemp;
//客户端发出GET请求
if ( strcasecmp( szMethod, "GET" ) == 0 )
{
printf( "The request method is GET\n" );
}
else
{
return BAD_REQUEST;
}
//strspn函数的作用是计算字符串szURL连续包含\t字符的个数。返回后指针右移,指向下一句
szURL += strspn( szURL, " \t" );
char* szVersion = strpbrk( szURL, " \t" );
if ( ! szVersion )
{
return BAD_REQUEST;
}
//获取http版本号,仅支持HTTP/1.1
*szVersion++ = '\0';
szVersion += strspn( szVersion, " \t" );
if ( strcasecmp( szVersion, "HTTP/1.1" ) != 0 )
{
return BAD_REQUEST;
}
//if (strncasecmp(szURL, "http://", 7) == 0):
//这是一个条件判断语句,使用 strncasecmp 函数比较 szURL 前7个字符(不区分大小写)
//是否等于 "http://"。如果相等,说明 szURL 以 "http://" 开头。
if ( strncasecmp( szURL, "http://", 7 ) == 0 )
{
//szURL += 7;: 如果以 "http://" 开头,将 szURL 指针向后移动7个字符,以跳过 "http://" 部分。
szURL += 7;
//szURL = strchr(szURL, '/');: 接下来,
//使用 strchr 函数在新的 szURL 中找到第一个斜杠 '/' 的位置。这将更新 szURL 指针,使其指向第一个斜杠的位置。
szURL = strchr( szURL, '/' );
}

if ( ! szURL || szURL[ 0 ] != '/' )
{
return BAD_REQUEST;
}

//URLDecode( szURL );
//http请求行处理完毕,状态转移到头部字段的分析
printf( "The request URL is: %s\n", szURL );
checkstate = CHECK_STATE_HEADER;
return NO_REQUEST;
}
//分析头部字段
HTTP_CODE parse_headers( char* szTemp )
{
if ( szTemp[ 0 ] == '\0' )
{
return GET_REQUEST;
}
else if ( strncasecmp( szTemp, "Host:", 5 ) == 0 )
{
szTemp += 5;
szTemp += strspn( szTemp, " \t" );
printf( "the request host is: %s\n", szTemp );
}
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_CODE retcode = NO_REQUEST;
//主状态机从buffer中取出所有完整的行
while( ( linestatus = parse_line( buffer, checked_index, read_index ) ) == LINE_OK )
{
char* szTemp = buffer + start_line;//start line是在buffer中的起始位置
start_line = checked_index;//记录下一行位置
switch ( checkstate )//检查主状态机当前状态
{
case CHECK_STATE_REQUESTLINE: //第一个状态分析请求行
{
retcode = parse_requestline( szTemp, checkstate );
if ( retcode == BAD_REQUEST )
{
return BAD_REQUEST;
}
break;
}
case CHECK_STATE_HEADER://分析头部字段
{
retcode = parse_headers( szTemp );
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;
inet_pton( AF_INET, ip, &address.sin_addr );
address.sin_port = htons( port );

int listenfd = socket( PF_INET, SOCK_STREAM, 0 );
assert( listenfd >= 0 );

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

ret = listen( listenfd, 5 );
assert( ret != -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 < 0 )
{
printf( "errno is: %d\n", errno );
}
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 )//尚未完成一个完整的,正确的http请求
{
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;
}

状态转移图:

状态转移