1. 程式人生 > >Boost.Asio C++ 網路程式設計之二:同步和非同步

Boost.Asio C++ 網路程式設計之二:同步和非同步

       首先,非同步程式設計和同步程式設計是截然不同的。在同步程式設計中,所有的操作都是順序執行的,比如從socket中讀取(請求),然後寫入(迴應)到socket中。每一個操作都是阻塞的。因為操作是阻塞的,所以為了不影響主程式,當在socket上讀寫時,通常會建立一個或多個執行緒來處理socket的輸入/輸出。因此,同步的服務端/客戶端通常是多執行緒的。
       相比之下,非同步程式設計是事件驅動的。你啟動了一個操作,但是不知道它何時會結束;你提供一個回撥函式,當操作結束時,相應的API會呼叫這個回撥函式,並傳入操作結果。對於有著豐富經驗的Qt程式設計師來說,這就是他們的第二天性。因此,在非同步程式設計中,你只需要一個執行緒。
       因為中途做改變會非常困難而且容易出錯,所以你在專案初期(最好是一開始)就得決定採用同步還是非同步的方式實現網路通訊。這兩種方式不僅API有極大的不同,程式的語意也會完全改變(非同步網路通訊通常比同步網路通訊更加難以測試和除錯)。你需要考慮是採用阻塞呼叫和多執行緒的方式(同步,通常比較簡單),還是採用更少的執行緒和事件驅動方式(非同步,通常更復雜)。

       下面是一個基礎的同步客戶端例子:

using boost::asio;
io_service service;
ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 2001);
ip::tcp::socket sock(service);
sock.connect(ep);
       首先,你的程式至少需要一個io_service例項。Boost.Asio使用io_service同作業系統的輸入/輸出服務進行互動。通常一個io_service的例項就足夠了。然後,建立你想要連線的地址和埠,並建立socket。把socket連線到你建立的地址和埠。

       下面是一個簡單的同步伺服器端:

using boost::asio;
typedef boost::shared_ptr<ip::tcp::socket> socket_ptr;
io_service service;
ip::tcp::endpoint ep( ip::tcp::v4(), 2001)); // listen on 2001
ip::tcp::acceptor acc(service, ep);
while ( true) {
    socket_ptr sock(new ip::tcp::socket(service));
    acc.accept(*sock);
    boost::thread( boost::bind(client_session, sock));
}
void client_session(socket_ptr sock) {
    while ( true) {
        char data[512];
        size_t len = sock->read_some(buffer(data));
        if ( len > 0)
            write(*sock, buffer("ok", 2));
    }
}

       首先,同樣是至少需要一個io_service例項。然後你指定你想要監聽的埠,再建立一個接收器——一個用來接收客戶端連線的物件。 在接下來的迴圈中,你建立一個虛擬socket並等待客戶端的連線。一旦連線建立,你需要建立一個執行緒來處理這個連線。

       在client_session執行緒中來讀取一個客戶端的請求,解析請求,然後進行回覆。
       而建立一個非同步的客戶端,你需要做如下的事情:

using boost::asio;
io_service service;
ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 2001);
ip::tcp::socket sock(service);
sock.async_connect(ep, connect_handler);
service.run();
void connect_handler(const boost::system::error_code & ec) {
    // 如果ec表示“成功”我們就可以知道連線成功了
}
       在程式中你需要建立至少一個io_service例項。你需要指定連線的地址以並建立socket。
       當連線完成時,你就非同步地連線到了指定的地址和埠,也就是說,connect_handler被呼叫了。
       當connect_handler被呼叫時,檢查錯誤程式碼(ec),如果ec表示“成功”,你就可以向服務端進行非同步的寫入。
       注意:只要還有待處理的非同步操作,servece.run()迴圈就會一直執行。在上述例子中,只執行了一個這樣的操作,就是socket的async_connect。在這之後,service.run()就退出了。

       每一個非同步操作都有一個完成處理程式——一個操作完成之後被呼叫的函式。 

       下面是一個簡單的非同步伺服器端:

using boost::asio;
typedef boost::shared_ptr<ip::tcp::socket> socket_ptr;
io_service service;
ip::tcp::endpoint ep( ip::tcp::v4(), 2001)); // 監聽埠2001
ip::tcp::acceptor acc(service, ep);
socket_ptr sock(new ip::tcp::socket(service));
start_accept(sock);
service.run();
void start_accept(socket_ptr sock) {
    acc.async_accept(*sock, boost::bind( handle_accept, sock, _1) );
}
void handle_accept(socket_ptr sock, const boost::system::error_code &err) {
    if (err) return;
    // 從這裡開始, 你可以從socket讀取或者寫入
    socket_ptr sock(new ip::tcp::socket(service));
    start_accept(sock);
}
       在上述程式碼片段中,首先,你建立一個io_service例項,指定監聽的埠。然後,你建立接收器acc——一個用來接受客戶端連線的物件,建立虛擬socket,非同步等待客戶端連線。
       最後,執行非同步service.run()迴圈。當接收到客戶端連線時,handle_accept被呼叫(async_accept的完成處理程式)。如果沒有錯誤,這個socket就可以用來進行讀寫操作。

       在使用這個socket之後,會建立一個新的socket,然後再次呼叫start_accept(),它會新增另外一個“等待客戶端連線”的非同步操作,從而使service.run()迴圈一直保持忙碌狀態。