zoukankan      html  css  js  c++  java
  • 回显服务端/client

    回显服务端/client

    在这一章。我们将会实现一个小的client/服务端应用。这可能会是你写过的最简单的client/服务端应用。回显应用就是一个把client发过来的不论什么内容回显给其本身,然后关闭连接的的服务端。这个服务端能够处理不论什么数量的client。每一个client连接之后发送一个消息,服务端接收到完毕消息后把它发送回去。在那之后。服务端关闭连接。

    因此。每一个回显client连接到服务端,发送一个消息,然后读取服务端返回的结果。确保这是它发送给服务端的消息就结束和服务端的会话。

    我们首先实现一个同步应用。然后实现一个异步应用,以便你能够很easy对照他们:

    这里写图片描写叙述

    为了节省空间,以下的代码有一些被裁剪掉了。你能够在附加在这本书的代码中看到全部的代码。

    TCP回显服务端/client

    对于TCP而言。我们须要一个额外的保证;每一个消息以换行符结束(‘ ’)。编写一个同步回显服务端/client很简单。

    我们会展示编码内容,比方同步client,同步服务端。异步client和异步服务端。

    TCP同步client

    在大多数有价值的样例中。client通常比服务端编码要简单(由于服务端须要处理多个client请求)。

    以下的代码展示了不符合这条规则的一个例外:

    size_t read_complete(char * buf, const error_code & err, size_t bytes)
    {
        if ( err) return 0;
        bool found = std::find(buf, buf + bytes, '
    ') < buf + bytes;
        // 我们一个一个读取直到读到回车,不缓存
        return found ?

    0 : 1; } void sync_echo(std::string msg) { msg += " ”; ip::tcp::socket sock(service); sock.connect(ep); sock.write_some(buffer(msg)); char buf[1024]; int bytes = read(sock, buffer(buf), boost::bind(read_complete,buf,_1,_2)); std::string copy(buf, bytes - 1); msg = msg.substr(0, msg.size() - 1); std::cout << "server echoed our " << msg << ": "<< (copy == msg ? "OK" : "FAIL") << std::endl; sock.close(); } int main(int argc, char* argv[]) { char* messages[] = { "John says hi", "so does James", "Lucy just got home", "Boost.Asio is Fun!", 0 }; boost::thread_group threads; for ( char ** message = messages; *message; ++message) { threads.create_thread( boost::bind(sync_echo, *message)); boost::this_thread::sleep( boost::posix_time::millisec(100)); } threads.join_all(); }

    核心功能sync_echo

    它包括了连接到服务端。发送信息然后等待回显的全部逻辑。

    你会发现,在读取时,我使用了自由函数read(),由于我想要读’ ’之前的全部内容。sock.read_some()方法满足不了这个要求,由于它仅仅会读可用的,而不是全部的消息。

    read()方法的第三个參数是完毕处理句柄。当读取到完整消息时,它返回0。否则,它会返回我下一步(直到读取结束)能都到的最大的缓冲区大小。

    在我们的样例中。返回结果始终是1,由于我永远不想读的消息比我们须要的很多其它。

    main()中,我们创建了几个线程;每一个线程负责把消息发送到client,然后等待操作结束。

    假设你执行这个程序,你会看到以下的输出:

    server echoed our John says hi: OK
    server echoed our so does James: OK
    server echoed our Lucy just got home: OK
    server echoed our Boost.Asio is Fun!: OK

    注意:由于我们是同步的,所以不须要调用service.run()

    TCP同步服务端

    回显同步服务端的编写很easy。參考例如以下的代码片段:

    io_service service;
    size_t read_complete(char * buff, const error_code & err, size_t bytes) {
        if ( err) return 0;
        bool found = std::find(buff, buff + bytes, '
    ') < buff + bytes;
        // 我们一个一个读取直到读到回车,不缓存
        return found ? 0 : 1;
    }
    void handle_connections() {
        ip::tcp::acceptor acceptor(service, ip::tcp::endpoint(ip::tcp::v4(),8001));
        char buff[1024];
        while ( true) {
            ip::tcp::socket sock(service);
            acceptor.accept(sock);
            int bytes = read(sock, buffer(buff), boost::bind(read_complete,buff,_1,_2));
            std::string msg(buff, bytes);
            sock.write_some(buffer(msg));
            sock.close();
        }
    }
    int main(int argc, char* argv[]) {
        handle_connections();
    }

    服务端的逻辑主要在handle_connections()。由于是单线程。我们接受一个client请求。读取它发送给我们的消息,然后回显。然后等待下一个连接。能够确定。当两个client同一时候连接时,第二个client须要等待服务端处理完第一个client的请求。

    还是要注意由于我们是同步的,所以不须要调用service.run()

    TCP异步client

    当我们開始异步时。编码会变得略微有点复杂。

    我们会构建在第二章 保持活动中展示的connection类。

    观察这个章节中接下来的代码,你会发现每一个异步操作启动了新的异步操作。以保持service.run()一直工作。

    首先,核心功能例如以下:

    #define MEM_FN(x)       boost::bind(&self_type::x, shared_from_this())
    #define MEM_FN1(x,y)    boost::bind(&self_type::x, shared_from_this(),y)
    #define MEM_FN2(x,y,z)  boost::bind(&self_type::x, shared_from_this(),y,z)
    class talk_to_svr : public boost::enable_shared_from_this<talk_to_svr> , boost::noncopyable {
        typedef talk_to_svr self_type;
        talk_to_svr(const std::string & message) : sock_(service), started_(true), message_(message) {}
        void start(ip::tcp::endpoint ep) {
            sock_.async_connect(ep, MEM_FN1(on_connect,_1));
        }
    public:
        typedef boost::system::error_code error_code;
        typedef boost::shared_ptr<talk_to_svr> ptr;
        static ptr start(ip::tcp::endpoint ep, const std::string &message) {
            ptr new_(new talk_to_svr(message));
            new_->start(ep);
            return new_;
        }
        void stop() {
            if ( !started_) return;
            started_ = false;
            sock_.close();
        }
        bool started() { return started_; }
        ...
    private:
        ip::tcp::socket sock_;
        enum { max_msg = 1024 };
        char read_buffer_[max_msg];
        char write_buffer_[max_msg];
        bool started_;
        std::string message_; 
    }; 

    我们须要一直使用指向talk_to_svr的智能指针。这种话当在tack_to_svr的实例上有异步操作时,那个实例是一直活动的。

    为了避免错误。比方在栈上构建一个talk_to_svr对象的实例时,我把构造方法设置成了私有并且不同意拷贝构造(继承自boost::noncopyable)。

    我们有了核心方法,比方start(),stop()started(),它们所做的事情也正如它们名字表达的一样。假设须要建立连接,调用talk_to_svr::start(endpoint, message)就可以。我们同一时候另一个read缓冲区和一个write缓冲区。(read_buufer_write_buffer_)。

    MEM_FN 是一个方便使用的宏,它们通过*shared_ptr_from_this()方法强制使用一个指向* this 的智能指针。

    以下的几行代码和之前的解释很不同:

    //等同于 "sock_.async_connect(ep, MEM_FN1(on_connect,_1));"
    sock_.async_connect(ep,boost::bind(&talk_to_svr::on_connect,shared_ptr_from_this(),_1));
    sock_.async_connect(ep, boost::bind(&talk_to_svr::on_connect,this,_1));

    在上述样例中。我们正确的创建了async_connect的完毕处理句柄;在调用完毕处理句柄之前它会保留一个指向talk_to_server实例的智能指针,从而保证当其发生时talk_to_server实例还是保持活动的。

    在接下来的样例中,我们错误地创建了完毕处理句柄。当它被调用时。talk_to_server实例很可能已经被释放了。

    从socket读取或写入时,你使用例如以下的代码片段:

    void do_read() {
        async_read(sock_, buffer(read_buffer_), MEM_FN2(read_complete,_1,_2), MEM_FN2(on_read,_1,_2));
    }
    void do_write(const std::string & msg) {
        if ( !started() ) return;
        std::copy(msg.begin(), msg.end(), write_buffer_);
        sock_.async_write_some( buffer(write_buffer_, msg.size()), MEM_FN2(on_write,_1,_2));
    }
    size_t read_complete(const boost::system::error_code & err, size_t bytes) {
        // 和TCPclient中的相似
    }

    do_read()方法会保证当on_read()被调用的时候,我们从服务端读取一行。do_write()方法会先把信息复制到缓冲区(考虑到当async_write发生时msg可能已经超出范围被释放),然后保证实际的写入操作发生时on_write()被调用。

    然后是最重要的方法,这种方法包括了类的主要逻辑:

    void on_connect(const error_code & err) {
        if ( !err)      do_write(message_ + "
    ");
        else            stop();
    }
    void on_read(const error_code & err, size_t bytes) {
        if ( !err) {
            std::string copy(read_buffer_, bytes - 1);
            std::cout << "server echoed our " << message_ << ": " << (copy == message_ ? "OK" : "FAIL") << std::endl; 
        }
        stop(); 
    }
    void on_write(const error_code & err, size_t bytes) {
        do_read();
    } 

    当连接成功之后。我们发送消息到服务端,do_write()。当write操作结束时,on_write()被调用,它初始化了一个do_read()方法。当do_read()完毕时。on_read()被调用;这里,我们简单的检查一下返回的信息是否是服务端的回显。然后退出服务。

    我们会发送三个消息到服务端让它变得更有趣一点:

    int main(int argc, char* argv[]) {
        ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 8001);
        char* messages[] = { "John says hi", "so does James", "Lucy got home", 0 };
        for ( char ** message = messages; *message; ++message) {
            talk_to_svr::start( ep, *message);
            boost::this_thread::sleep( boost::posix_time::millisec(100));
        }
        service.run();
    }

    上述的代码会生成例如以下的输出:

    server echoed our John says hi: OK
    server echoed our so does James: OK
    server echoed our Lucy just got home: OK

    TCP异步服务端

    核心功能和同步服务端的功能相似,例如以下:

    class talk_to_client : public boost::enable_shared_from_this<talk_to_
       client>, boost::noncopyable {
        typedef talk_to_client self_type;
        talk_to_client() : sock_(service), started_(false) {}
    public:
        typedef boost::system::error_code error_code;
        typedef boost::shared_ptr<talk_to_client> ptr;
        void start() {
            started_ = true;
            do_read(); 
        }
    
        static ptr new_() {
            ptr new_(new talk_to_client);
            return new_;
        }
        void stop() {
            if ( !started_) return;
            started_ = false;
            sock_.close();
        }
        ip::tcp::socket & sock() { return sock_;}
        ...
    private:
        ip::tcp::socket sock_;
        enum { max_msg = 1024 };
        char read_buffer_[max_msg];
        char write_buffer_[max_msg];
        bool started_;
    };

    由于我们是很简单的回显服务,这里不须要is_started()方法。对每一个client。仅仅读取它的消息,回显,然后关闭它。

    do_read(),do_write()read_complete()方法和TCP同步服务端的全然一致。

    基本的逻辑相同是在on_read()on_write()方法中:

    void on_read(const error_code & err, size_t bytes) {
        if ( !err) {
            std::string msg(read_buffer_, bytes);
            do_write(msg + "
    ");
        }
        stop(); 
    }
    void on_write(const error_code & err, size_t bytes) {
        do_read();
    }

    对client的处理例如以下:

    ip::tcp::acceptor acceptor(service, ip::tcp::endpoint(ip::tcp::v4(),8001));
    void handle_accept(talk_to_client::ptr client, const error_code & err)
    {
        client->start();
        talk_to_client::ptr new_client = talk_to_client::new_();
        acceptor.async_accept(new_client->sock(), boost::bind(handle_accept,new_client,_1));
    }
    int main(int argc, char* argv[]) {
        talk_to_client::ptr client = talk_to_client::new_();
        acceptor.async_accept(client->sock(), boost::bind(handle_accept,client,_1));
        service.run();
    } 

    每一次client连接到服务时,handle_accept被调用,它会异步地从client读取。然后相同异步地等待一个新的client。

    代码

    你会在这本书对应的代码中得到全部4个应用(TCP回显同步client,TCP回显同步服务端,TCP回显异步client,TCP回显异步服务端)。

    当測试时。你能够使用随意client/服务端组合(比方,一个异步client和一个同步服务端)。

    UDP回显服务端/client

    由于UDP不能保证全部信息都抵达接收者。我们不能保证“信息以回车结尾”。


    没收到消息,我们仅仅是回显,可是没有socket去关闭(在服务端)。由于我们是UDP。

    UDP同步回显client

    UDP回显client比TCP回显client要简单:

    ip::udp::endpoint ep( ip::address::from_string("127.0.0.1"), 8001);
    void sync_echo(std::string msg) {
        ip::udp::socket sock(service, ip::udp::endpoint(ip::udp::v4(), 0));
        sock.send_to(buffer(msg), ep);
        char buff[1024];
        ip::udp::endpoint sender_ep;
        int bytes = sock.receive_from(buffer(buff), sender_ep);
        std::string copy(buff, bytes);
        std::cout << "server echoed our " << msg << ": " << (copy == msg ?

    "OK" : "FAIL") << std::endl; sock.close(); } int main(int argc, char* argv[]) { char* messages[] = { "John says hi", "so does James", "Lucy got home", 0 }; boost::thread_group threads; for ( char ** message = messages; *message; ++message) { threads.create_thread( boost::bind(sync_echo, *message)); boost::this_thread::sleep( boost::posix_time::millisec(100)); } threads.join_all(); }

    全部的逻辑都在synch_echo()中。连接到服务端。发送消息,接收服务端的回显,然后关闭连接。

    UDP同步回显服务端

    UDP回显服务端会是你写过的最简单的服务端:

    io_service service;
    void handle_connections() {
        char buff[1024];
        ip::udp::socket sock(service, ip::udp::endpoint(ip::udp::v4(), 8001));
        while ( true) {
            ip::udp::endpoint sender_ep;
            int bytes = sock.receive_from(buffer(buff), sender_ep);
            std::string msg(buff, bytes);
            sock.send_to(buffer(msg), sender_ep);
        } 
    }
    int main(int argc, char* argv[]) {
        handle_connections();
    } 

    它很简单。并且能很好的自释。

    我把异步UDPclient和服务端留给读者当作一个练习。

    总结

    我们已经写了完整的应用。终于让Boost.Asio得以工作。

    回显应用是開始学习一个库时很好的工具。你能够常常学习和执行这个章节所展示的代码,这样你就能够很easy地记住这个库的基础。


    在下一章。我们会建立更复杂的client/服务端应用,我们要确保避免低级错误,比方内存泄漏,死锁等等。

  • 相关阅读:
    20191330雷清逸 学习笔记4
    sort
    20191330雷清逸 MyOD(选作,计入平时成绩)
    20191330雷清逸 Linux C语言编程基础(必做)
    20191330雷清逸 学习笔记3
    20191330雷清逸 学习笔记2
    无限的技能
    20191330 雷清逸 学习笔记1
    2021-2022-1-diocs-Linux系统编程第四周学习笔记
    Linux下man命令的使用
  • 原文地址:https://www.cnblogs.com/wgwyanfs/p/7403661.html
Copyright © 2011-2022 走看看