• 基于ASIO的协程库orchid简介


    什么是orchid?

    orchid是一个构建于boost库基础上的C++库,类似于python下的gevent/eventlet,为用户提供基于协程的并发模型。

    什么是协程:

    协程,即协作式程序,其思想是,一系列互相依赖的协程间依次使用CPU,每次只有一个协程工作,而其他协程处于休眠状态。协程在控制离开时暂停执行,当控制再次进入时只能从离开的位置继续执行。 协程已经被证明是一种非常有用的程序组件,不仅被python、lua、ruby等脚本语言广泛采用,而且被新一代面向多核的编程语言如golang rust-lang等采用作为并发的基本单位。

    协程可以被认为是一种用户空间线程,与传统的抢占式线程相比,有2个主要的优点:

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

    green化

    术语“green化”来自于python下著名的协程库greenlet,指改造IO对象以能和协程配合。某种意义上,协程与线程的关系类似与线程与进程的关系,多个协程会在同一个线程的上下文之中运行。因此,当出现IO操作的时候,为了能够与协程相互配合,只阻塞当前协程而非整个线程,需要将io对象“green化”。目前orchid提供的green化的io对象包括:

    • tcp socket(还不支持udp)
    • descriptor(目前仅支持非文件类型文件描述符,如管道和标准输入/输出,文件类型的支持会在以后版本添加)
    • timer (定时器)
    • signal (信号)

    chan:协程间通信

    chan这个概念引用自golang的chan。每个协程是一个独立的执行单元,为了能够方便协程之间的通信/同步,orchid提供了chan这种机制。chan本质上是一个阻塞消息队列,后面我们将看到,chan不仅可以用于同一个调度器上的协程之间的通信,而且可以用于不同调度器上的协程之间的通信。

    多核

    建议使用的scheduler per cpu的的模型来支持多核的机器,即为每个CPU核心分配一个调度器,有多少核心就创建多少个调度器。不同调度器的协程之间也可以通过chan来通信。协程应该被创建在那个调度器里由用户自己决定。

    预备知识

    orchid的实现严重依赖于boost,依赖的主要子库包括:boost.context boost.asio boost.iostreams shared_ptr boost.bind 等等。如果用户对这些子库,尤其是boost.asio和boost.bind、shared_ptr具有一定的了解的话,会更加有利于了解和使用orchid。当然如果不了解也没有关系,本文会在后面的例子中对涉及的相关知识进行简单的介绍。

    编译与安装

    orchid 本身是个只包含头文件的模板库,拷贝到指定的目录即可,但是orchid依赖的boost库需要编译。而且在使用orchid的时需要链接 boost_context boost_iostreams boost_system boost_thread 等子库(参见unit_test里的CMakeLists.txt)。

    boost需要采用最新的svn里的版本,因为1.52及以下版本缺少boost.atomic等子库。

    MAC OS

    git clone https://github.com/ryppl/boost-svn
    cd boost-svn
    ./bootstrap.sh
    ./b2 toolset=clang cxxflags="-arch x86_64" linkflags="-arch x86_64" install
    cd ..
    git clone https://github.com/ioriiod0/orchid.git
    cd orchid
    cp -r orchid <安装路径>

    LINUX

    git clone https://github.com/ryppl/boost-svn
    cd boost-svn
    ./bootstrap.sh
    ./b2 install
    cd ..
    git clone https://github.com/ioriiod0/orchid.git
    cd orchid
    cp -r orchid <安装路径>

    例子

    第一个栗子:一个复杂一些的hello world

    国际惯例,让我们从hello world开始。在这个例子中,我们将看到如何创建和执行一个协程。

    //函数签名为 void(orchid::coroutine_handle) 的函数
    void f(orchid::coroutine_handle co) {
        std::cout<<"f:hello world"<<std::endl;
    }
    
    //函数签名为 void(orchid::coroutine_handle,const char*) 的函数
    void f1(orchid::coroutine_handle co,const char* str) {
        std::cout<<str<<std::endl;
    }
    
    void stop(orchid::coroutine_handle co) {
        co -> get_scheduler().stop();
    }
    
    int main(int argc,char* argv[]) {
        orchid::scheduler sche;
        sche.spawn(f,orchid::coroutine::minimum_stack_size());
        sche.spawn(boost::bind(f1,_1,"f1:hello world"),orchid::coroutine::default_stack_size());
        sche.spawn(stop);
        sche.run();
        std::cout<<"done!"<<std::endl;
    }

    程序的输出:

    f:hello world
    f1:hello world
    done!

    在这个例子中,我们首先声明一个调度器sche,然后调用sche的spawn方法以2种方式创建了2个协程来输出hello world,最后调用调度器的run方法来执行整个程序。当程序执行时,2个协程依次被执行。需要注意的是,在调用run方法之前,被创建的协助程序并不会被执行,只有调用了run方法之后,被创建的协程才会被调度执行。 调用run方法的线程会被阻塞,直到调度器的stop方法被调用为止。 (实际上,在这个例子中我们并没有对std::cout进行green化,因此每次调用std::cout的时候,整个调度器/线程都会被阻塞,在后面的介绍中我们将看到如何将IO对象green化)。

    spawn方法有2个参数:

    template <typename F>
    void spawn(const F& func,std::size_t stack_size = coroutine_type::default_stack_size())

    第一个参数func是协程被执行时所执行的函数。可以认为是协程的main函数。func可以是任何符合函数签名

    void(orchid::coroutine_handle)

    的函数或函数对象,如上面例子中的f。其中,orchid::coroutine_handle是协程的句柄,代表了func本身所处的协程。 往往要完成协程所执行的任务,仅仅有orchid::coroutine_handle是不够的,比如函数f1,f1需要额外传入一个const char* 的参数,对于这种函数,我们可以通过boost.bind、std::bind(tr1或者C++11)、或者lambda表达式(c++11)将他们适配成要求的函数类型。如上例中的:

    sche.spawn(boost::bind(f1,_1,"f1:hello world"),orchid::coroutine::default_stack_size())

    boost::bind将f1从void(orchid::coroutine,const char*)适配成了void(orchid::coroutine_handle)类型。其中_1是占位符,表示空出f1的第一个参数,而f1的第二个参数由"f1:hello world"填充。在后面的例子中将会大量出现这种用法,如果用户对boost.bind并不是很清楚,可以先阅读一下boost文档中的相关章节。

    第二个参数指定了协程栈空间的大小,orchid::coroutine提供了3个相关的函数:

    std::size_t default_stack_size();//默认的栈大小,一般为16个内存页
    std::size_t minimum_stack_size();//栈的最小值,一般为2个内存页面
    std::size_t maximum_stack_size();//栈的最大值,一般与进程的栈的最大值(软限制)相同。

    用户可以根据自己的情况指定所需栈空间的大小。当协程使用的栈空间超过了提供的栈空间的大小的时候,程序会异常结束, orchid并不会自动增加栈空间的大小,所以用户必须预估出足够的栈空间。

    这个hello world的例子过于简单,下面我们将通过创建一个echo server的客户端和服务器端来进一步了解orchid。

    第二个栗子:echo server

    第二个栗子,让我们从网络编程届的hello world:echo server开始。echo server首先必须要处理连接事件,在orchid中,我们创建一个协程来专门处理连接事件:

    typedef boost::shared_ptr<orchid::socket> socket_ptr;
    
    //处理ACCEPT事件的协程
    void handle_accept(orchid::coroutine_handle co) {
        try {
            orchid::acceptor acceptor(co -> get_scheduler().get_io_service());
            acceptor.bind_and_listen("5678",true);
            for(;;) {
                socket_ptr sock(new orchid::socket(co -> get_scheduler().get_io_service()));
                acceptor.accept(*sock,co);
                co -> get_scheduler().spawn(boost::bind(handle_io,_1,sock),orchid::minimum_stack_size());
            }
        }
        catch(boost::system::system_error& e) {
            cerr<<e.code()<<" "<<e.what()<<endl;
        }
    }

    在上面的代码中,我们创建了一个green化的acceptor,并让它监听5678端口,然后在"阻塞"等待连接到来,当连接事件发生时,创建一个新的协程来服务新得到的socket。green化的socket被包裹在智能指针中以参数形式传递给处理socket io事件的协程。处理套接字IO的协程如下:

    //处理SOCKET IO事件的协程
    void handle_io(orchid::coroutine_handle co,socket_ptr sock) {
        orchid::tcp_ostream out(*sock,co);
        orchid::tcp_istream in(*sock,co);
        for(std::string str;std::getline(in, str) && out;)
        {
            out<<str<<endl;
        }
    
    }

    协程首先在传入的套接字上创建了一个输入流和一个输出流,分别代表了TCP的输入和输出。然后不断地从输入流中读取一行,并输出到输出流当中。当socket上的TCP连接断开时,输入流和输出流的eof标志为会被置位,因此循环结束,协程退出。

    orchid可以使用户以流的形式来操作套接字。输入流和输出流分别提供了std::istream和std::ostream的接口;输入流和输出流是带缓冲的,如果用户需要无缓冲的读写socket或者自建缓冲,可以直接调用orchid::socket的read和write函数。但是需要注意这两个函数会抛出boost::system_error异常来表示错误(参见benchmark_orchid_client和benchmar_orchid_server)。

    最后是main函数:

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

    然后我们来看客户端的代码,在客户端中,我们创建100个并发的TCP连接不断的向echo server发送hello world。

    首先是处理socket io的协程:

    void handle_io(orchid::coroutine_handle co) {
        orchid::descriptor stdout(co -> get_scheduler().get_io_service(),STDOUT_FILENO);
        orchid::socket sock_(co -> get_scheduler().get_io_service());
        try {
            sock_.connect("127.0.0.1","5678",co);
            orchid::tcp_istream in(sock_,co);
            orchid::tcp_ostream out(sock_,co);
            orchid::descriptor_ostream console(stdout,co);
            out << "hello world !!!!" <<endl;
            for (string str;std::getline(in,str);) {
                console << str << endl;
                out << "hello world !!!!" <<endl;
            }
        } catch (const boost::system::system_error& e) {
            cerr<<e.code()<<" "<<e.what()<<endl;
        }
    }

    处理socket io的协程分别创建了一个green化的socket和一个green话的标准输出,然后连接到echo server上,不断执行 输出 -> 接收 -> 打印 这个流程。

    为了能够从外部打断client的执行,我们还需要一个协程来处理中断信号,这样我们就可以用ctrl+c来正确的中断程序的执行:

    void handle_sig(orchid::coroutine_handle co) {
        orchid::signal sig(co -> get_scheduler().get_io_service());
        try {
            sig.add(SIGINT);
            sig.add(SIGTERM);
            sig.wait(co);
            co->get_scheduler().stop();
    
        } catch (const boost::system::system_error& e) {
            cerr<<e.code()<<" "<<e.what()<<endl;
        }
    }

    在这个协程中,协程“阻塞”在SIGINT 和 SIGTERM信号上,当信号发生时,调用调度器的stop方法来中断程序的执行,并安全的回收资源。

    int main() {
        orchid::scheduler sche;
        sche.spawn(handle_sig,orchid::coroutine::minimum_stack_size());
        for (int i=0;i<100;++i) {
            sche.spawn(handle_io);
        }
        sche.run();
    }

    在客户端的main函数中,我们创建100个协程,同时向服务器发送hello world。

    在上面这个echo server的例子中,我们采用了一种 coroutine per connection 的编程模型,与传统的 thread per connection 模型一样的简洁清晰,但是整个程序实际上运行在同一线程当中。 由于协程的切换和内存开销远远小于线程,因此我们可以轻易的同时启动上千协程来同时服务上千连接,这是 thread per connection的模型很难做到的; 在性能方面,整个green化的IO系统实际上是使用boost.asio这种高性能的异步io库实现的,与原始的asio相比,orchid的性能损耗非常小,性能基本持平。 因此通过orchid,我们可以在保持同步IO模型简洁性的同时,获得近似于异步IO模型的高性能。

    第三个栗子:生产者-消费者

    在这个例子中,我们将主要介绍orchid提供的协程间的通信机制:chan。chan这个概念引用自golang的chan。chan表现为一个阻塞消息队列。 orchid提供的chan只支持 单生产者-单消费者 和 多生产者-单消费者 这两种模型(在其他模型,如多生产者-多消费者中,也可以工作,单可能会出现某些消费者饿死的现象)。

    在下面的例子中,代码orchid::chan < int > ch(10) 表示创建一个大小为10,装载类型为int的chan。 chan 有3个重要的接口:

    // 向chan中发送一个对象。t为要发送的对象,co为当前协程的协程句柄。
    // 当队列为空时,co会阻塞,当队列不在为空时,阻塞的协程会被唤醒;
    // 发送成功返回true,否则false。
    bool send(const U& t,coroutine_pointer co);
    
    // 从chan中接收一对象。
    // 当队列满时,co会阻塞,当队列不再满时,阻塞的协程会被唤醒。
    // 接收成功返回true,否则false。
    bool recv(U& t,coroutine_pointer co);
    
    // 关闭一个chan,当chan关闭后,再调用send和recv都会直接返回false;并且所有阻塞在chan中的协程都会被唤醒
    // 被唤醒的协程中 send/recv 返回false。
    void close();

    下面是一个简单的生产者和消费者的例子:

    //生产者,不断发送自己的ID给消费者
    void sender(orchid::coroutine_handle co,int id,orchid::chan<int>& ch) {
        for (;;) {
            ch.send(id,co);
        }
    }
    //消费者,不断接收生产者发送的ID并打印ID.
    void receiver(orchid::coroutine_handle co,orchid::chan<int>& ch) {
        orchid::descriptor stdout(co -> get_scheduler().get_io_service(),STDOUT_FILENO);
        orchid::descriptor_ostream console(stdout,co);
        int id;
        for (;;) {
            ch.recv(id,co);
            console<<"receiver receive: "<<id<<std::endl;
        }
    }
    
    //生产者和消费者运行在同一个调度器中。
    void test_one_scheduler() {
        orchid::scheduler sche;
        orchid::chan<int> ch(10);
        for (int i=0;i<100;++i) {
            sche.spawn(boost::bind(sender,_1,i,boost::ref(ch)));
        }
        sche.spawn(boost::bind(receiver,_1,boost::ref(ch)));
        sche.run();
    }
    
    //生产者和消费者运行再不同的调度器中。
    void test_scheduler_group() {
        orchid::scheduler_group group(2);
        orchid::chan<int> ch(10);
        for (int i=0;i<100;++i) {
            group[i%2].spawn(boost::bind(sender,_1,i,boost::ref(ch)));
        }
        group[0].spawn(boost::bind(receiver,_1,boost::ref(ch)));
        group.run();
    }

    通过scheduler_group类我们可以方便的创建一组调度器,每个调度器运行在一个单独的线程中。可以通过下标来访问某个调度器;通过调用其run方法同时启动多个调度器;通过调用其stop方法,同时停止多个调度器。

    第四个栗子:chat server

    这次我们来一个复杂一些的例子:chat server 和 chat client。从这个例子中我们将看到一些有用的技巧,比如如何使用boost::shared_from_this来管理协程间共享对象的生命周期;如何利用boost.variant在一个chan中接收多种类型消息。

    先从较为简单的chat clent开始:在chat client中我们将创建两个协程,一个不断从本机的标准输入读取输入,然后发送到chat server;另一个则不断从chat server接受消息并发送到本机的标准输出上。

    const static std::size_t STACK_SIZE = 64*1024;
    
    //客户端类
    class chat_client {
    public:
        chat_client(const string& ip,const string& port)
            :sche_(),
            stdin_(sche_.get_io_service(),STDIN_FILENO),//green化的标准输入
            stdout_(sche_.get_io_service(),STDOUT_FILENO),//green化的标准输出
            sock_(sche_.get_io_service()),
            is_logined_(false),ip_(ip),port_(port) {
    
        }
        ~chat_client() {
    
        }
    public:
        //创建协程并启动调度器
        void run() {
            sche_.spawn(boost::bind(&chat_client::handle_console,this,_1),STACK_SIZE);
            sche_.run();
        }
        //停止调度器
        void stop() {
            sche_.stop();
        }
    
    private:
        // 不断从chat server接收消息并打印到标准输出上。
        void receive_msg(orchid::coroutine_handle co) {
            string str;
            orchid::descriptor_ostream out(stdout_,co);
            orchid::tcp_istream in(sock_,co);
            for (string str;std::getline(in, str);) {
                out<<str<<endl;
            }
        }
    
        //不断从标准输入接收用户输入,并处理用户输入,发送消息chat server。
        //登陆用 /l username
        //发送消息用 /s xxxxxxx
        //退出用 /q
        void handle_console(orchid::coroutine_handle co) {
            orchid::descriptor_istream in(stdin_,co);
            orchid::tcp_ostream out(sock_,co);
            //首先连接chat server
            try {
                sock_.connect(ip_,port_,co);
            } catch (boost::system::system_error& e) {
                cerr<<e.code()<<" "<<e.what()<<endl;
                return;
            }
            //连接成功则启动接受消息的协程。
            sche_.spawn(boost::bind(&chat_client::receive_msg,this,_1), STACK_SIZE);
            //不断读取标准输入并进行处理。
            for(string str;std::getline(in,str);) {
                if(str.empty()) continue;
                // 退出 “/q”
                if(str.size() >= 2 && str[0] == '/' && str[1] == 'q') { 
                    sock_.close();
                    user_.clear();
                    is_logined_ = false;
                    cerr<<"closed"<<endl;
                    stop();
                }
                // 发送消息 /s message
                else if(str.size() >= 4 && str[0] == '/' && str[1] =='s') {
                    if(!is_logined_) {
                        cerr<<"login first"<<endl;
                    } else {
                        out<<user_<<" : "<<str.substr(3)<<endl;
                    }
                }
                // 登陆 “/l username”
                else if(str.size() >= 4 && str[0] == '/' && str[1] == 'l') {
                    if (!is_logined_) {
                        user_ = str.substr(3);
                        is_logined_ = true;
                    } else {
                        cerr<<"err: already logined!"<<endl;
                    }
                } else {
                    print_err();
                }
            }
    
        }
    
        void print_err() {
               cerr<<"err: bad cmd!"<<endl
                <<"usage:"<<endl
                <<"login: /l username"<<endl
                <<"exit: /q"<<endl
                <<"send: /s xxxxxxxxxxxx"<<endl;
        }
    
    private:
        orchid::scheduler sche_;
        orchid::descriptor stdin_;
        orchid::descriptor stdout_;
        orchid::socket sock_;
        string user_;
        bool is_logined_;
        string ip_;
        string port_;
    
    };
    
    int main(int argc,char* argv[]) {
        string ip = argv[1];
        string port = argv[2];
        chat_client client(ip,port);
        client.run();
    }

    在上面的代码中,我们利用了boost.bind的强大能力,使用类的成员函数创建了协程。

    然后是chat server:

    chat server中有2个类,server和client。类server实现了chat server的主要逻辑,类client则是客户端代理类,负责从客户端处接收和发送数据。

    server类的职责包括:维护客户端列表,广播某个客户端的发来的消息。 因此server处理的消息有2类,第一类是控制消息(下面代码中的ctrl_t类型),代表了客户端的到来和离开事件; 另外一类是文本消息,server类需要向所有的客户端转发、广播该类消息。不管是处理第一种类型的消息还是处理第二种类型的消息,都需要访问到其内部维护的客户端列表。为了同步这些访问,我们需要在同一个chan中接收这两种消息。

    const static std::size_t STACK_SIZE = 64*1024;
    
    template <typename Client>
    struct server {
        ///////////////typedef/////////////
        typedef server<Client> self_type;
        typedef boost::shared_ptr<Client> client_sp_type; 
        typedef std::list<client_sp_type> client_list_type;// 客户端列表类型,
                                                            // 保存的是智能指针。
        enum {REGISTER,UNREGISTER};
        struct ctrl_t {
            int cmd_;
            client_sp_type client_;
        };
        typedef boost::variant<string,ctrl_t> msg_type;//消息类型
    
        //////////////////变量///////////
        orchid::scheduler_group schedulers_;
        orchid::acceptor acceptor_;
        std::string port_;
        orchid::chan<msg_type> msg_ch_;//server的消息队列
        client_list_type clients_;//客户端列表
        ///////////////////////////
    
        server(std::size_t size,const string& port)
            :schedulers_(size),acceptor_(schedulers_[0].get_io_service()),port_(port),msg_ch_(512) {
        }
        ~server() {
        }
    
        //handle_msg是消息处理协程。不断的从消息队列中读取消息,然后判断消息类型,并处理。
        void handle_msg(orchid::coroutine_handle co) {
            msg_type msg;
            for (;;) {
                msg_ch_.recv(msg,co);
                if(msg.which() == 0) {// 如果是string类型,即文本消息,则向所有的客户端代理广播。
                    for(typename client_list_type::iterator it = clients_.begin(); it != clients_.end(); ++it) {
                        //向客户端代理的chan中发送消息。
                        (*it) -> ch_.send(boost::get<string>(msg),co);
                    }
                } else if(msg.which() == 1) {//如果是ctrl_t类型,即控制消息,则修改客户端列表。
                    if(boost::get<ctrl_t>(msg).cmd_ == REGISTER) {//注册消息
                        clients_.push_back(boost::get<ctrl_t>(msg).client_);
                    } else if(boost::get<ctrl_t>(msg).cmd_ == UNREGISTER) {//反注册消息
                        clients_.remove(boost::get<ctrl_t>(msg).client_);
                    } else {
                        throw std::runtime_error("unkonw cmd! should never hanppened!");
                    }
                } else {
                    throw std::runtime_error("unkonw msg! should never hanppened!");
                }
            }
        }
    
        //处理连接到来事件。当连接到来时,发送表示注册的控制消息到消息队列中。
        void handle_accept(orchid::coroutine_handle co) {
        try {
            int index = 1;
            acceptor_.bind_and_listen(port_);
            for (;;) {
                if(index >= schedulers_.size()) index = 0;
                boost::shared_ptr<Client> c(new Client(schedulers_[index++],*this));
                acceptor_.accept(c->sock_,co);
                c -> start();
                ctrl_t msg;
                msg.cmd_ = REGISTER;
                msg.client_ = c;
                msg_ch_.send(msg,co);
            }
        } catch (boost::system::system_error& e) {
            cout<<e.code()<<" "<<e.what()<<endl;
        }
    }
    
        void run() {
            schedulers_[0].spawn(boost::bind(&self_type::handle_accept,this,_1),STACK_SIZE);
            schedulers_[0].spawn(boost::bind(&self_type::handle_msg,this,_1),STACK_SIZE);
            schedulers_.run();
        }
    
    };

    客户端代理类的主要职责是:接收客户端socket上的数据,并发送到server类中;接收server发来的消息,并发送到客户端socket中;当客户端断开连接时,通知server。 不难看出,一个客户端代理类对象会同时被3个协程访问到:server的消息处理协程,client的sender和receiver协程。为了安全的释放客户端代理类的对象,我们使用引用计数的方案。引用计数通过boost::enable_shared_from_this来实现。

    //客户端代理类。
    struct client:public boost::enable_shared_from_this<client> {
        orchid::scheduler& sche_;
        server<client>& server_;
        orchid::socket sock_;
        orchid::chan<string> ch_;
    
        client(orchid::scheduler& sche,server<client>& s)
            :sche_(sche),server_(s),
            sock_(sche_.get_io_service()),ch_(32) {
    
        }
        ~client() {
    
        }
    
        // 启动发送和接收协程。注意这个地方的 this -> shared_from_this(),
        // 启动的协程中保存了当前对象的智能指针。
        // 当协程结束后,智能指针会释放,并在引用计数为0时,释放当前对象。
        void start() {
             sche_.spawn(boost::bind(&client::sender,this -> shared_from_this(),_1),STACK_SIZE);
             sche_.spawn(boost::bind(&client::receiver,this -> shared_from_this(),_1),STACK_SIZE);
        }
    
        //不断从client的消息队列中接收消息,并通过socket发送。
        //chan被关闭后退出循环,协程结束。
        void sender(orchid::coroutine_handle& co) {
            string str;
            orchid::tcp_ostream out(sock_,co);
            while(ch_.recv(str,co)) {
                out<<str<<endl;
            }
        }
    
        // 不断从客户端接收消息,直到客户端断开连接。
        // 当连接断开后,关闭自身的chan并发送反注册信息到server的消息队列中。
        void receiver(orchid::coroutine_handle& co) {
            orchid::tcp_istream in(sock_,co);
            //客户端断开连接后会退出循环。
            for (string str;std::getline(in,str);) {
                //向server的chan中发送消息。
                server_.msg_ch_.send(str,co);
            }
            ch_.close();
            server<client>::ctrl_t ctrl_msg;
            ctrl_msg.cmd_ = server<client>::UNREGISTER;
            ctrl_msg.client_ = this -> shared_from_this();
            server_.msg_ch_.send(ctrl_msg, co);
    
        }
    };
    
    int main(int argc,char* argv[]) {
        string port = argv[1];
        server<client> s(4,port);
        s.run();
    }

    需要注意的是,对同一个套接字进行读写的协程应该建立在同一个调度器上。因为实际上从green化的IO对象的构造函数即可以看出:green化的IO对象是与调度器中io_service对象绑定的。当不同调度器上的协程访问同一个IO对象的时,orchid不能保证其行为的正确性。

  • 相关阅读:
    计算机网络中的多路复用技术
    ActiveMQ之一--ActiveMQ入门
    ehcache介绍
    I/O模型之二:Linux IO模式及 select、poll、epoll详解
    【甘道夫】HBase(0.96以上版本号)过滤器Filter具体解释及实例代码
    Android Studio安装及主题字体配置
    HDU 2136 Largest prime factor 參考代码
    update更新两个字段
    Hadoop对小文件的解决方式
    赵雅智_ContentProvider
  • 原文地址:https://www.cnblogs.com/fuland/p/3736735.html
Copyright © 2020-2023  润新知