(原創)談談boost.asio的異步發送

  在上一篇博文中提到asio的異步發送稍微複雜一點,有必要單獨拿出來講說。asio異步發送複雜的地方在於: 不能連續調用異步發送接口async_write,由於async_write內部是不斷調用async_write_some,直到全部的數據發送完成爲止。因爲async_write調用以後就直接返回了,若是第一次調用async_write發送一個較大的包時,立刻又再調用async_write發送一個很小的包時,有可能這時第一次的async_write還在循環調用async_write_some發送,而第二次的async_write要發送的數據很小,一會兒就發出去了,這使得第一次發送的數據和第二次發送的數據交織在一塊兒了,致使發送亂序的問題。解決這個問題的方法就是在第一次發送完成以後再發送第二次的數據。具體的作法是用一個發送緩衝區,在異步發送完成以後從緩衝區再取下一個數據包發送。下面看看異步發送的代碼是如何實現的。html

list<MyMessage> m_sendQueue; //發送隊列

void HandleAsyncWrite(char* data, int len)
    {
        bool write_in_progress = !m_sendQueue.empty();
        m_sendQueue.emplace_back(data, len);
        if (!write_in_progress)
        {
            AsyncWrite();
        }
    }

    void AsyncWrite()
    {
        auto msg = m_sendQueue.front();
        async_write(m_sock, buffer(msg.pData, msg.len),
            [this](const boost::system::error_code& ec, std::size_t size)
        {
            if (!ec)
            {
                m_sendQueue.pop_front();

                if (!m_sendQueue.empty())
                {
                    AsyncWrite();
                }
            }
            else
            {
                HandleError(ec);
                if (!m_sendQueue.empty())
                    m_sendQueue.clear();
            }
        });
    }

  

  代碼的邏輯是這樣的:當用戶發送數據時,不直接調用異步發送接口,而是將數據放到一個發送隊列中,異步發送接口會循環從隊列中取數據發送。循環發送過程的一個細節須要注意,用戶發送數據時,若是發送隊列爲空時,說明異步發送已經將隊列中全部的數據都發送完了,也意味着循環發送結束了,這時,須要在數據入隊列以後再調用一下async_write從新發起異步循環發送。c++

  能夠看到,異步發送比異步接收等其餘異步操做更復雜,須要一個發送隊列來保證發送不會亂序。可是,還有一個問題須要注意就是這個發送隊列是沒有加限制的,若是接收端收到數據以後阻塞處理,而發送又很快的話,就會致使發送隊列的內存快速增加甚至內存爆掉。解決辦法有兩個:異步

  1. 發慢一點,而且保證接收端不會長時間阻塞socket;
  2. 控制發送隊列的上限。

  第一種方法對實際應用的約束性較強,實際可操做性不高。第二種方法須要控制隊列上限,不可避免的要加鎖,這樣就喪失了單線程異步發送的性能優點。因此建議用同步發送接口來發送數據,一來不用發送隊列,天然也不會有內存暴漲的問題,二來也不會有複雜的循環發送過程,並且還能夠經過線程池來提升發送效率。socket

總結:async

  • 不要連續發起異步發送,要等上次發送完成以後再發起下一個異步發送;
  • 要考慮異步發送的發送隊列內存可能會暴漲的問題;
  • 相比複雜的異步發送,同步發送簡單可靠,推薦優先使用同步發送接口。

 

若是你以爲這篇文章對你有用,能夠點一下推薦,謝謝。性能

c++11 boost技術交流羣:296561497,歡迎你們來交流技術。this

相關文章
相關標籤/搜索