閉包
在函數內部實現一個子函數,子函數的作用域內能訪問外部函數的局部變量。閉包就是能夠讀取其他函數內部變量。但是由于閉包會使得函數中的變量都被保存在內存中,內存消耗很大,所以不能濫用閉包,否則會造成程的性能問題,可能導致內存泄露。
在上次的echo異步服務器中,在極端情況下客戶端關閉導致觸發寫和讀回調函數,二者都進入錯誤處理邏輯,進而造成二次析構的問題。
這里,使用C++11智能指針(引用計數的特性)構造成一個偽閉包的狀態延長session的生命周期。
Server類改造
作為Server類,希望能夠管理每一個生成的普通套接字,即管理每一個客戶端連接的Session。所以在頭文件中增加了一個map結構,key為唯一的uid,value則是交由智能指針管理的Session會話。
再改在Server類中的start_accept函數,將每次構造的新Session交由智能指針管理
在start_accept函數中綁定了handle_accept函數,所以這個函數也需要再改造一次
當調用start_accept函數的時候,就說明已經有新的連接到來了,這個時候需要將新的session添加到map中,供server管理。
session類改造
我們可以思考一下,當讀寫的時候,在觸發讀寫回調函數的時候,session已經被釋放,這樣的情況是非常危險的。所以在綁定將session作為智能指針傳遞給回調函數,延長其生命周期。
在session類中,邏輯沖start函數中開始在綁定回調函數,該怎么將對象本身作為智能指針向下傳遞呢?
對象類必須繼承一個模板類std::enable_shared_from_this<T>,這里的T就是類本身的類名,再繼承這個模板類之后,提供有一個方法shared_from_this(),這個方法能將構造返回一個對象本身的智能指針。
至此,回調函數只用拿一個智能指針參數接收即可,若需要繼續綁定其它回調函數,將這個智能指針參數繼續向下傳遞即可。
二次析構測驗
這里在讀回調這里測驗二次析構的問題,代碼如下
具體操作是在異步寫的地方打下斷點,客戶端連接之后立馬斷開,server端再繼續向下執行,觀察是否會發生內存崩潰的情況。
測試結果是不會發生內存崩潰的情況。
分析:這里使用shared_ptr的引用計數的策略延長對象的生命周期,實現偽閉包的效果。
再server類改造的時候,引用計數就為3。構造智能指針的時候,引用計數+1;綁定回調函數的時候,引用計數+1;加入map中,引用計數+1;所以session的引用計數至少為3,因為在session中,對應的session本身也在綁定回調函數的時候向下傳遞,引用計數也隨之增加,離開對應作用域,引用計數也隨之減少。當引用計數減至為0的時候,session的資源便會自動被釋放了。
bind綁定函數的時候,會返回一個新的函數對象回來,所以在綁定函數的時候對應的智能指針的引用計數會+1。
也許上邊智能指針的引用計數加減還會有疑惑,建議可以舉一個客戶端連接的實例分析,或者調斷點打印智能指針的引用計數,觀察效果。