网络服务器开发

p协议设计(网络协议和应用协议)
n语法(报文格式)
n语义(指令的处理, 交互时序等)
n只要涉及到交互(即使不是网络交互), 就需要协议
p
p网络实现(IO)
n网络开发的基础是socket
n任何对网络的封装, 在三度关系之内必然是socket(最多三层封装)
最简单的tcp服务器
serv = new TcpSocket();serv.listen();sock = serv.accept();

sock.read(buf);

sock.write(buf);

sock.close()

p 一次服务
p 没有并发
p 短连接
p 有没有协议?
p
重复服务
serv = new TcpSocket();serv.listen();while(1){

sock = serv.accept();

while(1){//重复服务单用户

sock.read(buf);

sock.write(buf);

}

sock.close()

}

p 一次服务
p 没有并发
p 短连接
pread/write是可阻塞的
p阻塞是并发和高性能的死敌
p实现并发和高性能的途径就是解阻塞
p太简单了!
p
p后面继续讲解read/write...
并发服务
serv = new TcpSocket();serv.listen();while(1){

sock = serv.accept();

new Thread(){

while(1){//重复服务单用户

sock.read(buf);

sock.write(buf);

}

sock.close()

}

}

p 一次服务
p 没有并发
p 短连接
优缺点
p优点:
n简单易用, 一般工作得很好
n多线程, 可以利用CPU多核
p
p缺点:
n启动线程也有成本, 很可能占大头
n线程的数量不受控制, 危险!
n
p问题在于IO...
io 多路复用
p专业地解决一个问题
pselect/poll/epoll/kqueue的API几乎一样, 实现不同, 性能不同
pIO多路复用接口的作用, 就是测试accept/read/write等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 为空, 因为我们不测试可写

}

pwrite也可能阻塞, 修改...
p

sock.read(buf);

sock.write(buf); sock.close();

select.add(sock, WRITE);

foreach(writable as sock){

sock.write(buf);

sock.close();

}

报文设计

p最重要的TCP协议是流式协议,                                但几乎所有的应用协议都是基于报文的协议
n报文分隔
p用连接关闭来表示报文结束. 如, HTTP/1.0的响应
p固定长度的报文. 如, TFTP的数据报文.
p带自描述长度的固定长度首部的变长报文. 如IP包, TCP分段.
p带结束符. 如, 行协议, HTTP协议. 逐字节解析和数据转义的影响.
高层 文本, 带结束符
底层 二进制, 固定长度
ssdb的报文格式

Packet := Block+ '\n'

Block := Size Data

Size := literal_integer '\n'

Data := size_bytes_of_data '\n'

示例:

3

get

3

key

<回车>

优点

p简单
p带长度字段, 支持二进制数据
p同时对人和对机器友好, 报文数据文本化效果好. 可以telnet到服务器进行交互
p解析器非常简单, PHP代码不过几十行
p同时兼容LF和CRLF(惯例)
socket io2
pread/write读取/发送的是字节数组. C语言的char[], PHP的string
pread返回时读取的准确字节数无法预计
n导致"粘包", "断包"
pwrite返回时不表示数据已到达对方机器
n所以, 即便基于可靠传输的TCP协议, 也需要应用层协议进行确认来保证真正意义上的"可靠"
带有报文解析的服务器
serv = new TcpSocket();serv.listen();sock = serv.accept();

client = new Client(sock);

client.recv(packet);

client.send(packet);

client.close()

ppacket一般是编程语言中的对象
序列化和反序列化
pread收到的是字节数组, 要进行反序列化转成编程语言的对象
p反序列化的过程就是报文解析
解析报文

bytes = [];

while(1){

bytes += sock.read();

if(try_parse(bytes, &len) == READY){

// 已经解析出了一个报文, 报文的长度是len

// 从字节数组中清除掉已解析的

bytes.remove(len);

}

}

整合网络IO和报文解析

p将上例中的read和try_parse分离
p作业...
第三节 – 业务处理
p网络服务器 = 协议处理 + 网络IO + 业务处理
业务处理的位置
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 中.

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