回顯服務端/client

回顯服務端/client

在這一章。咱們將會實現一個小的client/服務端應用。這可能會是你寫過的最簡單的client/服務端應用。回顯應用就是一個把client發過來的不論什麼內容回顯給其自己,而後關閉鏈接的的服務端。這個服務端可以處理不論什麼數量的client。每個client鏈接以後發送一個消息,服務端接收到完畢消息後把它發送回去。在那以後。服務端關閉鏈接。css

所以。每個回顯client鏈接到服務端,發送一個消息,而後讀取服務端返回的結果。確保這是它發送給服務端的消息就結束和服務端的會話。緩存

咱們首先實現一個同步應用。而後實現一個異步應用,以便你可以很easy對照他們:ruby

這裏寫圖片描寫敘述

爲了節省空間,如下的代碼有一些被裁剪掉了。你可以在附加在這本書的代碼中看到所有的代碼。markdown

TCP回顯服務端/client

對於TCP而言。咱們需要一個額外的保證;每個消息以換行符結束(‘\n’)。編寫一個同步回顯服務端/client很簡單。異步

咱們會展現編碼內容,比方同步client,同步服務端。異步client和異步服務端。socket

TCP同步client

在大多數有價值的樣例中。client一般比服務端編碼要簡單(因爲服務端需要處理多個client請求)。async

如下的代碼展現了不符合這條規則的一個例外:tcp

函數

size_t read_complete(char * buf, const error_code & err, size_t bytes) { if ( err) return 0; bool found = std::find(buf, buf + bytes, '\n') < buf + bytes; // 咱們一個一個讀取直到讀到回車,不緩存 return found ?

0 : 1; } void sync_echo(std::string msg) { msg += "\n」; 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(),因爲我想要讀’\n’以前的所有內容。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, '\n') < 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_ + "\n");
    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 + "\n");
    }
    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/服務端應用,咱們要確保避免低級錯誤,比方內存泄漏,死鎖等等。

相關文章
相關標籤/搜索