使用 C++ 构建高性能网络服务

本文介绍了如何使用 C++ 构建高性能的网络服务,特别是基于 Reactor 模型的设计。讨论了线程与 IO 的处理,如非阻塞模式、Acceptor 和 Connector 线程,以及 Server 和 Client 的连接建立。同时,文章还涵盖了接口设计,包括 Server 和 Client 的回调函数,以及如何处理套接字的读写和关闭。在性能方面,文中提到在本地测试中,两万连接的建立仅用5秒,并展示了良好的系统稳定性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于 Reactor

Reactor 是目前业界对于海量 TCP 处理的最佳解决方案,该模型最早在 Netty 中被实现。

Netty是Java的一个高性能网络库,看了一下架构设计,主要是两个模块:
1.网络套接字处理。对于IO多路复用以及线程池的抽象,采用reactor模型实现高性能。
2.对应用层协议的支持。粗略看了一眼HTTP/redis/sock5代理/smtp/dns/protobuf。感兴趣的同学可以自行查看4.1最新的JavaDoc。Netty API Reference

那么对于 C++ 而言,有没有类似的网络库呢?答案肯定是有的,所以在写这个模型的时候,本人也试图写出一些新意。

新在哪里?

  1. TCP的协议族共三个:ipv4、ipv6、unix。unix域协议族底层跟fifo/pipe走的是一套代码,从socketpair这个系统调用大概就能窥见一般,有的系统上甚至将sock文件直接表示为fifo文件。使用reactor处理并没有实际意义。但对于ipv6的支持却是一个小的创新,在cppev中提供了这样的支持。
  2. 封装。在设计TCP Server/Client时,尽量提供给用户更舒服的使用方式,更加一致的内存管理,数据几乎都存放于堆区。
  3. 性能。这个见仁见智,虽然并没有看过其他库的代码,但自问性能是优化到极致的,如果哪位能发现疏漏,也欢迎指正。

架构设计

线程与IO的处理

对于 Reactor 模型的实现架构如下:
在这里插入图片描述
请注意 IO 均采用非阻塞模式;对于Server/Client来说,唯一的不同就是已连接套接字的获取方式。

详细讲解:

  1. Server使用Acceptor线程,内含eventloop(即IO多路复用)对监听套接字注册可读事件监听;Client使用Connector线程,内含eventloop对管道文件描述符注册可读事件监听(这里的管道是一个self-pipe技巧,在对这个并发客户端投入task时会在管道另一端写入以触发读事件,使得线程被唤醒,作用与条件变量类似)。
  2. Server/Client分别通过accept/connect系统调用生成已连接套接字,以某个负载均衡算法从线程池中选出一个IO线程,并立即对其eventloop注册可写事件监听。
    注意这里的立即,也就是说连接建立时的回调函数是由IO线程去执行的,而不是由Acceptor/Connector线程去执行,这样可以保证它们自身负载是最低的,从而最大化响应速度。
  3. 对Server来说,可写事件会被立即触发;对于Client来说,可写事件会等到连接建立时(即三次握手的第二次完成)触发,需要使用getsockopt去判断连接是否建立成功。
  4. IO线程执行完回调函数后,会执行彻底的清理操作,然后重新对自身的eventloop注册回调函数,并enable可读事件监听,此处开始该连接的处理被IO线程完全接管。
接口设计

Server:

on_accept / on_read_complete / on_write_complete / on_closed

Client:

on_connet / on_read_complete / on_write_complete / on_closed
  1. 这里将连接建立分别作为 on_accept 和 on_connect,注意这两个回调函数在上述的Step2中被注册到IO线程并在Step3中被执行。
  2. 套接字缓冲区的异步读写由lib自行完成,将读写完成抽象为了两个函数on_read_complete和on_write_complete,最后加上了on_closed。由于在lib的IO模块设计中是考虑到了连接被对端关闭或对端崩溃的情况,on_closed这个函数也可以由用户自行通过非阻塞IO类的对象指针判断并处理,加上这个回调函数也是帮用户减轻一些负担。
案例/性能/稳定性

服务端:

  1. 指定线程池线程数量。
  2. 注册回调函数。
  3. 使用 IPv4 / IPv6 / Unix 协议族分别监听。
  4. 启动 IO 线程池。
/*
 * Simple Server
 *
 * The simple server just send a message to the client, then echo any message back to the client.
 */

#include "config.h"
#include "cppev/cppev.h"

/*
 * Define handler
 *
 * On the socket accepted : Put the message to the write buffer, then trying to send it.
 * The async_write here means if the message is very long then would register writable
 * event to do asynchrous write.
 *
 * On the socket read from sys-buffer completed : Retrieve the message from read buffer, then
 * put to the write buffer and trying to send it.
 *
 * On the socket write to sys-buffer completed : Log the info.
 *
 * On the socket closed : Log the info.
 */
cppev::reactor::tcp_event_handler on_accept = [](const std::shared_ptr<cppev::nsocktcp> &iopt) -> void
{
   
   
    iopt->wbuf()->put_string("Cppev is a C++ event driven library");
    cppev::reactor::async_write(iopt);
    cppev::log::info << "Connection " << iopt->fd() 
1.1 什么是Herm Herm是一套快速开发高性能网络应用的C++库。比如开发网络游戏、即时通信、流媒体、文件下载、P2P等基于TCP/IP网络应用。 Herm包括三个组件: (1)Utilities 最基础的组件,提供线程、一读一写线程不加锁的ring buffer、二进制消息解析器、支持多态的对象管理器等。 (2)Socket 用面向对象和泛型的方法抽象了TCP/UDP的Socket IOs;抽象了Win32 Select、Linux epoll和FreeBSD kqueue的多路复用API。统一了三者水平模式(Level Triggered)的语义(一套代码在Win32/Linux/FreeBSD运行结果是一样的),Linux上也支持了边缘模式(Edge Triggered)。 (3)Framework 基于Utilities和Socket的简化开发网络应用的框架,抽象出Peer和Session对象。Peer和Session对象以及Framework实现的功能将在第2章介绍。 Herm目前仅支持Linux/Windows/FreeBSD 32bits平台。调用者可以基于不同的需求使用不用的组件。 1.2 最简单的例子 本节给出两个分别用Framework和Socket组件实现的简单TCP Server的例子。所有的例子可以参考examples frameworks和multiplexors目录。 1.2.1 用Framework实现TCP Server 首先,实现一个Listener, class Listener : public Herm::Listener { virtual void Accept(Herm::Session* session) { // 在这里得到一个于客户端通信的Session // 注册用于处理收到的消息的handler // 用Session::Push将数据写到发送buffer,最终数据传给client } }; 实现一个App, class App : public Herm::App { virtual bool Init() { // 1. 创建Network Herm::Network* net = CreateNetwork(); // 2. 创建一个TCP Server Peer,将Listener注册到Peer,进行监听 Herm::Peer* peer = net->CreateTCPServer(addr, new Listener); } }; 1.2.2 用Socket实现TCP Server 用Socket实现TCP Server更灵活,但实现者要做一些额外的工作,比如tcp stream解析,缓冲队列处理等等。 首先实现一个AcceptHandler,处理Client连接, class AcceptHandler : public Herm::EventHandler { virtual int Receive(int) { m_acceptor->Accept(streamHandler->GetStream()); g_reactor->Register(streamHandler, Herm::READ_MASK); ... } private: Herm::Acceptor* m_acceptor; }; 实现StreamHandler, 处理数据收发, class StreamHandler : public Herm::EventHandler { // Handle onle int param on FreeBSD, pls see the example of FreeBSD_tcp_server virtual int Receive(int) { m_stream->Recieve(buf, .......); ..... } private: Herm::Stream* m_stream; }; 最后,在一个线程里把上面Handler执行起来, while (true) g_reactor->Run(); 1.3 Herm地址 https://sourceforge.net/projects/speed/ 1.4 下一章话题 下章将较详细地介绍Framework组件,主要介绍如何用Framework实现真实可用的网络游戏的接入网关Server(gated)和逻辑Server(zoned) 转自优快云的herm_lib http://blog.youkuaiyun.com/herm_lib
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值