[Boost] ASIO學習筆記:network basic– TCP Server

posted in: ASIO, boost, C/C++程式設計, Server | 0

在上一篇[Boost] ASIO學習筆記:network basic – client有展示了該怎麼撰寫一個client去連接一個遠端的Server,而在本篇,將會接著介紹怎麼利用ASIO撰寫一個Server來處理與client端的連接。

首先在Server端開始運行之前,最重要的事便是決定要與哪個port綁定並監聽是否有資料傳送進來。只是,到底什麼是port呢?port是範圍定在0~65535之間的號碼,為Client跟Server之間連接的管道。打個比喻來說:如果ip是電話號碼的話,那麼port便是一個公司(Server)的分機號碼。port所定義的,是Client要打哪個分機號碼才能得公司(Server)所提供的服務。

其中:

0~1023為公認埠(Well Known Ports),通常用於某些特定的系統程序或者是常用的網路協定上。例如80為http、21為ftp、23為telnet…等等。

1024~49151為註冊埠(Registered Ports):理論上要使用該port來提供服務的公司要跟IANA是出申請,但實際上我們也常常拿來做動態或私人用途。

49152~65535為動態和/或私有埠(Dynamic and/or Private Ports):通常是拿來當作臨時或特定客製化的需求時使用。

其實,在撰寫程式的時候,我們可以去監聽任何的port,下圖為TCP Server操作的流程圖(下圖引自wiki,我們可以看到ASIO完全遵照Berkeley sockets API進行封裝):

File:InternetSocketBasicDiagram zhtw.png

以下為一個最簡單監聽port 12345的serve程式:

#include <boost/asio.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <string>
#include <boost/thread.hpp>

using namespace std;

void onAcceptEvent(boost::system::error_code ec, boost::shared_ptr<boost::asio::ip::tcp::socket> socket){

    if(ec){
        cout << boost::system::system_error(ec).what() << endl;
    }else{
        boost::asio::ip::tcp::endpoint remote_ep = socket->remote_endpoint();
        boost::asio::ip::address remote_ad = remote_ep.address();
        std::string s = remote_ad.to_string();
        cout << "on accept from ip->" << s << endl;

    }
    //cout << "on acceptEvent:" << socket.use_count() << endl;
    system("pause");
}



    

void main(){

    boost::system::error_code ec;
    boost::shared_ptr<boost::asio::io_service> io_service(new boost::asio::io_service);
    boost::shared_ptr<boost::asio::io_service::strand> strand(new boost::asio::io_service::strand(*io_service));
    boost::shared_ptr<boost::asio::ip::tcp::socket> socket(new boost::asio::ip::tcp::socket(*io_service));
    boost::asio::ip::tcp::resolver resolver(*io_service);
    boost::asio::ip::tcp::resolver::query query(boost::asio::ip::tcp::v6(), "12345");
    boost::shared_ptr<boost::asio::ip::tcp::acceptor> acceptor(new boost::asio::ip::tcp::acceptor(*io_service));



    try{

        boost::asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query, ec);
        boost::asio::ip::tcp::endpoint endpoint = *iterator;

        if(ec){cout << boost::system::system_error(ec).what() << endl;};

        acceptor->open( endpoint.protocol(), ec);
        cout << "ip address->" << endpoint.address() << endl;
        cout << "protocol type->"<< endpoint.protocol().type() << endl;
        cout << "protocol number->"<< endpoint.protocol().protocol() << endl;
        cout << "port->" <<  endpoint.port()  << endl;
        if(ec){cout << boost::system::system_error(ec).what() << endl;};


        acceptor->set_option( boost::asio::ip::tcp::acceptor::reuse_address( false ) );
        acceptor->bind( endpoint, ec);


        if(ec){cout << boost::system::system_error(ec).what() << endl;};
        acceptor->listen( boost::asio::socket_base::max_connections, ec );
        if(ec){cout << boost::system::system_error(ec).what() << endl;};

        acceptor->async_accept(*socket, boost::bind( onAcceptEvent, _1, socket ) );

        io_service->run(ec);

    }catch(std::exception e){
        cout << e.what() << endl;
    }

    cout << "in main:" << socket.use_count() << endl;
    system("pause");
    acceptor->close( ec );
    io_service->stop();
    std::system("pause");
}

若我們在執行列打上telnet localhost 12345:

則執行結果為on accept from ip-> ::1

其中::1所代表的便是ipv6版本中的localhost,在ipv4版本上為127.0.0.1。

在上面的程式碼中我們可以看到boost::asio::ip::tcp::acceptor,其職則有以下幾個:

1.open:以特定的protocol打開acceptor,這所代表的便是client端只能以所指定的形式跟我們的server連結。在上面的程式中(cout << “protocol number->”<< endpoint.protocol().protocol() << endl; )印出來是6,查看protocol number可以得知其使用的協定是tcp。

2.bind:跟endpoint進行綁定,意即該acceptor會監聽endpoint中所指定的ip跟port。

3.listen:讓acceptor進入可以開始監聽新連線的狀態,其中max_connections指的是最多可以接收幾個連線等待要求。

4.accept:可以用同步或不同步的方式來等待接收,在這裡是用不同步的方式,因此需要設置接收成功後的事件function。

5.close:待任務完成後,關閉接收器。

只是,很不幸的是上面的程式只能夠接收到一次連線而已。若要讓我們的server能夠持續不斷的接收新的連線的話,則需要將程式做以下改寫:

 

#include <boost/asio.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <string>
#include <boost/thread.hpp>
using namespace std;

void onAcceptEvent(boost::system::error_code ec, boost::shared_ptr<boost::asio::ip::tcp::socket> socket){

    if(ec){
        cout << boost::system::system_error(ec).what() << endl;
    }else{

        boost::asio::ip::tcp::endpoint remote_ep = socket->remote_endpoint();
        boost::asio::ip::address remote_ad = remote_ep.address();
        std::string s = remote_ad.to_string();
        cout << "on accept from ip->" << s << endl;
        socket->write_some(boost::asio::buffer("hello world!"), ec);//同步write
        cout << boost::system::system_error(ec).what() << endl;
    }

}


 
void main(){
    boost::system::error_code ec;
    boost::shared_ptr<boost::asio::io_service> io_service(new boost::asio::io_service);
    boost::shared_ptr<boost::asio::io_service::strand> strand(new boost::asio::io_service::strand(*io_service));
    //    boost::shared_ptr< boost::asio::io_service::work > work(new boost::asio::io_service::work( *io_service ));    
    boost::asio::ip::tcp::resolver resolver(*io_service);
    boost::asio::ip::tcp::resolver::query query(boost::asio::ip::tcp::v6(), "12345");
    boost::shared_ptr<boost::asio::ip::tcp::acceptor> acceptor(new boost::asio::ip::tcp::acceptor(*io_service));

    try{

        boost::asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query, ec);
        boost::asio::ip::tcp::endpoint endpoint = *iterator;
        if(ec){cout << boost::system::system_error(ec).what() << endl;};
        acceptor->open( endpoint.protocol(), ec);
        cout << "ip address->" << endpoint.address() << endl;//ip
        cout << "protocol type->"<< endpoint.protocol().type() << endl;//ip type:v4 or v6
        cout << "protocol number->"<< endpoint.protocol().protocol() << endl;//tcp or udp
        cout << "port->" <<  endpoint.port()  << endl;//port
        cout << "max_connections->" << boost::asio::socket_base::max_connections << endl;

        if(ec){cout << boost::system::system_error(ec).what() << endl;};


        acceptor->set_option( boost::asio::ip::tcp::acceptor::reuse_address( false ) );
        acceptor->bind( endpoint, ec);

        if(ec){cout << boost::system::system_error(ec).what() << endl;};
        acceptor->listen( boost::asio::socket_base::max_connections, ec );
        if(ec){cout << boost::system::system_error(ec).what() << endl;};


        boost::shared_ptr<boost::asio::ip::tcp::socket> socket(new boost::asio::ip::tcp::socket(*io_service));
        while(true){
            acceptor->async_accept(*socket, boost::bind( onAcceptEvent, _1, socket));
            io_service->run(ec);
            io_service->reset();
            socket->close();
            if(ec){cout << ec.message() << endl;};
        }
    }catch(std::exception e){
        cout << e.what() << endl;
    }
    acceptor->close( ec );
    io_service->stop();
    std::system("pause");
}

上面的程式所做的事情,便是在處理完上一次的連線要求後,清空上一次連線的內容準備接收下次的連線,其中比較重要的是要執io_service->reset()跟socket->close()這二行。為什麼要進行這二個動作呢?

io_service->reset()根據官網的解釋:This function must be called prior to any second or later set of invocations of the run(), run_one(), poll() or poll_one() functions。因此我們可以知道,其作用便是去將io_service各種暫存資料做清除,以不影響下次io_service的執行。如果不執行這行的話,我們會發現每次我們run()的時候都會去執行上次執行的結果。

socket->close()的功用,則是取消目前所進行的任何非同步的操作,如果不執行此行,則會造成上次的連線沒中斷進而無法接受下一次進來的連線。

另外,socket->write_some(boost::asio::buffer(“hello world!”), ec) 則是將hello world用同步的方式回傳給連接進來的client

dorgon

dorgon

職業:LV3遊戲軟體工程師 為了追尋小時候玩遊戲的感動,而一頭栽入遊戲業界。 本來以撰寫遊戲劇本為主要志向,但回過神來才發現已經踏入程序猿的不歸路。 專長為client端跨平台遊戲開發架構與自動建置流程,主要使用引擎為cocos2d-x與UnrealEngine4。

More Posts - Website

Follow Me:
FacebookLinkedIn

有什麼想法嗎?請發表你的看法