在网络编程模型中,一个Session代表一次会话,主要维护网络数据的发送和接收。对外提供发送数据和处理数据的接口。一个高效的Session主要通过缓冲和异步来提高IO效率。NGServer的Session运用双缓冲和boost::asio的异步机制,很好地做到了这一点。
一. 双缓冲
在网络IO中,读写线程的互斥访问一直都是一个关乎性能的大问题。为了减少互斥锁的使用,环形缓冲和双缓冲是常见的策略。NGServer使用后者作为消息和数据缓冲。
在NGServer MessageQueue.h中,定义了两种双缓冲:基于消息的MessageQueue和基于数据的ByteBuff。下面简要介绍ByteBuff类:
ByteBuff类的基本思想是通过两个缓冲区_buff_read和_buff_write来使读写分离。通过size_t Push(const char* data, size_t len)
来写入数据:
1 | // 压入字节流数据 压入成功 则返回当前缓冲区长度 否则返回0 |
Push方法是线程安全的,它通过AutoLocker来保证对_buff_write的互斥访问。
char* PopAll(size_t& len)
用于读取数据:
1 | // 返回当前缓冲区指针 长度由len返回 若当前缓冲区无消息 返回nullptr |
它返回当前_buff_write的指针,并且交换_buff_write和_buff_read的指针,这样下次再Push数据时,实际上写到了之前的_read_buff中,如此交替,完成读写分离。
需要注意到,Push接口是线程安全的,而对于PopAll:
由于PopAll直接返回缓冲区指针(避免内存拷贝),因此同一时刻双缓冲中,必有一读一写,故同一时刻只能有一个线程读取和处理数据(处理数据时,_buff_read仍然是被占用的)。读取线程需要将上次PopAll的数据处理完成之后再次调用PopAll。因为调用PopAll时,之前的读缓冲已变成写缓冲,并且写缓冲将从头开始写。
基于消息的MessageQueue原理与ByteBuff一样,只不过_buff_read和_buff_write均为vector
二. Session类的设计
Session类利用boost::asio异步读写提高IO性能,它使用线性缓冲作为接收缓冲,使用ByteBuff作为发送缓冲,提高发送性能。由于ByteBuff同一时刻只能由一个线程读取和处理,Session需要使用一个锁来保证同一时刻只有一个线程来读取ByteBuff并发送其中的数据:
1 | // 发送数据 |
当网络空闲时,在SendAsync中,消息通过Push压入缓冲区后,将即时发送。当网络IO繁忙时,调用SendAsync中,可能已有数据正在发送,在将新数据压入缓冲区后,_sending_lock.TryLock()将返回false,此时数据被放在缓冲区中。待已有数据发送完成后,_sending_lock解锁。那么下次调用SendAsync发送的数据将和缓冲区中已有的数据立即发送。而ByteBuff双缓冲最大程度避免了这个过程中的内存拷贝。
Session将收到的数据放在线性缓冲区中,如此方便解包。在每次接收数据完成后,都尝试解包,并校正缓冲区新的偏移。