文档章节

C++ 协程与网络编程

江浸月
 江浸月
发布于 2012/12/31 17:32
字数 2163
阅读 11413
收藏 194
点赞 8
评论 15

###协程 协程,即协作式程序,其思想是,一系列互相依赖的协程间依次使用CPU,每次只有一个协程工作,而其他协程处于休眠状态。协程可以在运行期间的某个点上暂停执行,并在恢复运行时从暂停的点上继续执行。 协程已经被证明是一种非常有用的程序组件,不仅被python、lua、ruby等脚本语言广泛采用,而且被新一代面向多核的编程语言如golang rust-lang等采用作为并发的基本单位。 协程可以被认为是一种用户空间线程,与传统的抢占式线程相比,有2个主要的优点:

  • 与线程不同,协程是自己主动让出CPU,并交付他期望的下一个协程运行,而不是在任何时候都有可能被系统调度打断。因此协程的使用更加清晰易懂,并且多数情况下不需要锁机制。
  • 与线程相比,协程的切换由程序控制,发生在用户空间而非内核空间,因此切换的代价非常的小。
  • 某种意义上,协程与线程的关系类似与线程与进程的关系,多个协程会在同一个线程的上下文之中运行。

###网络编程模型 我们首先来简单回顾一下一些常用的网络编程模型。网络编程模型可以大体的分为同步模型和异步模型两类。

  • 同步模型:

同步模型使用阻塞IO模式,在阻塞IO模式下调用read等IO函数时会阻塞线程直到IO完成或失败。 同步模型的典型代表是thread_per_connection模型,每当阻塞在主线程上的accept调用返回时则创建一个新的线程去服务于新的socket的读/写。这种模型的优点是程序逻辑简洁,符合人的思维;缺点是可伸缩性收到线程数的限制,当连接越来越多时,线程也越来越多,频繁的线程切换会严重拖累性能,同时不得不处理多线程同步的问题。

  • 异步模型:

异步模型一般使用非阻塞IO模式,并配合epoll/select/poll等多路复用机制。在非阻塞模式下调用read,如果没有数据可读则立即返回,并通知用户没有可读(EAGAIN/EWOULDBLOCK),而非阻塞当前线程。异步模型可以使一个线程同时服务于多个IO对象。 异步模型的典型代表是reactor模型。在reactor模型中,我们将所有要处理的IO事件注册到一个中心的IO多路复用器中(一般为epoll/select/poll),同时主线程阻塞在多路复用器上。一旦有IO事件到来或者就绪,多路复用器返回并将对应的IO事件分发到对应的处理器(即回调函数)中,最后处理器调用read/write函数来进行IO操作。

异步模型的特点是性能和可伸缩性比同步模型要好很多,但是其结构复杂,不易于编写和维护。在异步模型中,IO之前的代码(IO任务的提交者)和IO之后的处理代码(回调函数)是割裂开来的。

###协程与网络编程 协程的出现出现为克服同步模型和异步模型的缺点,并结合他们的优点提供了可能: 现在假设我们有3个协程A,B,C分别要进行数次IO操作。这3个协程运行在同一个调度器或者说线程的上下文中,并依次使用CPU。调度器在其内部维护了一个多路复用器(epoll/select/poll)。 协程A首先运行,当它执行到一个IO操作,但该IO操作并没有立即就绪时,A将该IO事件注册到调度器中,并主动放弃CPU。这时调度器将B切换到CPU上开始执行,同样,当它碰到一个IO操作的时候将IO事件注册到调度器中,并主动放弃CPU。调度器将C切换到cpu上开始执行。当所有协程都被“阻塞”后,调度器检查注册的IO事件是否发生或就绪。假设此时协程B注册的IO时间已经就绪,调度器将恢复B的执行,B将从上次放弃CPU的地方接着向下运行。A和C同理。 这样,对于每一个协程来说,它是同步的模型;但是对于整个应用程序来说,它是异步的模型。

好了,原理说完了,我们来看一个实际的例子,echo server。

###echo server

在这个例子中,我们将使用orchid库来编写一个echo server。orchid库是一个构建于boost基础上的 协程/网络IO C++库。

echo server首先必须要处理连接事件,我们创建一个协程来专门处理连接事件:

typedef boost::shared_ptr<orchid::socket> socket_ptr;
//处理ACCEPT事件的协程
void handle_accept(orchid::coroutine_handle co) {
    try {
        orchid::acceptor acceptor(co -> get_io_service());//构建一个acceptor
        acceptor.bind_and_listen("5678",true);
        for(;;) {
            socket_ptr sock(new orchid::socket(co -> get_scheduler().get_io_service()));
            acceptor.accept(*sock,co);

            //在调度器上创建一个协程来服务新的socket。
            //第一个参数是要创建的协程的main函数,
            //第二个参数是要创建的协程的栈的大小。
            co -> get_scheduler().spawn(boost::bind(handle_io,_1,sock),orchid::minimum_stack_size());
        }
    }
    catch(orchid::io_error& e) {
        ORCHID_ERROR("id %lu msg:%s",co->id(),e.what());
    }
}

在orchid中,协程的main函数必须满足函数签名void(orchid::coroutine_handle),如handle_accept所示,其中参数co是协程句柄,代表了当前函数所位于的协程。

在上面的代码中,我们创建了一个acceptor,并让它监听5678端口,然后在"阻塞"等待连接到来,当连接事件到来时,创建一个新的协程来服务新的socket。处理套接字IO的协程如下:

//处理SOCKET IO事件的协程
void handle_io(orchid::coroutine_handle co,socket_ptr sock) {
orchid::buffered_reader<orchid::socket> reader(*sock,co,16);//在socket上构建缓冲输入流
orchid::buffered_writer<orchid::socket> writer(*sock,co,16);//在socket上构建缓冲输出流

try {
    std::string line;
    std::size_t n = 0;

    for(;;) {
        n = reader.read_until(line,'\n');
        ORCHID_DEBUG("id %lu recv: %s",co->id(),line.c_str());
        writer.write(line.c_str(),line.size());
        writer.flush();
    }

} catch (const orchid::io_error& e) {
    if (e.code() == boost::asio::error::eof) {
        ORCHID_DEBUG("id %lu msg:%s",co->id(),"socket closed by remote side!");
    } else {
        ORCHID_ERROR("id %lu msg:%s",co->id(),e.what());
    }
}

IO处理协程首先在传入的套接字上创建了一个输入流和一个输出流,分别代表了TCP的输入和输出。然后不断地从输入流中读取一行,并输出到输出流当中。当socket上的TCP连接断开时,会抛出orchid::io_error的异常,循环结束,值得注意的是eof事件也被当成异常来抛出。对于不喜欢使用异常的用户,orchid提供了另外一套使用boost::system::error_code的接口。同时,对于熟悉asio的用户,orchid提供了一套boost asio风格的接口。

如果用户需要无缓冲的读写socket或者自建缓冲,可以直接调用orchid::socket的read和write函数,或者使用无缓冲的reader和writer。

细心的读者可能已经发现,handle_io的函数签名并不满足void(orchid::coroutine_handle),回到handle_accept中,可以发现,实际上我们使用了boost.bind对handle_io函数进行了适配,使之符合函数签名的要求。

最后是main函数:

int main() {
    orchid::scheduler sche;
    sche.spawn(handle_accept,orchid::coroutine::minimum_stack_size());//创建协程
    sche.run();
}

###总结 在上面这个echo server的例子中,我们采用了一种 coroutine per connection 的编程模型,与传统的 thread per connection 模型一样的简洁清晰,但是整个程序实际上运行在同一线程当中,所以我们也不需要处理多线程同步的问题。 由于协程的切换开销远远小于线程,因此我们可以轻易的同时启动上千协程来同时服务上千连接,这是 thread per connection的模型很难做到的;在性能方面,整个底层的IO系统实际上是使用boost.asio这种高性能的异步io库实现的。与IO所费的时间相比,协程切换的开销基本可以忽略。 因此通过orchid,我们可以在保持同步IO模型简洁性的同时,获得异步IO模型的高性能和扩展性。

最后,如果您对orchid感兴趣,欢迎关注和参与。

© 著作权归作者所有

共有 人打赏支持
江浸月

江浸月

粉丝 39
博文 9
码字总数 18652
作品 2
深圳
程序员
加载中

评论(15)

z
zcy421593
楼主这个封装有点重量级了
我也准备实现一个基于协程的网络库,是C风格的接口,现在还在早期开发阶段,有空可以交流下
https://github.com/zcy421593/co_socket
zmen
zmen
mark
a
amengren
mark
c
cc_osc
让我想到了 window 的 消息处理
蓝仇
yes
stonexin
stonexin
mark
付超_pku
付超_pku
mark~
LastRitter
LastRitter
mark
帝调
mark
starstroll
starstroll
mark
基于协程的网络编程库 - QtNetworkNg

QtNetworkNg 是一个基于协程的网络编程库。目标为 C++ 开发者提供简洁而不失强大的网络编程 API,成为 C++ 界最好的网络编程库。目前已经具备完善的协程管理功能、基本的 socket 编程和完善的...

hgoldfish
05/19
0
0
CN Erlounge IV tweets

前言 本文整理了Erlounge IV Erlang杭州开发者大会现场的记录的Tweets列表,建议先到 http://ecug.org/agenda/ 下载演讲文档,以便理解上下文。 Twitter的优势是小之美,整理成一篇大文章比较...

TimYangNet
06/29
0
0
acl_3.4.0 发布,跨平台网络通信与服务器框架

历时一年,acl 跨平台网络通信及服务器框架库发布大版本升级。主要有以下改进: 1、重构网络协程库,支持更多的操作系统平台(Linux/FreeBSD/MacOS/Windows),且支持更多网络事件引擎:epo...

郑树新
05/01
0
0
【C#每日一帖】【转】C#与C++的区别

没有什么语言能比C++更加贴近Windows本身了,这一点也是不可否认的。如果哪一天C#也能写驱动的时候,那么C++就真的会淘汰了(这天可能不会太远又或者很遥远)。 C#能做的,C++不一定都能做,C...

c_o_d_e_r
2011/07/22
0
0
CSDN回帖得分大全(近两年)

√ vs2005调用dll的时候Initialize()函数返回错误 [VC/MFC 基础类] √ 为什么我创建登陆框之后,然后获取登陆框的数据时候总是出现非法操作! [VC/MFC 界面] √ CFileFind::FindFile 支持通配...

junwong
2012/03/09
0
0
C++ 协程的近况、设计与实现中的细节和决策

作者:Li_Mr 原文:开源中国博客 时至2018年的今天,C++ 在互联网服务端开发方向依然占据着相当大的份额;百度,腾讯,甚至以java为主流开发语言的阿里都在大规模使用C++做互联网服务端开发,...

开源中国
05/31
0
0
让控制台应用程序支持MFC类库

1、 问题阐述:在基于控制台的应用程序中并不支持MFC库,如果使基于控制台的应用程序能够使用MFC类库呢? 2、 实现技巧:在控制台应用程序中通过include来引入MFC库,因为控制台应用程序默认...

Amamatthew
2014/06/16
0
0
《C++ primer》读后感:时代的经典

说起Lippman的C++ Primer,我总是有种特殊感情。这本书既是我进入C++领域的敲门砖,也是我第一次在网络上发表技术文章的对象。当年读书笔记中的青涩迷惘和年少轻狂都还历历在目,转眼已经从第...

凌杰_owlman
05/15
0
0
BOOST.ASIO源码剖析(一)

前言 源码之前,了无秘密。 ——侯捷 Boost库是一个可移植、提供源代码的C++库,作为标准库的后备,是C++标准化进程的开发引擎之一。Boost库由C++标准委员会库工作组成员发起,其中有些内容有...

moki_oschina
04/11
0
0
Go语言中协程的概念和基本使用

Go协程(Goroutine)是与其他函数同时运行的函数。可以认为Go协程是轻量级的线程。与创建线程相比,创建Go协程的成本很小。因此在Go中同时运行上千个协程是很常见的。 1、 Go语言的并发性 Go...

Oo若离oO
05/22
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

expect(spawn) 自动化git提交和scp拷贝---centos(linux)

**在进行SCP文件拷贝中,往往需要进行用户密码的输入,即用户交互。若采用自动化脚本的方式进行,则可用以下方式: ** #!/usr/bin/expect #设置参数 set src [lindex $argv 0] set dest [lin...

helplove
9分钟前
1
0
用Build来构建对象的写法

如果一个类的属性过多,用构造器来构建对象很难写,因此我们时用Build方式来构建对象。写法大致如下。 import java.io.Serializable;import java.util.Date;public class Log impleme...

算法之名
11分钟前
11
0
利用 acme.sh 获取网站证书并配置https访问

acme.sh 实现了 acme 协议, 可以从 letsencrypt 生成免费的证书.(https://github.com/Neilpang/acme.sh/wiki/%E8%AF%B4%E6%98%8E) 主要步骤: 安装 acme.sh 生成证书 copy 证书到 nginx/ap...

haoyuehong
24分钟前
2
0
微擎框架内如何根据media_id获取到微信图片的路径

微擎的框架内,图片选择后,获取的是那个字符串是media_id,相当于你这张图片在微信的图片服务器里面的id 要求是:获取https://mmbiz.qpic.cn/mmbiz_jpg/…… 微信图片的路径 而微信并没有根据m...

老bia同学
28分钟前
1
0
Spring boot中日期的json格式化

Model 在model层中,类的日期属性上面添加如下注解: @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm:ss") 参考 Jackson Date格式化教程...

亚林瓜子
30分钟前
2
0
Eclipse:Failed to load the JNI shared library

1.问题背景: 由于我之前使用jdk1.9学习,当使用Luke的时候发现jdk版本过高,需要向下配置jdk,就向朋友拷了一个安装包。重新配置路径后,便开始报错。 2.问题描述: Failed to load the JNI...

tinder_boy
32分钟前
1
0
少儿学习编程课程是否真的适合七八岁的低龄儿童[图]

少儿学习编程课程是否真的适合七八岁的低龄儿童[图]: 天下熙熙皆为利来,天下攘攘皆为利往。 这几年来,乐高教育机构在国内如同雨后春笋般出现,当然关闭/转手的也很多。从教师角度来看,部...

原创小博客
38分钟前
1
0
ES12-词项查询

1.词项查询介绍 全文查询将在执行之前分析查询字符串,但词项级别查询将按照存储在倒排索引中的词项进行精确操作。这些查询通常用于数字,日期和枚举等结构化数据,而不是全文本字段。 或者,...

贾峰uk
46分钟前
2
0
http状态码与ajax的状态值

ajax状态值 1.1 200 & OK:状态请求成功

litCabbage
49分钟前
2
0
iOS动画效果合集、飞吧企鹅游戏、换肤方案、画板、文字效果等源码

iOS精选源码 动画知识运用及常见动画效果收集 3D卡片拖拽卡片叠加卡片 iFIERO - FLYING PENGUIN 飞吧企鹅SpriteKit游戏(源码) Swift封装的空数据提醒界面EmptyView 沙盒文件浏览与分享调试控...

sunnyaigd
52分钟前
3
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部