C/S框架 st_asio_wrapper 開發教程(2019.10.17更新)(五)

如果你偶然瀏覽到這裏,請先看 C/S框架 st_asio_wrapper 開發教程(一)
源代碼及例程下載地址:
git:https://github.com/youngwolf-project/st_asio_wrapper/
QQ交流羣:198941541

十二:高級應用

        如果必須要處理一個耗時業務,如何避免影響所有連接,包括自己的數據收發呢,推薦的方法是,當要處理耗時業務時,在on_msg_handler裏面開一個線程,然後馬上退出,耗時業務在線程中處理,處理完了自動退出,這就帶來了一個問題,這個耗時業務還沒處理完的時候,後面的消息可能已經到達,這樣就出現了消息亂序的問題,就是說你無法按順序處理消息了(絕大多數情況下,順序處理消息是必須的),此時,必須暫停消息派發,可以很容易的寫出如下僞代碼:
bool thread_running = false;
void run()
{
        pop all messages from a global list
        handle message
        thread_running = false;
}

virtual bool on_msg_handle(out_msg_type& msg)
{
        if (thread_running) return false;
        push message to a global list
        thread_running = true;
        thread t(bind(&run));
        return true;
}

        這樣就做到了既不影響所有連接上的數據收發,又不至於讓消息亂序。當然,如果你需要非常頻繁的執行以上代碼,最好的辦法還是直接爲service_pump多指定一些線程,多指定多少,要看你的業務,如果平均來說,有兩個線程被創建用於處理耗時業務,那麼多指定兩個service線程即可,同樣可以做到不影響所有連接上的數據收發,還簡單很多。如果你的耗時業務是非常稀少的執行(意味着不值得多指定一些service線程,在一定吞吐量的情況下,用得線程越少,越說明你的代碼寫的好,線程越多不代表你的水平越高,況且很多時候,多線程是影響效率的),那麼上面的代碼值得你一試。
        還有一種情況跟耗時業務差不多,就是如果我暫時不方便處理業務怎麼辦?典型的例子就是:我的業務是將消息發送到一個隊列裏面供其它模塊使用,但是這個隊列滿了,我只知道它會在某個時候變得可用,但不知道要等多久,這怎麼辦呢?可以在on_msg_handle裏面直接返回false,這樣的效果就是,socket會延遲一小段時間之後,嘗試再次派發消息,這就完美解決了前面所提的問題(而不再需要開線程了)。

十三:擁塞控制

       前面說了,如果因爲發送緩存滿而阻塞在業務處理上(on_msg_handle),就會有死鎖的可能,如果我的業務就是把消息原樣返回(比如echo_server)並且要緩存可控(不能以can_overflow爲true調用send_msg),那麼我必定要在on_msg_handle中發送消息,且解除死鎖的條件就是發送緩存可用,豈不是非得面對死鎖的可能?答案是否定的:首先,消息總有個主動發起方,它要保證自己的內存佔用可控,那麼它一定會以can_overflow爲false調用send_msg,並且處理失敗的情況,這又分兩種處理方式:一是調用safe_send_msg(但不要在service線程調用它,任何時候阻塞service線程都不是明智的,哪怕你不是在等發送緩存可用,此時雖然你可以阻塞service線程,但會嚴重影響其它連接上的數據收發和處理,況且,你可以阻塞,別人也阻塞,service線程很快就全阻塞了,那你的系統就不響應了)或者類似的處理方式;二是在on_msg_send中發送消息(只發送一條),此時send_msg應該能成功(因爲我們已經成功發送了一些消息),但推薦以can_overflow爲true調用,因爲st_asio_wrapper未做精確的緩存控制,即沒有把對緩存大小的判斷和對緩存的插入進行統一互斥,因爲對於lock-free隊列,由於對其操作都不帶鎖,所以也無法精確控制,我們不能爲此給lock-free隊列加一把鎖吧?我寫點代碼示例一下:
一:精確控制:
lock
check buffer's size
insert message into buffer
unlock

二:st_asio_wrapper採用的控制:
check buffer's size
lock //對於lock-free隊列,沒有這行
insert message into buffer
unlock //對於lock-free隊列,沒有這行
這樣一來,如果有10個線程同時調用send_msg,則緩存最多可以溢出9個消息,這也算是緩存可控了,因爲溢出的數量是有上限的。從示例我們可以看出,即使在on_msg_send中發送消息,也有可能遇到緩存滿(因爲緩存可以最多溢出9個消息),所以爲了保證成功,我們以can_overflow爲true來調用send_msg是明智的,它不會造成更大的緩存溢出(最多隻溢出9個)。

現在看看接收方,如果瓶頸在接收方處理消息(特指回送消息之外的處理),那麼擁塞已經控制住了,發送方一定會遇到一些send_msg失敗的情況(發送方採用了第一種處理方式),並重發,這就是擁塞控制;如果瓶頸在接收方的上行速度(有些網絡的上行和下行速度是不一樣的)上,即在接收方的on_msg_handle裏面send_msg失敗,此時怎麼辦呢?首先我們不能阻塞on_msg_handle(因爲發送緩存不可用而阻塞),那麼我們可以直接在on_msg_handle裏面返回false,如下:
virtual bool on_msg_handle(out_msg_type& msg, bool link_down) {return send_msg(msg.data(), msg.size());}

另外,有些業務本身就是天然的自帶擁塞控制,比如pingpong測試,如果你的業務剛好是這種,那無需做任何處理就天然的具有了擁塞控制,但這種業務會嚴重限制IO吞吐量。

 

C/S框架 st_asio_wrapper 開發教程(一)
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章