post請求阻塞問題回顧


前言

昨天在處理post 請求的時候,遇到了socketinputstream被阻塞的情況,前前后后查了很多資料,試了很多方法,但是問題依然無法解決。

我甚至還去看了Tomcat的源碼,也沒發現好的解決思路,但是在查資料的過程中,我已經找到問題的原因了,所以今天主要是來梳理下問題的原因,總結下經驗,讓各位小伙伴在以后遇到類似問題的時候,可以少踩坑,少走彎路。

問題的原因

問題的原因很簡單,就是因為我們socket實現用的是傳統的I/O流,這種實現有一個顯著的缺點——阻塞,因為本身它就是阻塞是通信。

這種實現方式下的socket通信處理過程是這樣的:客戶端像服務器端發起訪問請求(假設服務器已經啟動),服務器端接受訪問請求后,開始建立通信(InputStreamOutputStream),但是線路只有一條,發送消息的時候不能接收消息,接收消息的時候也是不能發送消息的。所以,在實際數據傳輸的時候,服務端必須接受完客戶端的所有消息后,才能返回響應消息。

在我們的項目中,阻塞就發生在接收消息階段,服務器端一直在接收客戶端消息,消息沒有接受完成,通信管路被占用,所以客戶端無法接收到響應。

這里還不得不說更詳細的原因,也就是消息接收一直不完成的原因。我們都知道,socket通信是依賴InputStreamOutputStream流的,我們這里阻塞就是因為InputStream,在處理客戶端請求的時候,我們需要不斷地從InputSteam中讀取數據,但是InputStreamread方法都是阻塞的,也就是說它讀不到數據的時候,就一直等在那里等待客戶端的數據,因為它不知道客戶端的什么時候可以傳完,這樣線程就被阻塞了,后續業務也就無法處理。

read方法,按照官方給的文檔,讀取完成后應該返回null或者-1,在讀取文件的的時候,是可以正常終止的,但是由於socket本身傳輸數據形式的特殊性,這個返回結果基本上是不會出現的。也就是說,如果已經沒有數據了,你還在讀取,這時候只會讓線程阻塞。

如果這個時候,客戶端終止請求,服務端就知道數據傳輸完了,線程就不再阻塞了,但是由於連接斷開了,服務器的響應數據也就無法傳給客戶端了。

針對這個問題,大致有兩種解決方案,一種就是客戶端在發送數據最開始就告訴服務器,他要發送多少數據,然后服務器按照數據傳輸大小,終止讀取。

我們之前有做過一個和銀行對接的項目,用的就是socket,當時的約定的規范就是先在消息最開始發送本次消息大小,然后再拼接上消息內容,這樣服務器可以先接收一個int的消息大小,然后后續數據再根據這個大小去讀取,也就不會出現阻塞的情況。

另一種解決方案是在消息傳輸結束加一個結束符號。我們在最開始的get請求處理實現中,就是根據消息最后的空行(\r\n),如果讀到這個內容,后續就不再讀取在,這樣就不會發生阻塞。

不知道大家還記不記得我們昨天說的請求頭的知識點:

post請求中,請求頭與請求體之間是有一個空行的,但是在請求體結束后,並沒有結束標識,所以無法確定啥時候請求體完成,這就導致了我們前面說的阻塞。

也有資料說,在post請求頭中增加content-length,可以解決這個問題,從理論上講應該沒啥問題,但是我在實際測試的時候,發現也有問題,有時間再研究下。

總結

任何技術的誕生和出現,都是因為其他技術的缺陷或者局限性。Nio就是在這樣的背景之下出現的,Nio全稱就是new I/O,表面它是一種新的實現方式的I/O,它是一種非阻塞式流,它可以讓你在讀的同時,實現的操作,而且目前NIO的應用特別廣泛,netty就是基於Nio構建的一個web框架。

說了這么多,其實就想說一句,如果某種實現方式滿足不了當下的業務需求,那就換種實現方式繼續戰斗。只要思想不滑坡,辦法總比問題多😹

另外需要補充說明下,后面我會再試下content-length的方式,如果還是解決不了這個問題,就打算直接上NIO


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM