More Related Content
Similar to 高性能并发网络服务器设计与实现
Similar to 高性能并发网络服务器设计与实现 (20)
高性能并发网络服务器设计与实现
- 2. 网络服务器开发
协议设计 ( 网络协议和应用协议 )
语法 ( 报文格式 )
语义 ( 指令的处理 , 交互时序等 )
只要涉及到交互 ( 即使不是网络交互 ), 就需要协议
网络实现 (IO)
网络开发的基础是 socket
任何对网络的封装 , 在三度关系之内必然是
socket( 最多三层封装 )
- 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 服务器
- 8. 优缺点
优点 :
简单易用 , 一般工作得很好
多线程 , 可以利用 CPU 多核
缺点 :
启动线程也有成本 , 很可能占大头
线程的数量不受控制 , 危险 !
问题在于 IO...
- 9. IO 多路复用
专业地解决一个问题
select/poll/epoll/kqueue 的 API 几乎一样 , 实
现不同 , 性能不同
IO 多路复用接口的作用 , 就是测试
accept/read/write 等 IO 调用会不会阻塞
- 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 为空 , 因为我们不测试可写
}
- 13. 报文设计
最重要的 TCP 协议是流式协议 ,
但几乎
所有的应用协议都是基于报文的
协议
报文分隔
用连接关闭来表示报文结束 . 如 ,
HTTP/1.0 的响应
固定长度的报文 . 如 , TFTP 的数
据报文 .
带自描述长度的固定长度首部的变
长报文 . 如 IP 包 , TCP 分段 .
带结束符 . 如 , 行协议 , HTTP 协
议 . 逐字节解析和数据转义的影响
.
高层 文本 , 带结
束符
底层 二进制 , 固
定长度
- 14. SSDB 的报文格式
Packet := Block+ 'n'
Block := Size Data
Size := literal_integer 'n'
Data := size_bytes_of_data 'n'
示例 :
3
get
3
key
< 回车 >
- 15. 优点
简单
带长度字段 , 支持二进制数据
同时对人和对机器友好 , 报文数据文本化效果好
. 可以 telnet 到服务器进行交互
解析器非常简单 , PHP 代码不过几十行
同时兼容 LF 和 CRLF( 惯例 )
- 16. socket io 2
read/write 读取 / 发送的是字节数组 . C 语言的
char[], PHP 的 string
read 返回时读取的准确字节数无法预计
导致 " 粘包 ", " 断包 "
write 返回时不表示数据已到达对方机器
所以 , 即便基于可靠传输的 TCP 协议 , 也需要应用层
协议进行确认来保证真正意义上的 " 可靠 "
- 17. 带有报文解析的服务器
serv = new TcpSocket();
serv.listen();
sock = serv.accept();
client = new Client(sock);
client.recv(packet);
client.send(packet);
client.close()
packet 一般是编程语言中的对象
- 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. IO 多路复用正确实践
IO 多路复用的程序具有相同的核心逻辑 ( 主循
环 )
初学者套用固定套路也能写出可用的代码
初学者对 IO 多路复用的不完整理解 , 会导致程
序具有致命缺陷
- 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. 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. 错误在哪 ?
时间点 客户端 服务器端
0ms 发出请求
10ms 网络延时 10ms, 收到请求
20ms 线程处理完毕
220ms epoll.wait() 阻塞了 200ms
220ms client.send()
230ms 网络延时 10ms, 收到响应
从客户端的角度 , 请求花了 230ms
从服务器端的角度 , 它只化了 20ms 处理请求
问题出在错误的 epoll 使用 !
- 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