基于协程和事件循环的c++网络库

103 阅读2分钟

完整资料进入【数字空间】查看——baidu搜索"writebug"

介绍

开发服务端程序的一个基本任务是处理并发连接,现在服务端网络编程处理并发连接主要有两种方式:

  1. 当“线程”很廉价时,一台机器上可以创建远高于CPU数目的“线程”。这时一个线程只处理一个TCP连接,通常使用阻塞IO。例如Go goroutine。这里的“线程”由语言的runtime自行调度。

  2. 当线程很宝贵时,一台机器上只能创建与CPU数目相当的线程。这时一个线程要处理多个TCP连接上的IO,通常使用非阻塞IO和IO multiplexing。C++编程主要采用这种方式。

在线程很宝贵的情况下,常见的服务器编程模型有如下几种:

  1. 每个请求创建一个线程,使用阻塞式IO操作(或者叫thread per connection)。这种模型的优点是可以使用阻塞操作,缺点是伸缩性不强,每台机器能创建的线程是有限的,32位的机器应该不超过400个。

  2. 非阻塞IO+IO多路复用(或者叫one loop per thread或者Reactor)+ 线程池。

melon是基于Reactor模式的Linux C++网络服务框架,集合了上述两种方式,实现了协程的概念,对一些函数进行了hook,所以可以像操作阻塞IO一样进行编程。

使用

在工程主目录下新建build目录,进入build目录,


cmake ..

make  all

编译完成后,example和test中的可执行程序分别位于build目录下的example和test中。

以echo服务端为例,


void handleClient(TcpConnection::Ptr conn){

conn->setTcpNoDelay(true);

Buffer::Ptr buffer = std::make_shared<Buffer>();

while (conn->read(buffer) > 0) {

conn->write(buffer);

}

  


conn->close();

}

  


  


int main(int args, char* argv[]) {

if (args != 2) {

printf("Usage: %s threads\n", argv[0]);

return 0;

}

Logger::setLogLevel(LogLevel::INFO);

Singleton<Logger>::getInstance()->addAppender("console", LogAppender::ptr(new ConsoleAppender()));

  


IpAddress listen_addr(5000);

int threads_num = std::atoi(argv[1]);

  


Scheduler scheduler(threads_num);

scheduler.startAsync();

TcpServer server(listen_addr, &scheduler);

server.setConnectionHandler(handleClient);

server.start();

  


scheduler.wait();

return 0;

}

只需要为TcpServer设置连接处理函数,在连接处理函数中,参数TcpConnection::Ptr conn代表此次连接,可以像阻塞IO一样进行读写,如果发生阻塞,当前协程会被切出去,直到可读或者可写事件到来时,该协程会被重新执行。

QQ截图20230718091429.png

QQ截图20230718094713.png

z1.png

z4.jpg

z5.jpg

z6.jpg