高性能并发网络服务器
设计与实现
http://www.ideawu.net/
网络服务器开发
 协议设计 ( 网络协议和应用协议 )
 语法 ( 报文格式 )
 语义 ( 指令的处理 , 交互时序等 )
 只要涉及到交互 ( 即使不是网络交互 ), 就需要协议
 网络实现 (IO)
 网络开发的基础是 soc...
第一节 – 网络 IO
Talk is cheap, show me the code
serv = new TcpSocket();
serv.listen();
sock = serv.accept();
sock.read(buf);
sock.write(bu...
重复服务
socket IO
 read/write 是可阻塞的
 阻塞是并发和高性能的死敌
 实现并发和高性能的途径就是解阻塞
 太简单了 !
 后面继续讲解 read/write...
并发服务
优缺点
 优点 :
 简单易用 , 一般工作得很好
 多线程 , 可以利用 CPU 多核
 缺点 :
 启动线程也有成本 , 很可能占大头
 线程的数量不受控制 , 危险 !
 问题在于 IO...
IO 多路复用
 专业地解决一个问题
 select/poll/epoll/kqueue 的 API 几乎一样 , 实
现不同 , 性能不同
 IO 多路复用接口的作用 , 就是测试
accept/read/write 等 IO 调用会不...
IO 多路复用示例
serv = new TcpSocket(); serv.listen();
select.add(serv, READ); // READ 表示只测试可读 ( 读不阻塞 )
while(1){
readable, writ...
还不完善
第二节 – 报文解析
报文设计
 最重要的 TCP 协议是流式协议 ,
但几乎
所有的应用协议都是基于报文的
协议
 报文分隔
 用连接关闭来表示报文结束 . 如 ,
HTTP/1.0 的响应
 固定长度的报文 . 如 , TFTP 的数
据报文 .
 带...
SSDB 的报文格式
Packet := Block+ 'n'
Block := Size Data
Size := literal_integer 'n'
Data := size_bytes_of_data 'n'
示例 :
3
get
3...
优点
 简单
 带长度字段 , 支持二进制数据
 同时对人和对机器友好 , 报文数据文本化效果好
. 可以 telnet 到服务器进行交互
 解析器非常简单 , PHP 代码不过几十行
 同时兼容 LF 和 CRLF( 惯例 )
socket io 2
 read/write 读取 / 发送的是字节数组 . C 语言的
char[], PHP 的 string
 read 返回时读取的准确字节数无法预计
 导致 " 粘包 ", " 断包 "
 write 返回时...
带有报文解析的服务器
serv = new TcpSocket();
serv.listen();
sock = serv.accept();
client = new Client(sock);
client.recv(packet);
cl...
序列化和反序列化
 read 收到的是字节数组 , 要进行反序列化转成编
程语言的对象
 反序列化的过程就是报文解析
解析报文
bytes = [];
while(1){
bytes += sock.read();
if(try_parse(bytes, &len) == READY){
// 已经解析出了一个报文 , 报文的长度是 len
// 从字节数组中...
整合网络 IO 和报文解析
 将上例中的 read 和 try_parse 分离
 作业 ...
第三节 – 业务处理
 网络服务器 = 协议处理 + 网络 IO + 业务处
理
业务处理的位置
serv = new TcpSocket();
serv.listen();
sock = serv.accept();
client = new Client(sock);
request = client.recv();
r...
IO 多路复用正确实践
 IO 多路复用的程序具有相同的核心逻辑 ( 主循
环 )
 初学者套用固定套路也能写出可用的代码
 初学者对 IO 多路复用的不完整理解 , 会导致程
序具有致命缺陷
IO 多路复用的基本框架
while(1){
readables = epoll.wait();
foreach(readables as r){
if(r.type == SERVER){
client = r.accept();
epoll...
IO 多路复用 + 多线程 ( 错误 )
while(1){
readables = epoll.wait(200);
foreach(readables as r){
if(r.type == SERVER){
client = r.acce...
错误在哪 ?
时间点 客户端 服务器端
0ms 发出请求
10ms 网络延时 10ms, 收到请求
20ms 线程处理完毕
220ms epoll.wait() 阻塞了 200ms
220ms client.send()
230ms 网络延时 ...
接上
epoll.wait(200) 的阻塞时间是 0ms ~ 200ms
参数决定阻塞上限 , 但不能改为 0, 否则 CPU 占用
100%
解决方案 :
SelectableQueue, 让 epoll 来监听 queue, 使得...
IO 多路复用 + 多线程 ( 正确 )
queue = new SelectableQueue();
epoll.add(queue);
while(1){
readables = epoll.wait();
foreach(readable...
剩下的 ...
 就是看代码写代码 ...
FIN
Thanks
C++ 网络服务器开发框架 sim:
https://github.com/ideawu/sim
Changelog
2015-06-06 更新
Upcoming SlideShare
Loading in …5
×

高性能并发网络服务器设计与实现

6,482 views

Published on

Published in: Technology
0 Comments
13 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
6,482
On SlideShare
0
From Embeds
0
Number of Embeds
3,552
Actions
Shares
0
Downloads
119
Comments
0
Likes
13
Embeds 0
No embeds

No notes for slide

高性能并发网络服务器设计与实现

  1. 1. 高性能并发网络服务器 设计与实现 http://www.ideawu.net/
  2. 2. 网络服务器开发  协议设计 ( 网络协议和应用协议 )  语法 ( 报文格式 )  语义 ( 指令的处理 , 交互时序等 )  只要涉及到交互 ( 即使不是网络交互 ), 就需要协议  网络实现 (IO)  网络开发的基础是 socket  任何对网络的封装 , 在三度关系之内必然是 socket( 最多三层封装 )
  3. 3. 第一节 – 网络 IO
  4. 4. Talk is cheap, show me the code serv = new TcpSocket(); serv.listen(); sock = serv.accept(); sock.read(buf); sock.write(buf); sock.close()  一次服务  没有并发  短连接  有没有协议 ?  最简单的 TCP 服务器
  5. 5. 重复服务
  6. 6. socket IO  read/write 是可阻塞的  阻塞是并发和高性能的死敌  实现并发和高性能的途径就是解阻塞  太简单了 !  后面继续讲解 read/write...
  7. 7. 并发服务
  8. 8. 优缺点  优点 :  简单易用 , 一般工作得很好  多线程 , 可以利用 CPU 多核  缺点 :  启动线程也有成本 , 很可能占大头  线程的数量不受控制 , 危险 !  问题在于 IO...
  9. 9. IO 多路复用  专业地解决一个问题  select/poll/epoll/kqueue 的 API 几乎一样 , 实 现不同 , 性能不同  IO 多路复用接口的作用 , 就是测试 accept/read/write 等 IO 调用会不会阻塞
  10. 10. IO 多路复用示例 serv = new TcpSocket(); serv.listen(); select.add(serv, READ); // READ 表示只测试可读 ( 读不阻塞 ) while(1){ readable, writable = select.wait(); foreach(readable as sock){ if(sock == serv){ sock = serv.accept(); select.add(sock, READ); }else{ sock.read(buf); sock.write(buf); sock.close(); } } // writable 为空 , 因为我们不测试可写 }
  11. 11. 还不完善
  12. 12. 第二节 – 报文解析
  13. 13. 报文设计  最重要的 TCP 协议是流式协议 , 但几乎 所有的应用协议都是基于报文的 协议  报文分隔  用连接关闭来表示报文结束 . 如 , HTTP/1.0 的响应  固定长度的报文 . 如 , TFTP 的数 据报文 .  带自描述长度的固定长度首部的变 长报文 . 如 IP 包 , TCP 分段 .  带结束符 . 如 , 行协议 , HTTP 协 议 . 逐字节解析和数据转义的影响 . 高层 文本 , 带结 束符 底层 二进制 , 固 定长度
  14. 14. SSDB 的报文格式 Packet := Block+ 'n' Block := Size Data Size := literal_integer 'n' Data := size_bytes_of_data 'n' 示例 : 3 get 3 key < 回车 >
  15. 15. 优点  简单  带长度字段 , 支持二进制数据  同时对人和对机器友好 , 报文数据文本化效果好 . 可以 telnet 到服务器进行交互  解析器非常简单 , PHP 代码不过几十行  同时兼容 LF 和 CRLF( 惯例 )
  16. 16. socket io 2  read/write 读取 / 发送的是字节数组 . C 语言的 char[], PHP 的 string  read 返回时读取的准确字节数无法预计  导致 " 粘包 ", " 断包 "  write 返回时不表示数据已到达对方机器  所以 , 即便基于可靠传输的 TCP 协议 , 也需要应用层 协议进行确认来保证真正意义上的 " 可靠 "
  17. 17. 带有报文解析的服务器 serv = new TcpSocket(); serv.listen(); sock = serv.accept(); client = new Client(sock); client.recv(packet); client.send(packet); client.close()  packet 一般是编程语言中的对象
  18. 18. 序列化和反序列化  read 收到的是字节数组 , 要进行反序列化转成编 程语言的对象  反序列化的过程就是报文解析
  19. 19. 解析报文 bytes = []; while(1){ bytes += sock.read(); if(try_parse(bytes, &len) == READY){ // 已经解析出了一个报文 , 报文的长度是 len // 从字节数组中清除掉已解析的 bytes.remove(len); } }
  20. 20. 整合网络 IO 和报文解析  将上例中的 read 和 try_parse 分离  作业 ...
  21. 21. 第三节 – 业务处理  网络服务器 = 协议处理 + 网络 IO + 业务处 理
  22. 22. 业务处理的位置 serv = new TcpSocket(); serv.listen(); sock = serv.accept(); client = new Client(sock); request = client.recv(); response = process(request); client.send(response); client.close() 对于 SSDB, LevelDB 相关的操 作封装在 process 中 .
  23. 23. IO 多路复用正确实践  IO 多路复用的程序具有相同的核心逻辑 ( 主循 环 )  初学者套用固定套路也能写出可用的代码  初学者对 IO 多路复用的不完整理解 , 会导致程 序具有致命缺陷
  24. 24. IO 多路复用的基本框架 while(1){ readables = epoll.wait(); foreach(readables as r){ if(r.type == SERVER){ client = r.accept(); epoll.add(client); } if(r.type == CLIENT){ data = r.receive(); resp = process(data); r.send(resp); } } }  经典套路  易学上手  process 可能阻塞
  25. 25. IO 多路复用 + 多线程 ( 错误 ) while(1){ readables = epoll.wait(200); foreach(readables as r){ if(r.type == SERVER){ client = r.accept(); epoll.add(client); } if(r.type == CLIENT){ data = r.receive(); queue.push(data); } } resp = queue.pop(); resp.client.send(resp); }  经典套路  易学上手  多线程  queue.pop() 可 能阻塞
  26. 26. 错误在哪 ? 时间点 客户端 服务器端 0ms 发出请求 10ms 网络延时 10ms, 收到请求 20ms 线程处理完毕 220ms epoll.wait() 阻塞了 200ms 220ms client.send() 230ms 网络延时 10ms, 收到响应  从客户端的角度 , 请求花了 230ms  从服务器端的角度 , 它只化了 20ms 处理请求  问题出在错误的 epoll 使用 !
  27. 27. 接上 epoll.wait(200) 的阻塞时间是 0ms ~ 200ms 参数决定阻塞上限 , 但不能改为 0, 否则 CPU 占用 100% 解决方案 : SelectableQueue, 让 epoll 来监听 queue, 使得 epoll.wait() 不会无端阻塞
  28. 28. IO 多路复用 + 多线程 ( 正确 ) queue = new SelectableQueue(); epoll.add(queue); while(1){ readables = epoll.wait(); foreach(readables as r){ if(r.type == SERVER){ client = r.accept(); epoll.add(client); } if(r.type == CLIENT){ data = r.receive(); queue.push(data); } if(r.type == QUEUE){ resp = queue.pop(); resp.client.send(resp); } } }  经典套路  易学上手  多线程  需要实现 SelectableQueu e
  29. 29. 剩下的 ...  就是看代码写代码 ...
  30. 30. FIN Thanks C++ 网络服务器开发框架 sim: https://github.com/ideawu/sim
  31. 31. Changelog 2015-06-06 更新

×