nodejs socket stream的奇葩問題
拿nodejs寫一個socks5代理玩,放到VPS上以後發現內存佔用在兩周後高達100M,乃大驚,這麼個小玩意怎會用掉這麼多內存,便開始填坑,這裡把結論記一下。
基本流程是這麼來的:
1.客戶端發起連接到serverlistener=>(socket),得到clientSocket,作為session開始
2.做一些簡單的協議處理
3.發起一個agentsocket到遠端目標伺服器
這時候session的是否保留上下文就由兩個socket是否經過on("close",()=>{im closed})來判定
中間的時序問題不丟一邊,理論上說在兩個socket都建立以後,總存在一方先進入"close",並以此為觸發時機關掉對端,而session只要藉此時機檢查兩端均已close即可刪除上下文,返回使用內存給GC。
所以基本的關閉代碼可以這麼寫(挖坑後的簡化,原上下文太長):
跑兩周下來,這個流程成功了50萬次,但有大概2000次走不完這個流程,測試了下session的內存佔用,大概有個30M左右,原來坑在此處,還有大概30-50M就不知何處跑掉了。尼瑪這邏輯上無懈可擊的事情怎麼就見了鬼了。挖了兩天,問題出在net.socket上。
nodejs的socket本身不帶關閉的方法,可用的辦法只有end()和destory()兩種,end比較明確,等效於IP層發送一個TCP_FIN的包做關閉握手。而destory()是強制結束本地連接強制結束。想下c的close(socket),做完走人,絕無殘留。這看起來還不錯,但是。。。nodejs
end()或是destory()以後socket並不是直接釋放或消失了
那些沒有觸發on(close,()=>{})的socket進入了一個叫做ReadOnly的狀態。
原來nodejs的socket自帶一個stream介面,這貨有一個buffer注意這個Buffer不是socket在驅動層本身的buffer,而是socket stream自帶。也就是說,當調用end到對端,socket被關閉而無法被寫入的時候,那些沒有被拿走的數據都被緩存到這個Buffer,在這個Buffer被清空之前是不會走到「end"->"close"->"destory"狀態的.所以解決辦法是把pengding的buffer拿空,保證socket drain.
由於前面被阻塞了一下,同時socket連接實際已經斷開,所以on("data")也是不能被觸發的。所以我們知道問題所在,也知道解決的辦法,但是不知道時機---啥時候去讀。
所以第一個骯髒的解決辦法就來了,在server上開一個專門的timer,每隔一段時間去看滿足被end()了,但沒有進入「close」的socket,發現一個讀一個,讀空位置。別說,還挺管用。
又過了幾天,想到用stream介面做一下這個代理的加密,發現stream有一個叫做"readable"的事件,所以直接試了一下,最後留下這個不髒的方案.
agentSocket.end()
agentSocket.on("readable",()=>{ while(agentSocket.read()!=null); })看不到臟實現,舒服。
推薦閱讀: