boost asio 的一些問題

周末用 boost asio 配合 beast::http 寫了一個(gè)小的異步 https 客戶端 demo,可以通過 cookie 從 bilibili 服務(wù)器獲取本機(jī)已登陸用戶的 id 還有所佩戴的徽章(只拿了一些簡單的信息,比如徽章名字還有等級),我自己的構(gòu)思中這可以作為一些粉絲服務(wù)的前置信息。(比如大家都是 Asaki 的粉絲然后就可以直接轉(zhuǎn)到一個(gè)聊天室這種)
那在實(shí)現(xiàn)的過程中就發(fā)現(xiàn)不可避免的碰到了回調(diào)地獄的問題(因?yàn)樽约簩f(xié)程其實(shí)了解不多所以也沒有用相關(guān)特性),這里做一個(gè)小的總結(jié):
首先我們正?;蛘哒f大部分時(shí)間寫代碼都是以一種同步的方式進(jìn)行的,比如我現(xiàn)在寫 cephfs 的代碼, client 要給 mds 發(fā)送一條消息請求 open 一個(gè)文件,這個(gè)流程是怎么樣的,他一定是三步走:
make_request 創(chuàng)建一條 open 請求
向 mds 發(fā)送這個(gè)請求并等待 mds 處理
處理 mds 回復(fù)并向 kernel 返回結(jié)果
在這個(gè)過程中我們說只要 mds 沒有處理完成這個(gè) open 請求,那么 client 或者說 client 的這個(gè)線程就一定會(huì)阻塞在 wait() 處。什么,你想處理多個(gè)請求?那么請使用多個(gè)線程來處理這些請求,同樣的,每個(gè)線程都會(huì)阻塞在各自的 wait() 處等待 mds 返回。
使用多線程技術(shù)確實(shí)可以減少 cpu 算力出現(xiàn)空置的情況,但緊接著 c10k 問題的普及讓人們發(fā)現(xiàn) cpu 對大量線程的調(diào)度實(shí)際上是對 cpu 性能的致命浪費(fèi),這嚴(yán)重影響了服務(wù)器的絕對性能,因此以 nginx 為代表的異步服務(wù)器設(shè)計(jì)出現(xiàn)并成為主流,同時(shí)異步編程方式也為大家所熟知。
如果我們使用異步的方式去改寫剛才 open 文件的過程,就會(huì)像下面這樣:
make_request 創(chuàng)建一條 open 請求
向事件隊(duì)列提交一個(gè)異步操作(向 mds 發(fā)送這個(gè)請求)并立即返回
make_request 創(chuàng)建第二條請求
向事件隊(duì)列提交一個(gè)異步操作并立即返回
啟動(dòng)事件循環(huán)向 mds 發(fā)送請求
mds 返回第一個(gè)請求, client 通過提前設(shè)置的回調(diào)處理并返回
mds 返回第二個(gè)請求, client 通過提前設(shè)置的回調(diào)處理并返回
以上的所有過程都是在同個(gè)線程中完成,因此避免了線程切換,提高了 cpu 利用率,唯一的阻塞點(diǎn)在啟動(dòng)事件循環(huán)(因?yàn)樾枰却祷兀?/p>
在 asio 中,提交異步操作就是通過 async_xxx 函數(shù)完成,而執(zhí)行異步操作以及監(jiān)聽事件循環(huán)就是通過 io_context.run() 觸發(fā)
但是這樣就不可避免的遇到一個(gè)問題,如果一個(gè)操作有多個(gè)步驟并且多個(gè)步驟都依賴上一步完成,那么我們就必須在上一步的回調(diào)中執(zhí)行下一步操作,比如我要和?api.bilibili.com?建立一個(gè) tls 連接:
提交一個(gè)解析域名的異步操作 async_resolve
設(shè)置 async_resolve 的回調(diào)函數(shù)為提交一個(gè)連接到 server 的異步操作 async_connect
設(shè)置 async_connect 的回調(diào)函數(shù)為提交一個(gè)建立 tls 連接的異步操作 async_handshake
設(shè)置 async_handshake 的回調(diào)函數(shù)將 session 狀態(tài)設(shè)為 ready
通過 io_context.run() 開始事件循環(huán)
async_resolve 返回,通過回調(diào)函數(shù)調(diào)用 async_connect
async_connect 返回,通過回調(diào)函數(shù)調(diào)用 async_handshake
async_handshake 返回,通過回調(diào)函數(shù)將 session 狀態(tài)設(shè)為 ready
簡化的代碼大概類似這樣:
這種鏈?zhǔn)降恼{(diào)用方式導(dǎo)致我們很難對代碼進(jìn)行拆分和抽象,這也是“回調(diào)地獄”的由來,你的所有操作只能通過一條遞歸的線來進(jìn)行,除非通過多線程配合 promise 和 future 還有可能解決,單線程中我感覺無解。
那解決辦法的話之前其實(shí)也提到了,通過協(xié)程來實(shí)現(xiàn),也就是??吹降摹耙酝椒绞綄懏惒酱a”
所以說協(xié)程還是繞不過去的坎啊,哎,學(xué),現(xiàn)在就學(xué)。