asio學習筆記5

出處:http://www.godebug.org/index.php/archives/69/

該寫點一部網絡通信的東西了..前面也說過,asio的異步網絡函數和同步的差別不大,只是異步和同步的思想的差別。如果前面的同步的函數都搞懂了,那只需要轉換一下思維方式就可以了——把要做的事提交給io_service,在run中執行他們,在事件完成的通知中進行下一步的操作。繼續上代碼:
// asio_sample.cpp : 定義控制台應用程序的入口點。
//

#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <iostream>

boost::asio::io_service ios;
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), 1234);
boost::asio::ip::tcp::acceptor acceptor(ios, endpoint);

char data[1024];

void handle_read(boost::asio::ip::tcp::socket* sock,
    const boost::system::error_code& error,
    size_t bytes_transferred);

void handle_write(boost::asio::ip::tcp::socket* sock,
    const boost::system::error_code& error,
    size_t bytes_transferred)
{
    if (error)
    {
        std::cout << "write發生錯誤:" << error.message() << std::endl;
        delete sock;
        return;
    }

    sock->async_read_some(boost::asio::buffer(data),
        boost::bind(handle_read, sock,
        boost::asio::placeholders::error,
        boost::asio::placeholders::bytes_transferred));
}

void handle_read(boost::asio::ip::tcp::socket* sock,
    const boost::system::error_code& error,
    size_t bytes_transferred)
{
    if (error)
    {
        std::cout << "read發生錯誤:" << error.message() << std::endl;
        delete sock;
        return;
    }

    boost::asio::async_write(*sock, boost::asio::buffer(data, bytes_transferred),
        boost::bind(handle_write, sock,
        boost::asio::placeholders::error,
        boost::asio::placeholders::bytes_transferred));
}

void session(boost::asio::ip::tcp::socket* sock,
    const boost::system::error_code& error)
{
    if (error)
    {
        delete sock;
    }
    else
    {
        std::cout << "有客戶端連接" << std::endl;

        sock->async_read_some(boost::asio::buffer(data),
            boost::bind(handle_read, sock,
            boost::asio::placeholders::error,
            boost::asio::placeholders::bytes_transferred));
    }

    boost::asio::ip::tcp::socket* s = new boost::asio::ip::tcp::socket(ios);
    acceptor.async_accept(*s, boost::bind(session, s,
        boost::asio::placeholders::error));
}

int main(int argc, char* argv[])
{
    boost::asio::ip::tcp::socket* s = new boost::asio::ip::tcp::socket(ios);
    acceptor.async_accept(*s, boost::bind(session, s, boost::asio::placeholders::error));

    ios.run();
    return 0;
}
跟同步的那個echo server是一樣的東西功能,就不截圖了。而且為了容易看清條理實現的比較醜陋。可以看出來這裡並沒有使用線程,卻實現了跟同步多線程的echo server相同的功能——可以同時跟多個客戶端通信。下面就詳細解釋一下代碼。
為了使用方便我把boost::asio::io_service、boost::asio::ip::tcp::endpoint和boost::asio::ip::tcp::acceptor定義成了全局變量,這三個類的使用和同步程序中的一模一樣就不多說了。下面依然是每個boost::asio::ip::tcp::socket代表收到的一個請求,不同的是這次沒有開新的線程,而是直接在async_accept的handler(也就是session函數)中進行的處理,在session函數中,我們先發了一個異步讀取數據的“任務”(sock->async_read_some)給io_service,然後又發了一個異步接收請求的“任務”(acceptor.async_accept)給io_service,因為只是發任務,函數式立即返回的,因此我們可以認為這兩個任務在同時進行。發讀取數據的任務是為了接受客戶端發來的數據,發accept任務是為了等待其他的客戶端的連接。在同步的程序中,不開多個線程是不可能做到的,因為我們如果先執行read任務的話,而客戶端一直不發送數據,我們會一直阻塞在read函數裡面;accept也是同樣的道理。
發了這兩個任務之後,我們就進入了兩個“循環”中,不明白這兩個循環怎麼來的請仔細看這裡,一個循環一直等待客戶的連接,另一個循環則重複執行著讀取數據->返回給客戶端->繼續讀取->返回給客戶端….直到客戶端下線或者其他錯誤發生時,就不再發異步任務,循環也就停止了。這樣就實現了一個線程,多個循環。
應該很容易就能看出其實異步的網絡函數和同步的差別不大,不過是多了個完成後的回調,結果都在回調中得知。你的回調需要哪方面的結果,就bind上boost::asio::placeholders命名空間下的對應的項。其中比較常用的是boost::asio::placeholders::error和boost::asio::placeholders::bytes_transferred,分別表示異步事件執行是否出錯以及傳送的數據的字節數。其他的項我也沒用過,以後用到再說。
另外說一下異步的幾個“坑”,如果被坑到會是很蛋疼的事。如下面的示例:
//異步讀取完成的handler
void handle_read(boost::asio::ip::tcp::socket* sock,size_t size,
    const boost::system::error_code& error,
    size_t bytes_transferred);

......
//某個函數中
    size_t size = 0;
    boost::asio::async_read(*sock,boost::asio::buffer(&size,sizeof(size)),
        boost::bind(handle_read,sock,size,
        boost::asio::placeholders::error,
        boost::asio::placeholders::bytes_transferred));
.......
這段代碼有兩個嚴重的錯誤:
1.要確定異步讀取的目標緩衝區的存活時間不能比事件的完成時間短,不管是異步讀取還是異步寫入。因為事件完成幾乎總是在發起異步事件的函數(調用 async_read或async_write的函數)完成之後才會觸發,這時候上面代碼中的size已經析構了,異步讀取或寫入會寫入到一個不存在的 buffer中,局部變量是放在棧上,這時這個變量的位置可能已經變成了另一個變量了,這肯定不是我們想要的結果。
2.不要把異步讀取寫入的目標bind到讀取完成的handler上。因為bind實際上是吧size的值(0)保存了下來,所以,不管讀取到的實際大小是多少,handler中始終會得到大小為0;
3.還有一個坑,就不寫代碼了。在同一個socket句柄上,不可以同時有多於一個異步讀取或異步寫入事件,必須等一個完成才能發下一個。原因很簡單,一起讀的話來了數據算誰的?一起寫的話數據都寫混亂了。
客戶端就不寫了,明白了這個客戶端也就很好理解了。下一次寫寫更好的管理異步連接的方法,一堆全局變量,socket指針到處傳還要自己手動delete的代碼太不雅觀了。
未經允許不得轉載:GoMCU » asio學習筆記5