Dubbo底層Socket通信詳解

 通信理論

計算機與外界的信息交換稱爲通信。基本的通信方法有並行通信和串行通信兩種。

1.一組信息(通常是字節)的各位數據被同時傳送的通信方法稱爲並行通信。並行通信依靠並行I/O接口實現。並行通信速度快,但傳輸線根數多,只適用於近距離(相距數公尺)的通信。

2.一組信息的各位數據被逐位順序傳送的通信方式稱爲串行通信。串行通信可通過串行接口來實現。串行通信速度慢,但傳輸線少,適宜長距離通信。

串行通信按信息傳送方向分爲以下3種:

1)   單工

只能一個方向傳輸數據

 

2)   半雙工

信息能雙向傳輸,但不能同時雙向傳輸

 

3)   全雙工

能雙向傳輸並且可以同時雙向傳輸

 

  Socket

Socket 是一種應用接口, TCP/IP 是網絡傳輸協議,雖然接口相同, 但是不同的協議會有不同的服務性質。創建Socket 連接時,可以指定使用的傳輸層協議,Socket 可以支持不同的傳輸層協議(TCP 或UDP ),當使用TCP 協議進行連接時,該Socket 連接就是一個TCP 連接。Soket 跟TCP/IP 並沒有必然的聯繫。Socket 編程接口在設計的時候,就希望也能適應其他的網絡協議。所以,socket 的出現只是可以更方便的使用TCP/IP 協議棧而已。

 

通常,一個典型的同步遠程調用應該是這樣的:

 

1, 客戶端線程調用遠程接口,向服務端發送請求,同時當前線程應該處於“暫停“狀態,即線程不能向後執行了,必需要拿到服務端給自己的結果後才能向後執行

2, 服務端接到客戶端請求後,處理請求,將結果給客戶端

3, 客戶端收到結果,然後當前線程繼續往後執行

Dubbo裏使用到了Socket(採用apache mina框架做底層調用)來建立長連接,發送、接收數據,底層使用apache mina框架的IoSession進行發送消息。

 

查看Dubbo文檔及源代碼可知,Dubbo底層使用Socket發送消息的形式進行數據傳遞,結合了mina框架,使用IoSession.write()方法,這個方法調用後對於整個遠程調用(從發出請求到接收到結果)來說是一個異步的,即對於當前線程來說,將請求發送出來,線程就可以往後執行了,至於服務端的結果,是服務端處理完成後,再以消息的形式發送給客戶端的。於是這裏出現了2個問題:

  • 當前線程怎麼讓它“暫停”,等結果回來後,再向後執行?
  • 正如前面所說,Socket通信是一個全雙工的方式,如果有多個線程同時進行遠程方法調用,這時建立在client server之間的socket連接上會有很多雙方發送的消息傳遞,前後順序也可能是亂七八糟的,server處理完結果後,將結果消息發送給client,client收到很多消息,怎麼知道哪個消息結果是原先哪個線程調用的?

分析源代碼,基本原理如下:

  1. client一個線程調用遠程接口,生成一個唯一的ID(比如一段隨機字符串,UUID等),Dubbo是使用AtomicLong從0開始累計數字的
  2. 將打包的方法調用信息(如調用的接口名稱,方法名稱,參數值列表等),和處理結果的回調對象callback,全部封裝在一起,組成一個對象object
  3. 向專門存放調用信息的全局ConcurrentHashMap裏面put(ID, object)
  4. 將ID和打包的方法調用信息封裝成一對象connRequest,使用IoSession.write(connRequest)異步發送出去
  5. 當前線程再使用callback的get()方法試圖獲取遠程返回的結果,在get()內部,則使用synchronized獲取回調對象callback的鎖, 再先檢測是否已經獲取到結果,如果沒有,然後調用callback的wait()方法,釋放callback上的鎖,讓當前線程處於等待狀態。
  6. 服務端接收到請求並處理後,將結果(此結果中包含了前面的ID,即回傳)發送給客戶端,客戶端socket連接上專門監聽消息的線程收到消息,分析結果,取到ID,再從前面的ConcurrentHashMap裏面get(ID),從而找到callback,將方法調用結果設置到callback對象裏。
  7. 監聽線程接着使用synchronized獲取回調對象callback的鎖(因爲前面調用過wait(),那個線程已釋放callback的鎖了),再notifyAll(),喚醒前面處於等待狀態的線程繼續執行(callback的get()方法繼續執行就能拿到調用結果了),至此,整個過程結束。

需要注意的是,這裏的callback對象是每次調用產生一個新的,不能共享,否則會有問題;另外ID必需至少保證在一個Socket連接裏面是唯一的。


現在,前面兩個問題已經有答案了,

  • 當前線程怎麼讓它“暫停”,等結果回來後,再向後執行?

     答:先生成一個對象obj,在一個全局map裏put(ID,obj)存放起來,再用synchronized獲取obj鎖,再調用obj.wait()讓當前線程處於等待狀態,然後另一消息監聽線程等到服務端結果來了後,再map.get(ID)找到obj,再用synchronized獲取obj鎖,再調用obj.notifyAll()喚醒前面處於等待狀態的線程。

  • 正如前面所說,Socket通信是一個全雙工的方式,如果有多個線程同時進行遠程方法調用,這時建立在client server之間的socket連接上會有很多雙方發送的消息傳遞,前後順序也可能是亂七八糟的,server處理完結果後,將結果消息發送給client,client收到很多消息,怎麼知道哪個消息結果是原先哪個線程調用的?

     答:使用一個ID,讓其唯一,然後傳遞給服務端,再服務端又回傳回來,這樣就知道結果是原先哪個線程的了。

參考資料:https://www.cnblogs.com/1201x/p/6482638.html

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