為什么需要異步編程


一、背景

Reactor和Proactor模型一文中講到,Reactor模型提供了一個比較理想的I/O編程框架,讓程序更有結構,用戶使用起來更加方便,比裸API調用開發效率要高。另外一方面,如果希望每個事件通知之后,做的事情能有機會被代理到某個線程里面去單獨運行,而線程完成的狀態又能通知回主任務,那么“異步”的機制就必須被引入。本文以boost.Asio庫(其設計模式為Proactor)為基礎,講解為什么需要異步編程以及異步編程的實現。

二、舉例

跑步

設想你是一位體育老師,需要測驗100位同學的400米成績。你當然不會讓100位同學一起起跑,因為當同學們返回終點時,你根本來不及掐表記錄各位同學的成績。

如果你每次讓一位同學起跑並等待他回到終點你記下成績后再讓下一位起跑,直到所有同學都跑完。恭喜你,你已經掌握了同步阻塞模式。你設計了一個函數,傳入參數是學生號和起跑時間,返回值是到達終點的時間。你調用該函數100次,就能完成這次測驗任務。這個函數是同步的,因為只要你調用它,就能得到結果;這個函數也是阻塞的,因為你一旦調用它,就必須等待,直到它給你結果,不能去干其他事情。

如果你一邊每隔10秒讓一位同學起跑,直到所有同學出發完畢;另一邊每有一個同學回到終點就記錄成績,直到所有同學都跑完。恭喜你,你已經掌握了異步非阻塞模式。你設計了兩個函數,其中一個函數記錄起跑時間和學生號,該函數你會主動調用100次;另一個函數記錄到達時間和學生號,該函數是一個事件驅動的callback函數,當有同學到達終點時,你會被動調用。你主動調用的函數是異步的,因為你調用它,它並不會告訴你結果;這個函數也是非阻塞的,因為你一旦調用它,它就馬上返回,你不用等待就可以再次調用它。但僅僅將這個函數調用100次,你並沒有完成你的測驗任務,你還需要被動等待調用另一個函數100次。

當然,你馬上就會意識到,同步阻塞模式的效率明顯低於異步非阻塞模式。那么,誰還會使用同步阻塞模式呢?不錯,異步模式效率高,但更麻煩,你一邊要記錄起跑同學的數據,一邊要記錄到達同學的數據,而且同學們回到終點的次序與起跑的次序並不相同,所以你還要不停地在你的成績冊上查找學生號。忙亂之中你往往會張冠李戴。你可能會想出更聰明的辦法:你帶了很多塊秒表,讓同學們分組互相測驗。恭喜你!你已經掌握了多線程同步模式

每個拿秒表的同學都可以獨立調用你的同步函數,這樣既不容易出錯,效率也大大提高,只要秒表足夠多,同步的效率也能達到甚至超過異步。

可以理解,你現的問題可能是:既然多線程同步既快又好,異步模式還有存在的必要嗎?

很遺憾,異步模式依然非常重要,因為在很多情況下,你拿不出很多秒表。你需要通信的對端系統可能只允許你建立一個SOCKET連接,很多金融、電信行業的大型業務系統都如此要求。

三、背景知識介紹

3.1 同步函數VS異步函數

以下部分主要來自於:https://www.cnblogs.com/balingybj/p/4780442.html
依據微軟的MSDN上的解說:
(1)、同步函數:當一個函數是同步執行時,那么當該函數被調用時不會立即返回,直到該函數所要做的事情全都做完了才返回。
(2)、異步函數:如果一個異步函數被調用時,該函數會立即返回盡管該函數規定的操作任務還沒有完成。
(3)、在一個線程中分別調用上述兩種函數會對調用線程有何影響呢?

  • 當一個線程調用一個同步函數時(例如:該函數用於完成寫文件任務),如果該函數沒有立即完成規定的操作,則該操作會導致該調用線程的掛起(將CPU的使用權交給系統,讓系統分配給其他線程使用),直到該同步函數規定的操作完成才返回,最終才能導致該調用線程被重新調度。
  • 當一個線程調用的是一個異步函數(例如:該函數用於完成寫文件任務),該函數會立即返回盡管其規定的任務還沒有完成,這樣線程就會執行異步函數的下一條語句,而不會被掛起。那么該異步函數所規定的工作是如何被完成的呢?當然是通過另外一個線程完成的了啊;那么新的線程是哪里來的呢?可能是在異步函數中新創建的一個線程也可能是系統中已經准備好的線程。

(4)、一個調用了異步函數的線程如何與異步函數的執行結果同步呢?

  • 為了解決該問題,調用線程需要使用“等待函數”來確定該異步函數何時完成了規定的任務。因此在線程調用異步函數之后立即調用一個“等待函數”掛起調用線程,一直等到異步函數執行完其所有的操作之后,再執行線程中的下一條指令。

我們是否已經發現了一個有趣的地方呢?!就是我們可以使用等待函數將一個異步執行的函數封裝成一個同步函數。

3.2 同步調用VS異步調用

操作系統發展到今天已經十分精巧,線程就是其中一個傑作。操作系統把 CPU 處理時間划分成許多短暫時間片,在時間 T1 執行一個線程的指令,到時間 T2 又執行下一線程的指令,各線程輪流執行,結果好象是所有線程在並肩前進。這樣,編程時可以創建多個線程,在同一期間執行,各線程可以“並行”完成不同的任務。

在單線程方式下,計算機是一台嚴格意義上的馮·諾依曼式機器,一段代碼調用另一段代碼時,只能采用同步調用,必須等待這段代碼執行完返回結果后,調用方才能繼續往下執行。有了多線程的支持,可以采用異步調用,調用方和被調方可以屬於兩個不同的線程,調用方啟動被調方線程后,不等對方返回結果就繼續執行后續代碼。被調方執行完畢后,通過某種手段通知調用方:結果已經出來,請酌情處理。  

計算機中有些處理比較耗時。調用這種處理代碼時,調用方如果站在那里苦苦等待,會嚴重影響程序性能。例如,某個程序啟動后如果需要打開文件讀出其中的數據,再根據這些數據進行一系列初始化處理,程序主窗口將遲遲不能顯示,讓用戶感到這個程序怎么等半天也不出來,太差勁了。借助異步調用可以把問題輕松化解:把整個初始化處理放進一個單獨線程,主線程啟動此線程后接着往下走,讓主窗口瞬間顯示出來。等用戶盯着窗口犯呆時,初始化處理就在背后悄悄完成了。程序開始穩定運行以后,還可以繼續使用這種技巧改善人機交互的瞬時反應。用戶點擊鼠標時,所激發的操作如果較費時,再點擊鼠標將不會立即反應,整個程序顯得很沉重。借助異步調用處理費時的操作,讓主線程隨時恭候下一條消息,用戶點擊鼠標時感到輕松快捷,肯定會對軟件產生好感。

異步調用用來處理從外部輸入的數據特別有效。假如計算機需要從一台低速設備索取數據,然后是一段冗長的數據處理過程,采用同步調用顯然很不合算:計算機先向外部設備發出請求,然后等待數據輸入;而外部設備向計算機發送數據后,也要等待計算機完成數據處理后再發出下一條數據請求。雙方都有一段等待期,拉長了整個處理過程。其實,計算機可以在處理數據之前先發出下一條數據請求,然后立即去處理數據。如果數據處理比數據采集快,要等待的只有計算機,外部設備可以連續不停地采集數據。如果計算機同時連接多台輸入設備,可以輪流向各台設備發出數據請求,並隨時處理每台設備發來的數據,整個系統可以保持連續高速運轉。編程的關鍵是把數據索取代碼和數據處理代碼分別歸屬兩個不同的線程。數據處理代碼調用一個數據請求異步函數,然后徑自處理手頭的數據。待下一組數據到來后,數據處理線程將收到通知,結束 wait 狀態,發出下一條數據請求,然后繼續處理數據。

異步調用時,調用方不等被調方返回結果就轉身離去,因此必須有一種機制讓被調方有了結果時能通知調用方。在同一進程中有很多手段可以利用,筆者常用的手段是回調、event 對象和消息。

回調:回調方式很簡單:調用異步函數時在參數中放入一個函數地址,異步函數保存此地址,待有了結果后回調此函數便可以向調用方發出通知。如果把異步函數包裝進一個對象中,可以用事件取代回調函數地址,通過事件處理例程向調用方發通知。   

event : event 是 Windows 系統提供的一個常用同步對象,以在異步處理中對齊不同線程之間的步點。如果調用方暫時無事可做,可以調用 wait 函數等在那里,此時 event 處於 nonsignaled 狀態。當被調方出來結果之后,把 event 對象置於 signaled 狀態,wait 函數便自動結束等待,使調用方重新動作起來,從被調方取出處理結果。這種方式比回調方式要復雜一些,速度也相對較慢,但有很大的靈活性,可以搞出很多花樣以適應比較復雜的處理系統。

消息:借助 Windows 消息發通知是個不錯的選擇,既簡單又安全。程序中定義一個用戶消息,並由調用方准備好消息處理例程。被調方出來結果之后立即向調用方發送此消息,並通過 WParam 和 LParam 這兩個參數傳送結果。消息總是與窗口 handle 關聯,因此調用方必須借助一個窗口才能接收消息,這是其不方便之處。另外,通過消息聯絡會影響速度,需要高速處理時回調方式更有優勢。

如果調用方和被調方分屬兩個不同的進程,由於內存空間的隔閡,一般是采用 Windows 消息發通知比較簡單可靠,被調方可以借助消息本身向調用方傳送數據。event 對象也可以通過名稱在不同進程間共享,但只能發通知,本身無法傳送數據,需要借助 Windows 消息和 FileMapping 等內存共享手段或借助 MailSlot 和 Pipe 等通信手段。

如果你的服務端的客戶端數量多,你的服務端就采用異步的,但是你的客戶端可以用同步的,客戶端一般功能比較單一,收到數據后才能執行下面的工作,所以弄成同步的在那等。

3.3 同步異步與阻塞和非阻塞

同步異步指的是通信模式,而阻塞和非阻塞指的是在接收和發送時是否等待動作完成才返回。

首先是通信的同步,主要是指客戶端在發送請求后,必須得在服務端有回應后才發送下一個請求。所以這個時候的所有請求將會在服務端得到同步。
其次是通信的異步,指客戶端在發送請求后,不必等待服務端的回應就可以發送下一個請求,這樣對於所有的請求動作來說將會在服務端得到異步,這條請求的鏈路就象是一個請求隊列,所有的動作在這里不會得到同步的。

阻塞和非阻塞只是應用在請求的讀取和發送。
在實現過程中,如果服務端是異步的話,客戶端也是異步的話,通信效率會很高,但如果服務端在請求的返回時也是返回給請求的鏈路時,客戶端是可以同步的,這種情況下,服務端是兼容同步和異步的。相反,如果客戶端是異步而服務端是同步的也不會有問題,只是處理效率低了些。

舉個打電話的例子

阻塞 block 是指,你撥通某人的電話,但是此人不在,於是你拿着電話等他回來,其間不能再用電話。同步大概和阻塞差不多。
非阻塞 nonblock 是指,你撥通某人的電話,但是此人不在,於是你掛斷電話,待會兒再打。至於到時候他回來沒有,只有打了電話才知道。即所謂的“輪詢 / poll”。
異步是指,你撥通某人的電話,但是此人不在,於是你叫接電話的人告訴那人(leave a message),回來后給你打電話(call back)。

一、同步阻塞模式
在這個模式中,用戶空間的應用程序執行一個系統調用,並阻塞,直到系統調用完成為止(數據傳輸完成或發生錯誤)。

二、同步非阻塞模式
同步阻塞 I/O 的一種效率稍低的。非阻塞的實現是 I/O 命令可能並不會立即滿足,需要應用程序調用許多次來等待操作完成。這可能效率不高,因為在很多情況下,當內核執行這個命令時,應用程序必須要進行忙碌等待,直到數據可用為止,或者試圖執行其他工作。因為數據在內核中變為可用到用戶調用 read 返回數據之間存在一定的間隔,這會導致整體數據吞吐量的降低。但異步非阻塞由於是多線程,效率還是高。

/* create the connection by socket 
* means that connect "sockfd" to "server_addr"
* 同步阻塞模式 
*/
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
{
perror("connect");
exit(1);
}

/* 同步非阻塞模式 */
while (send(sockfd, snd_buf, sizeof(snd_buf), MSG_DONTWAIT) == -1)
{
sleep(1);
printf("sleep\n");
}

四、異步的需求

前面說了那么多,現在終於可以回到我們的正題,介紹異步編程了。就像之前所說的,同步編程比異步編程簡單很多。這是因為,線性的思考是很簡單的(調用A,調用A結束,調用B,調用B結束,然后繼續,這是以事件處理的方式來思考)。后面你會碰到這種情況,比如:五件事情,你不知道它們執行的順序,也不知道他們是否會執行!這部分主要參考:https://mmoaay.gitbooks.io/boost-asio-cpp-network-programming-chinese/content/Chapter2.html

盡管異步編程更難,但是你會更傾向於選擇使用它,比如:寫一個需要處理很多並發訪問的服務端。並發訪問越多,異步編程就比同步編程越簡單。

假設:你有一個需要處理1000個並發訪問的應用,從客戶端發給服務端的每個信息都會再返回給客戶端,以‘\n’結尾。

同步方式的代碼,1個線程:

using namespace boost::asio;
struct client {
    ip::tcp::socket sock;
    char buff[1024]; // 每個信息最多這么大
    int already_read; // 你已經讀了多少
};
std::vector<client> clients;
void handle_clients() {
    while ( true)
        for ( int i = 0; i < clients.size(); ++i)
            if ( clients[i].sock.available() ) on_read(clients[i]);
}
void on_read(client & c) {
    int to_read = std::min( 1024 - c.already_read, c.sock.available());
    c.sock.read_some( buffer(c.buff + c.already_read, to_read));
    c.already_read += to_read;
    if ( std::find(c.buff, c.buff + c.already_read, '\n') < c.buff + c.already_read) {
        int pos = std::find(c.buff, c.buff + c.already_read, '\n') - c.buff;
        std::string msg(c.buff, c.buff + pos);
        std::copy(c.buff + pos, c.buff + 1024, c.buff);
        c.already_read -= pos;
        on_read_msg(c, msg);
    }
}
void on_read_msg(client & c, const std::string & msg) {
    // 分析消息,然后返回
    if ( msg == "request_login")
        c.sock.write( "request_ok\n");
    else if ...
}

有一種情況是在任何服務端(和任何基於網絡的應用)都需要避免的,就是代碼無響應的情況。在我們的例子里,我們需要handle_clients()方法盡可能少的阻塞。如果方法在某個點上阻塞,任何進來的信息都需要等待方法解除阻塞才能被處理。

為了保持響應,只在一個套接字有數據的時候我們才讀,也就是說,if ( clients[i].sock.available() ) on_read(clients[i])。在on_read時,我們只讀當前可用的;調用read_until(c.sock, buffer(...), '\n')會是一個非常糟糕的選擇,因為直到我們從一個指定的客戶端讀取了完整的消息之前,它都是阻塞的(我們永遠不知道它什么時候會讀取到完整的消息)

這里的瓶頸就是on_read_msg()方法;當它執行時,所有進來的消息都在等待。一個良好的on_read_msg()方法實現會保證這種情況基本不會發生,但是它還是會發生(有時候向一個套接字寫入數據,緩沖區滿了時,它會被阻塞)
同步方式的代碼,10個線程

using namespace boost::asio;
struct client {
   // ... 和之前一樣
    bool set_reading() {
        boost::mutex::scoped_lock lk(cs_);
        if ( is_reading_) return false; // 已經在讀取
        else { is_reading_ = true; return true; }
    }
    void unset_reading() {
        boost::mutex::scoped_lock lk(cs_);
        is_reading_ = false;
    }
private:
    boost::mutex cs_;
    bool is_reading_;
};
std::vector<client> clients;
void handle_clients() {
    for ( int i = 0; i < 10; ++i)
        boost::thread( handle_clients_thread);
}
void handle_clients_thread() {
    while ( true)
        for ( int i = 0; i < clients.size(); ++i)
            if ( clients[i].sock.available() )
                if ( clients[i].set_reading()) {
                    on_read(clients[i]);
                    clients[i].unset_reading();
                }
}
void on_read(client & c) {
    // 和之前一樣
}
void on_read_msg(client & c, const std::string & msg) {
    // 和之前一樣
}

為了使用多線程,我們需要對線程進行同步,這就是set_reading()set_unreading()所做的。set_reading()方法非常重要,比如你想要一步實現“判斷是否在讀取然后標記為讀取中”。但這是有兩步的(“判斷是否在讀取”和“標記為讀取中”),你可能會有兩個線程同時為一個客戶端判斷是否在讀取,然后你會有兩個線程同時為一個客戶端調用on_read,結果就是數據沖突甚至導致應用崩潰。

你會發現代碼變得極其復雜。

同步編程有第三個選擇,就是為每個連接開辟一個線程。但是當並發的線程增加時,這就成了一種災難性的情況。

然后,讓我們來看異步編程。我們不斷地異步讀取。當一個客戶端請求某些東西時,on_read被調用,然后回應,然后等待下一個請求(然后開始另外一個異步的read操作)。

異步方式的代碼,10個線程

using namespace boost::asio;
io_service service;
struct client {
    ip::tcp::socket sock;
    streambuf buff; // 從客戶端取回結果
}
std::vector<client> clients;
void handle_clients() {
    for ( int i = 0; i < clients.size(); ++i)
        async_read_until(clients[i].sock, clients[i].buff, '\n', boost::bind(on_read, clients[i], _1, _2));
    for ( int i = 0; i < 10; ++i)
        boost::thread(handle_clients_thread);
}
void handle_clients_thread() {
    service.run();
}
void on_read(client & c, const error_code & err, size_t read_bytes) {
    std::istream in(&c.buff);
    std::string msg;
    std::getline(in, msg);
    if ( msg == "request_login")
        c.sock.async_write( "request_ok\n", on_write);
    else if ...
    ...
    // 等待同一個客戶端下一個讀取操作
    async_read_until(c.sock, c.buff, '\n', boost::bind(on_read, c, _1, _2));
}

發現代碼變得有多簡單了吧?client結構里面只有兩個成員,handle_clients()僅僅調用了async_read_until,然后它創建了10個線程,每個線程都調用service.run()。這些線程會處理所有來自客戶端的異步read操作,然后分發所有向客戶端的異步write操作。另外需要注意的一件事情是:on_read()一直在為下一次異步read操作做准備(看最后一行代碼)。

4.1 持續運行

再一次說明,如果有等待執行的操作,run()會一直執行,直到你手動調用io_service::stop()。為了保證io_service一直執行,通常你添加一個或者多個異步操作,然后在它們被執行時,你繼續一直不停地添加異步操作,比如下面代碼:

using namespace boost::asio;
io_service service;
ip::tcp::socket sock(service);
char buff_read[1024], buff_write[1024] = "ok";
void on_read(const boost::system::error_code &err, std::size_t bytes);
void on_write(const boost::system::error_code &err, std::size_t bytes)
{
    sock.async_read_some(buffer(buff_read), on_read);
}
void on_read(const boost::system::error_code &err, std::size_t bytes)
{
    // ... 處理讀取操作 ...
    sock.async_write_some(buffer(buff_write,3), on_write);
}
void on_connect(const boost::system::error_code &err) {
    sock.async_read_some(buffer(buff_read), on_read);
}
int main(int argc, char* argv[]) {
    ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 2001);
    sock.async_connect(ep, on_connect);
    service.run();
}
  1. service.run()被調用時,有一個異步操作在等待。
  2. 當socket連接到服務端時,on_connect被調用了,它會添加一個異步操作。
  3. on_connect結束時,我們會留下一個等待的操作(read)。
  4. on_read被調用時,我們寫入一個回應,這又添加了另外一個等待的操作。
  5. on_read結束時,我們會留下一個等待的操作(write)。
  6. on_write操作被調用時,我們從服務端讀取另外一個消息,這也添加了另外一個等待的操作。
  7. on_write結束時,我們有一個等待的操作(read)。
  8. 然后一直繼續循環下去,直到我們關閉這個應用。

4.2 保持活動

假設你需要做下面的操作:

io_service service;
ip::tcp::socket sock(service);
char buff[512];
...
read(sock, buffer(buff));

在這個例子中,sockbuff的存在時間都必須比read()調用的時間要長。也就是說,在調用read()返回之前,它們都必須有效。這就是你所期望的;你傳給一個方法的所有參數在方法內部都必須有效。當我們采用異步方式時,事情會變得比較復雜。

io_service service;
ip::tcp::socket sock(service);
char buff[512];
void on_read(const boost::system::error_code &, size_t) {}
...
async_read(sock, buffer(buff), on_read);

在這個例子中,sockbuff的存在時間都必須比read()操作本身時間要長,但是read操作持續的時間我們是不知道的,因為它是異步的。

當使用socket緩沖區的時候,你會有一個buffer實例在異步調用時一直存在(使用boost::shared_array<>)。在這里,我們可以使用同樣的方式,通過創建一個類並在其內部管理socket和它的讀寫緩沖區。然后,對於所有的異步操作,傳遞一個包含智能指針的boost::bind仿函數給它:

using namespace boost::asio;
io_service service;
struct connection : boost::enable_shared_from_this<connection> {
    typedef boost::system::error_code error_code;
    typedef boost::shared_ptr<connection> ptr;
    connection() : sock_(service), started_(true) {}
    void start(ip::tcp::endpoint ep) {
        sock_.async_connect(ep, boost::bind(&connection::on_connect, shared_from_this(), _1));
    }
    void stop() {
        if ( !started_) return;
        started_ = false;
        sock_.close();
    }
    bool started() { return started_; }
private:
    void on_connect(const error_code & err) {
        // 這里你決定用這個連接做什么: 讀取或者寫入
        if ( !err) do_read();
        else stop();
    }
    void on_read(const error_code & err, size_t bytes) {
        if ( !started() ) return;
        std::string msg(read_buffer_, bytes);
        if ( msg == "can_login") do_write("access_data");
        else if ( msg.find("data ") == 0) process_data(msg);
        else if ( msg == "login_fail") stop();
    }
    void on_write(const error_code & err, size_t bytes) {
        do_read(); 
    }
    void do_read() {
        sock_.async_read_some(buffer(read_buffer_), boost::bind(&connection::on_read, shared_from_this(),   _1, _2)); 
    }
    void do_write(const std::string & msg) {
        if ( !started() ) return;
        // 注意: 因為在做另外一個async_read操作之前你想要發送多個消息, 
        // 所以你需要多個寫入buffer
        std::copy(msg.begin(), msg.end(), write_buffer_);
        sock_.async_write_some(buffer(write_buffer_, msg.size()), boost::bind(&connection::on_write, shared_from_this(), _1, _2)); 
    }

    void process_data(const std::string & msg) {
        // 處理服務端來的內容,然后啟動另外一個寫入操作
    }
private:
    ip::tcp::socket sock_;
    enum { max_msg = 1024 };
    char read_buffer_[max_msg];
    char write_buffer_[max_msg];
    bool started_;
};

int main(int argc, char* argv[]) {
    ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 8001);
    connection::ptr(new connection)->start(ep);
} 

在所有異步調用中,我們傳遞一個boost::bind仿函數當作參數。這個仿函數內部包含了一個智能指針,指向connection實例。只要有一個異步操作等待時,Boost.Asio就會保存boost::bind仿函數的拷貝,這個拷貝保存了指向連接實例的一個智能指針,從而保證connection實例保持活動。問題解決!

當然,connection類僅僅是一個框架類;你需要根據你的需求對它進行調整(它看起來會和當前服務端例子的情況相當不同)。

你需要注意的是創建一個新的連接是相當簡單的:connection::ptr(new connection)- >start(ep)。這個方法啟動了到服務端的(異步)連接。當你需要關閉這個連接時,調用stop()

當實例被啟動時(start()),它會等待客戶端的連接。當連接發生時。on_connect()被調用。如果沒有錯誤發生,它啟動一個read操作(do_read())。當read操作結束時,你就可以解析這個消息;當然你應用的on_read()看起來會各種各樣。而當你寫回一個消息時,你需要把它拷貝到緩沖區,然后像我在do_write()方法中所做的一樣將其發送出去,因為這個緩沖區同樣需要在這個異步寫操作中一直存活。最后需要注意的一點——當寫回時,你需要指定寫入的數量,否則,整個緩沖區都會被發送出去。

總結

網絡api實際上要繁雜得多,這個章節只是做為一個參考,當你在實現自己的網絡應用時可以回過頭來看看。

Boost.Asio實現了端點的概念,你可以認為是IP和端口。如果你不知道准確的IP,你可以使用resolver對象將主機名,例如www.yahoo.com轉換為一個或多個IP地址。

我們也可以看到API的核心——socket類。Boost.Asio提供了TCP、UDPICMP的實現。而且你還可以用你自己的協議來對它進行擴展;當然,這個工作不適合缺乏勇氣的人。

異步編程是剛需。你應該已經明白為什么有時候需要用到它,尤其在寫服務端的時候。調用service.run()來實現異步循環就已經可以讓你很滿足,但是有時候你需要更進一步,嘗試使用run_one()、poll()或者poll_one()

當實現異步時,你可以異步執行你自己的方法;使用service.post()或者service.dispatch()

最后,為了使socket和緩沖區(read或者write)在整個異步操作的生命周期中一直活動,我們需要采取特殊的防護措施。你的連接類需要繼承自enabled_shared_from_this,然后在內部保存它需要的緩沖區,而且每次異步調用都要傳遞一個智能指針給this操作。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM