'分佈式專題(2)- 分佈式 Java通信'

"

Java實現系統間的通信概覽

我們知道,所謂分佈式,無非就是“將一個系統拆分成多個子系統並散佈到不同設備”的過程而已。在微服務的大潮之中, 我們把系統拆分成了多個服務,根據需要部署在多個機器上,這些服務非常靈活,可以隨著訪問量彈性擴展。本質上而言,實現一個分佈式系統,最核心的部分無非有兩點:

如何拆分——可以有很多方式,核心依據一是業務需求,二是成本限制。這是實踐中構建分佈式系統時最主要的設計依據。

如何連接——光把系統拆開成各個子系統還不夠,關鍵是拆開後的各個子系統之間還要能通信,因此涉及通信協議設計的問題,需要考慮的因素很多,好消息是這部分其實成熟方案很多。

分佈式系統並非靈丹妙藥,解決問題的關鍵還是看你對問題本身的瞭解。通常我們需要使用分佈式的常見理由是:

為了性能擴展——系統負載高,單臺機器無法承載,希望通過使用多臺機器來提高系統的負載能力。

為了增強可靠性——軟件不是完美的,網絡不是完美的,甚至機器本身也不可能是完美的,隨時可能會出錯,為了避免故障,需要將業務分散開保留一定的冗餘度。

本篇要講的是分佈式應用中解決“如何連接”的問題,即Java是如何實現系統間的通信的。先上一張總圖:

"

Java實現系統間的通信概覽

我們知道,所謂分佈式,無非就是“將一個系統拆分成多個子系統並散佈到不同設備”的過程而已。在微服務的大潮之中, 我們把系統拆分成了多個服務,根據需要部署在多個機器上,這些服務非常靈活,可以隨著訪問量彈性擴展。本質上而言,實現一個分佈式系統,最核心的部分無非有兩點:

如何拆分——可以有很多方式,核心依據一是業務需求,二是成本限制。這是實踐中構建分佈式系統時最主要的設計依據。

如何連接——光把系統拆開成各個子系統還不夠,關鍵是拆開後的各個子系統之間還要能通信,因此涉及通信協議設計的問題,需要考慮的因素很多,好消息是這部分其實成熟方案很多。

分佈式系統並非靈丹妙藥,解決問題的關鍵還是看你對問題本身的瞭解。通常我們需要使用分佈式的常見理由是:

為了性能擴展——系統負載高,單臺機器無法承載,希望通過使用多臺機器來提高系統的負載能力。

為了增強可靠性——軟件不是完美的,網絡不是完美的,甚至機器本身也不可能是完美的,隨時可能會出錯,為了避免故障,需要將業務分散開保留一定的冗餘度。

本篇要講的是分佈式應用中解決“如何連接”的問題,即Java是如何實現系統間的通信的。先上一張總圖:

分佈式專題(2)- 分佈式 Java通信

上圖中,我們看到圖片左邊的【網絡通信】,是由協議和網絡IO組成。協議如TCP/IP等在上一篇文章中已經介紹過,多出的Multicast(組播)此處也不再延伸介紹,有需要的同學另外自行了解即可。上一篇文章在介紹傳輸層的TCP協議時,已經提到了“TCP提供全雙工通信,會話雙方都可以同時接收和發送數據。都設有接收緩存和發送緩存,用來臨時存放雙向通信的數據”。發送緩存也就是寫緩存,接收緩存也就是讀緩存。在客戶端與服務器經過三次握手建立連接後,在二者之間就相當於打開了一條可以互相傳送數據的道路,道路的兩端就是各自的讀寫緩存和我們所說的套接字Socket,每一個socket都有一個輸出流和一個輸入流。這種跨越網絡的數據IO流,就是我們說的網絡IO。然後可以看到網絡IO還分為了BIO、NIO和AIO,這個我們可以先不管,後面我會再細說。所以TCP連接差不多就是下圖這個樣子。

"

Java實現系統間的通信概覽

我們知道,所謂分佈式,無非就是“將一個系統拆分成多個子系統並散佈到不同設備”的過程而已。在微服務的大潮之中, 我們把系統拆分成了多個服務,根據需要部署在多個機器上,這些服務非常靈活,可以隨著訪問量彈性擴展。本質上而言,實現一個分佈式系統,最核心的部分無非有兩點:

如何拆分——可以有很多方式,核心依據一是業務需求,二是成本限制。這是實踐中構建分佈式系統時最主要的設計依據。

如何連接——光把系統拆開成各個子系統還不夠,關鍵是拆開後的各個子系統之間還要能通信,因此涉及通信協議設計的問題,需要考慮的因素很多,好消息是這部分其實成熟方案很多。

分佈式系統並非靈丹妙藥,解決問題的關鍵還是看你對問題本身的瞭解。通常我們需要使用分佈式的常見理由是:

為了性能擴展——系統負載高,單臺機器無法承載,希望通過使用多臺機器來提高系統的負載能力。

為了增強可靠性——軟件不是完美的,網絡不是完美的,甚至機器本身也不可能是完美的,隨時可能會出錯,為了避免故障,需要將業務分散開保留一定的冗餘度。

本篇要講的是分佈式應用中解決“如何連接”的問題,即Java是如何實現系統間的通信的。先上一張總圖:

分佈式專題(2)- 分佈式 Java通信

上圖中,我們看到圖片左邊的【網絡通信】,是由協議和網絡IO組成。協議如TCP/IP等在上一篇文章中已經介紹過,多出的Multicast(組播)此處也不再延伸介紹,有需要的同學另外自行了解即可。上一篇文章在介紹傳輸層的TCP協議時,已經提到了“TCP提供全雙工通信,會話雙方都可以同時接收和發送數據。都設有接收緩存和發送緩存,用來臨時存放雙向通信的數據”。發送緩存也就是寫緩存,接收緩存也就是讀緩存。在客戶端與服務器經過三次握手建立連接後,在二者之間就相當於打開了一條可以互相傳送數據的道路,道路的兩端就是各自的讀寫緩存和我們所說的套接字Socket,每一個socket都有一個輸出流和一個輸入流。這種跨越網絡的數據IO流,就是我們說的網絡IO。然後可以看到網絡IO還分為了BIO、NIO和AIO,這個我們可以先不管,後面我會再細說。所以TCP連接差不多就是下圖這個樣子。

分佈式專題(2)- 分佈式 Java通信

在瞭解了Socket和網絡IO的含義之後,我們看回第一張圖的右邊,可以看到Java實現系統間的通信方式有基於Java API、基於開源框架、基於遠程通信技術等。下面,我們用Java代碼來一起實現一下這幾種方式。

Socket:socket本身並不是協議,它是應用層與TCP/IP協議族通信的中間軟件抽象層,是一組調用接口(TCP/IP網絡的API函數)。可以看做是對TCP/IP協議的封裝,它把複雜的TCP/IP協議族隱藏在Socket接口後面,它的出現只是使得程序員更方便地使用TCP/IP協議棧而已。

基於Java API

java.net 包中的 API 包含有網絡編程相關的類和接口。java.net 包中能夠找到對TCP協議、UDP協議、Multicast協議的支持。我們仍以基於TCP協議的網絡編程為例。

在編程開始前,我們再次簡單回顧一下計算機網絡中的傳輸層和TCP協議。

傳輸層為應用進程之間提供端口到端口的通信

TCP提供全雙工通信,會話雙方都可以同時接收和發送數據。

(在看API的具體實現之前,思考一個有意思的問題:如果是交給你去實現客戶端與服務器的通信,你會設計多少個對象?如何設計它們的關係?如何做到面向對象設計?多看,多想,多換位思考,如果是你的話,你怎麼處理,這是對提高自己水平很有裨益的事,無論是做人還是做事。)

官方文檔提到:以下步驟在兩臺計算機之間使用套接字建立TCP連接時會出現:

  • 服務器實例化一個 ServerSocket 對象,表示通過服務器上的端口通信。
  • 服務器調用 ServerSocket 類的 accept() 方法,該方法將一直等待,直到客戶端連接到服務器上給定的端口。
  • 服務器正在等待時,一個客戶端實例化一個 Socket 對象,指定服務器名稱和端口號來請求連接。
  • Socket 類的構造函數試圖將客戶端連接到指定的服務器和端口號。如果通信被建立,則在客戶端創建一個 Socket 對象能夠與服務器進行通信。
  • 在服務器端,accept() 方法返回服務器上一個新的 socket 引用,該 socket 連接到客戶端的 socket。
  • 連接建立後,通過使用 I/O 流在進行通信,每一個socket都有一個輸出流和一個輸入流,客戶端的輸出流連接到服務器端的輸入流,而客戶端的輸入流連接到服務器端的輸出流。

上述流程有空就多看幾遍,我們後面講的所有通信都是基於上述流程。各種技術和框架不過是對這些流程不斷封裝、抽象、擴展而已,但是主流程仍是不變的。

現在,打開我們的IDEA或者Eclipse,按照API中的實現步驟,一起來實現下面的小目標。

(終於要回到我們熟悉的代碼部分了,Code Time Begin !)

小目標:對基於Java API的網絡編程有初步的瞭解。具體需求如下:

1)從客戶端把“Hello, I am xxx. Here is Client.”這條消息傳送給服務端;

2)從服務端讀取該消息,並給客戶端返回響應消息:“Hello, xxx, nice to meet you! Here is Server.”

我們可以按照以下步驟實現上述需求:

第一步:建項目

我們先新建一個項目distributed,再建一個名為mysocket的包。為了以後方便添加Jar包,我們建的是maven項目。

第二步:建服務端類

然後建一個服務端類:HelloServer,代碼如下:

package socket;

import java.io.*;
import java.net.*;

public class HelloServer {
\t// 選擇一個端口作為服務端端口
\tprivate static int port = 8888;

\tpublic static void main(String[] args) throws Exception {
\t\t// 創建ServerSocket對象,相當於在服務端(本機)打開一個監聽
\t\tServerSocket serverSocket = new ServerSocket(port);
\t\tSystem.out.println("開始監聽端口:" + port + "...");
\t\t// accept方法阻塞等待客戶端連接,連接成功後得到socket對象
\t\tSocket socket = serverSocket.accept();
\t\t// 獲取服務端socket的輸入流,客戶端通過這個輸入流給服務端傳遞消息
\t\tDataInputStream in = new DataInputStream(socket.getInputStream());
\t\t// 通過服務端socket的輸入流,輸出客戶端發送過來的消息
\t\tSystem.out.println("客戶端消息:"+in.readUTF());
\t\t// 獲取服務端socket的輸出流,服務端端通過這個輸出流給客戶端傳遞消息
\t\tDataOutputStream out = new DataOutputStream(socket.getOutputStream());
\t\t// 通過服務端socket的輸出流,給客戶端發送消息
\t\tout.writeUTF("Hello,jvxb, nice to meet you!Here is Server。");
\t\t// 關閉服務端socket。
\t\tsocket.close();
\t\t// 關閉監聽
\t\tserverSocket.close();
\t}

}

第三步:建客戶端類

然後建一個客戶端類:HelloClient,代碼如下:

package socket;

import java.io.*;
import java.net.*;

public class HelloClient {

\t// 需連接的服務端IP或域名,此例中本機即為服務端。一般都是通過配置文件來設置。
\tprivate static String serverName = "127.0.0.1";
\t// 需連接的服務端端口
\tprivate static int port = 8888;

\tpublic static void main(String[] args) throws Exception {
\t\t// 通過指定服務端IP、服務端端口,連接到服務端,連接成功後獲得客戶端socket.
\t\tSocket clientSocket = new Socket(serverName, port);
\t\t// 通過客戶端socket,獲得客戶端輸出流。
\t\tDataOutputStream out = new DataOutputStream(clientSocket.getOutputStream());
\t\t// 通過客戶端輸出流,向服務端發送消息。
\t\tout.writeUTF("Hello,I am jvxb! Here is Client.");
\t\t// 通過客戶端輸出流,讀取服務端發送過來的消息。
\t\tDataInputStream in = new DataInputStream(clientSocket.getInputStream());
\t\t// 輸出服務端發送過來的消息
\t\tSystem.out.println("服務器響應: " + in.readUTF());
\t\t// 關閉客戶端socket
\t\tclientSocket.close();
\t}
}

第四步:測試

1)運行服務端類

2)運行客戶端類

3)查看輸出結果

可以看到結果如下:

"

Java實現系統間的通信概覽

我們知道,所謂分佈式,無非就是“將一個系統拆分成多個子系統並散佈到不同設備”的過程而已。在微服務的大潮之中, 我們把系統拆分成了多個服務,根據需要部署在多個機器上,這些服務非常靈活,可以隨著訪問量彈性擴展。本質上而言,實現一個分佈式系統,最核心的部分無非有兩點:

如何拆分——可以有很多方式,核心依據一是業務需求,二是成本限制。這是實踐中構建分佈式系統時最主要的設計依據。

如何連接——光把系統拆開成各個子系統還不夠,關鍵是拆開後的各個子系統之間還要能通信,因此涉及通信協議設計的問題,需要考慮的因素很多,好消息是這部分其實成熟方案很多。

分佈式系統並非靈丹妙藥,解決問題的關鍵還是看你對問題本身的瞭解。通常我們需要使用分佈式的常見理由是:

為了性能擴展——系統負載高,單臺機器無法承載,希望通過使用多臺機器來提高系統的負載能力。

為了增強可靠性——軟件不是完美的,網絡不是完美的,甚至機器本身也不可能是完美的,隨時可能會出錯,為了避免故障,需要將業務分散開保留一定的冗餘度。

本篇要講的是分佈式應用中解決“如何連接”的問題,即Java是如何實現系統間的通信的。先上一張總圖:

分佈式專題(2)- 分佈式 Java通信

上圖中,我們看到圖片左邊的【網絡通信】,是由協議和網絡IO組成。協議如TCP/IP等在上一篇文章中已經介紹過,多出的Multicast(組播)此處也不再延伸介紹,有需要的同學另外自行了解即可。上一篇文章在介紹傳輸層的TCP協議時,已經提到了“TCP提供全雙工通信,會話雙方都可以同時接收和發送數據。都設有接收緩存和發送緩存,用來臨時存放雙向通信的數據”。發送緩存也就是寫緩存,接收緩存也就是讀緩存。在客戶端與服務器經過三次握手建立連接後,在二者之間就相當於打開了一條可以互相傳送數據的道路,道路的兩端就是各自的讀寫緩存和我們所說的套接字Socket,每一個socket都有一個輸出流和一個輸入流。這種跨越網絡的數據IO流,就是我們說的網絡IO。然後可以看到網絡IO還分為了BIO、NIO和AIO,這個我們可以先不管,後面我會再細說。所以TCP連接差不多就是下圖這個樣子。

分佈式專題(2)- 分佈式 Java通信

在瞭解了Socket和網絡IO的含義之後,我們看回第一張圖的右邊,可以看到Java實現系統間的通信方式有基於Java API、基於開源框架、基於遠程通信技術等。下面,我們用Java代碼來一起實現一下這幾種方式。

Socket:socket本身並不是協議,它是應用層與TCP/IP協議族通信的中間軟件抽象層,是一組調用接口(TCP/IP網絡的API函數)。可以看做是對TCP/IP協議的封裝,它把複雜的TCP/IP協議族隱藏在Socket接口後面,它的出現只是使得程序員更方便地使用TCP/IP協議棧而已。

基於Java API

java.net 包中的 API 包含有網絡編程相關的類和接口。java.net 包中能夠找到對TCP協議、UDP協議、Multicast協議的支持。我們仍以基於TCP協議的網絡編程為例。

在編程開始前,我們再次簡單回顧一下計算機網絡中的傳輸層和TCP協議。

傳輸層為應用進程之間提供端口到端口的通信

TCP提供全雙工通信,會話雙方都可以同時接收和發送數據。

(在看API的具體實現之前,思考一個有意思的問題:如果是交給你去實現客戶端與服務器的通信,你會設計多少個對象?如何設計它們的關係?如何做到面向對象設計?多看,多想,多換位思考,如果是你的話,你怎麼處理,這是對提高自己水平很有裨益的事,無論是做人還是做事。)

官方文檔提到:以下步驟在兩臺計算機之間使用套接字建立TCP連接時會出現:

  • 服務器實例化一個 ServerSocket 對象,表示通過服務器上的端口通信。
  • 服務器調用 ServerSocket 類的 accept() 方法,該方法將一直等待,直到客戶端連接到服務器上給定的端口。
  • 服務器正在等待時,一個客戶端實例化一個 Socket 對象,指定服務器名稱和端口號來請求連接。
  • Socket 類的構造函數試圖將客戶端連接到指定的服務器和端口號。如果通信被建立,則在客戶端創建一個 Socket 對象能夠與服務器進行通信。
  • 在服務器端,accept() 方法返回服務器上一個新的 socket 引用,該 socket 連接到客戶端的 socket。
  • 連接建立後,通過使用 I/O 流在進行通信,每一個socket都有一個輸出流和一個輸入流,客戶端的輸出流連接到服務器端的輸入流,而客戶端的輸入流連接到服務器端的輸出流。

上述流程有空就多看幾遍,我們後面講的所有通信都是基於上述流程。各種技術和框架不過是對這些流程不斷封裝、抽象、擴展而已,但是主流程仍是不變的。

現在,打開我們的IDEA或者Eclipse,按照API中的實現步驟,一起來實現下面的小目標。

(終於要回到我們熟悉的代碼部分了,Code Time Begin !)

小目標:對基於Java API的網絡編程有初步的瞭解。具體需求如下:

1)從客戶端把“Hello, I am xxx. Here is Client.”這條消息傳送給服務端;

2)從服務端讀取該消息,並給客戶端返回響應消息:“Hello, xxx, nice to meet you! Here is Server.”

我們可以按照以下步驟實現上述需求:

第一步:建項目

我們先新建一個項目distributed,再建一個名為mysocket的包。為了以後方便添加Jar包,我們建的是maven項目。

第二步:建服務端類

然後建一個服務端類:HelloServer,代碼如下:

package socket;

import java.io.*;
import java.net.*;

public class HelloServer {
\t// 選擇一個端口作為服務端端口
\tprivate static int port = 8888;

\tpublic static void main(String[] args) throws Exception {
\t\t// 創建ServerSocket對象,相當於在服務端(本機)打開一個監聽
\t\tServerSocket serverSocket = new ServerSocket(port);
\t\tSystem.out.println("開始監聽端口:" + port + "...");
\t\t// accept方法阻塞等待客戶端連接,連接成功後得到socket對象
\t\tSocket socket = serverSocket.accept();
\t\t// 獲取服務端socket的輸入流,客戶端通過這個輸入流給服務端傳遞消息
\t\tDataInputStream in = new DataInputStream(socket.getInputStream());
\t\t// 通過服務端socket的輸入流,輸出客戶端發送過來的消息
\t\tSystem.out.println("客戶端消息:"+in.readUTF());
\t\t// 獲取服務端socket的輸出流,服務端端通過這個輸出流給客戶端傳遞消息
\t\tDataOutputStream out = new DataOutputStream(socket.getOutputStream());
\t\t// 通過服務端socket的輸出流,給客戶端發送消息
\t\tout.writeUTF("Hello,jvxb, nice to meet you!Here is Server。");
\t\t// 關閉服務端socket。
\t\tsocket.close();
\t\t// 關閉監聽
\t\tserverSocket.close();
\t}

}

第三步:建客戶端類

然後建一個客戶端類:HelloClient,代碼如下:

package socket;

import java.io.*;
import java.net.*;

public class HelloClient {

\t// 需連接的服務端IP或域名,此例中本機即為服務端。一般都是通過配置文件來設置。
\tprivate static String serverName = "127.0.0.1";
\t// 需連接的服務端端口
\tprivate static int port = 8888;

\tpublic static void main(String[] args) throws Exception {
\t\t// 通過指定服務端IP、服務端端口,連接到服務端,連接成功後獲得客戶端socket.
\t\tSocket clientSocket = new Socket(serverName, port);
\t\t// 通過客戶端socket,獲得客戶端輸出流。
\t\tDataOutputStream out = new DataOutputStream(clientSocket.getOutputStream());
\t\t// 通過客戶端輸出流,向服務端發送消息。
\t\tout.writeUTF("Hello,I am jvxb! Here is Client.");
\t\t// 通過客戶端輸出流,讀取服務端發送過來的消息。
\t\tDataInputStream in = new DataInputStream(clientSocket.getInputStream());
\t\t// 輸出服務端發送過來的消息
\t\tSystem.out.println("服務器響應: " + in.readUTF());
\t\t// 關閉客戶端socket
\t\tclientSocket.close();
\t}
}

第四步:測試

1)運行服務端類

2)運行客戶端類

3)查看輸出結果

可以看到結果如下:

分佈式專題(2)- 分佈式 Java通信

通過以上的例子我們可以看到,只需要簡單的幾行Java代碼,通過Java API我們就能夠實現基於TCP協議的客戶端/服務端通信。同理,通過DatagramSocket對象也能很快速地實現基於UDP協議的客戶端/服務端通信,此處不再展開。當然,我們上面舉的例子只是最基礎的。一般來說服務端不會只與一個客戶端連接,服務端需要監聽多個客戶端連接的話,就得讓accept()方法在while中持續循環,所以服務端的代碼一般都是配合多線程來使用,傳統做法是一個客戶端連接過來就開一個線程去單獨處理,這種處理是比較簡單容易實現,但很明顯客戶端連接一多,性能方面就跟不上了,因為光是線程的切換開銷就挺大的,更不用說每個線程都會佔用挺大的資源。那要怎麼解決性能的問題呢?功力深厚者,可以自己去設計出自己的一套東西去解決,像小兵我這種水平未到家的,我覺得用人家東西也挺不錯的。。比如我們可以直接使用Netty框架。

私信我:“學習”,可免費領:java,架構,大數據,AI等相關學習資料 (免費的哦)。

"

Java實現系統間的通信概覽

我們知道,所謂分佈式,無非就是“將一個系統拆分成多個子系統並散佈到不同設備”的過程而已。在微服務的大潮之中, 我們把系統拆分成了多個服務,根據需要部署在多個機器上,這些服務非常靈活,可以隨著訪問量彈性擴展。本質上而言,實現一個分佈式系統,最核心的部分無非有兩點:

如何拆分——可以有很多方式,核心依據一是業務需求,二是成本限制。這是實踐中構建分佈式系統時最主要的設計依據。

如何連接——光把系統拆開成各個子系統還不夠,關鍵是拆開後的各個子系統之間還要能通信,因此涉及通信協議設計的問題,需要考慮的因素很多,好消息是這部分其實成熟方案很多。

分佈式系統並非靈丹妙藥,解決問題的關鍵還是看你對問題本身的瞭解。通常我們需要使用分佈式的常見理由是:

為了性能擴展——系統負載高,單臺機器無法承載,希望通過使用多臺機器來提高系統的負載能力。

為了增強可靠性——軟件不是完美的,網絡不是完美的,甚至機器本身也不可能是完美的,隨時可能會出錯,為了避免故障,需要將業務分散開保留一定的冗餘度。

本篇要講的是分佈式應用中解決“如何連接”的問題,即Java是如何實現系統間的通信的。先上一張總圖:

分佈式專題(2)- 分佈式 Java通信

上圖中,我們看到圖片左邊的【網絡通信】,是由協議和網絡IO組成。協議如TCP/IP等在上一篇文章中已經介紹過,多出的Multicast(組播)此處也不再延伸介紹,有需要的同學另外自行了解即可。上一篇文章在介紹傳輸層的TCP協議時,已經提到了“TCP提供全雙工通信,會話雙方都可以同時接收和發送數據。都設有接收緩存和發送緩存,用來臨時存放雙向通信的數據”。發送緩存也就是寫緩存,接收緩存也就是讀緩存。在客戶端與服務器經過三次握手建立連接後,在二者之間就相當於打開了一條可以互相傳送數據的道路,道路的兩端就是各自的讀寫緩存和我們所說的套接字Socket,每一個socket都有一個輸出流和一個輸入流。這種跨越網絡的數據IO流,就是我們說的網絡IO。然後可以看到網絡IO還分為了BIO、NIO和AIO,這個我們可以先不管,後面我會再細說。所以TCP連接差不多就是下圖這個樣子。

分佈式專題(2)- 分佈式 Java通信

在瞭解了Socket和網絡IO的含義之後,我們看回第一張圖的右邊,可以看到Java實現系統間的通信方式有基於Java API、基於開源框架、基於遠程通信技術等。下面,我們用Java代碼來一起實現一下這幾種方式。

Socket:socket本身並不是協議,它是應用層與TCP/IP協議族通信的中間軟件抽象層,是一組調用接口(TCP/IP網絡的API函數)。可以看做是對TCP/IP協議的封裝,它把複雜的TCP/IP協議族隱藏在Socket接口後面,它的出現只是使得程序員更方便地使用TCP/IP協議棧而已。

基於Java API

java.net 包中的 API 包含有網絡編程相關的類和接口。java.net 包中能夠找到對TCP協議、UDP協議、Multicast協議的支持。我們仍以基於TCP協議的網絡編程為例。

在編程開始前,我們再次簡單回顧一下計算機網絡中的傳輸層和TCP協議。

傳輸層為應用進程之間提供端口到端口的通信

TCP提供全雙工通信,會話雙方都可以同時接收和發送數據。

(在看API的具體實現之前,思考一個有意思的問題:如果是交給你去實現客戶端與服務器的通信,你會設計多少個對象?如何設計它們的關係?如何做到面向對象設計?多看,多想,多換位思考,如果是你的話,你怎麼處理,這是對提高自己水平很有裨益的事,無論是做人還是做事。)

官方文檔提到:以下步驟在兩臺計算機之間使用套接字建立TCP連接時會出現:

  • 服務器實例化一個 ServerSocket 對象,表示通過服務器上的端口通信。
  • 服務器調用 ServerSocket 類的 accept() 方法,該方法將一直等待,直到客戶端連接到服務器上給定的端口。
  • 服務器正在等待時,一個客戶端實例化一個 Socket 對象,指定服務器名稱和端口號來請求連接。
  • Socket 類的構造函數試圖將客戶端連接到指定的服務器和端口號。如果通信被建立,則在客戶端創建一個 Socket 對象能夠與服務器進行通信。
  • 在服務器端,accept() 方法返回服務器上一個新的 socket 引用,該 socket 連接到客戶端的 socket。
  • 連接建立後,通過使用 I/O 流在進行通信,每一個socket都有一個輸出流和一個輸入流,客戶端的輸出流連接到服務器端的輸入流,而客戶端的輸入流連接到服務器端的輸出流。

上述流程有空就多看幾遍,我們後面講的所有通信都是基於上述流程。各種技術和框架不過是對這些流程不斷封裝、抽象、擴展而已,但是主流程仍是不變的。

現在,打開我們的IDEA或者Eclipse,按照API中的實現步驟,一起來實現下面的小目標。

(終於要回到我們熟悉的代碼部分了,Code Time Begin !)

小目標:對基於Java API的網絡編程有初步的瞭解。具體需求如下:

1)從客戶端把“Hello, I am xxx. Here is Client.”這條消息傳送給服務端;

2)從服務端讀取該消息,並給客戶端返回響應消息:“Hello, xxx, nice to meet you! Here is Server.”

我們可以按照以下步驟實現上述需求:

第一步:建項目

我們先新建一個項目distributed,再建一個名為mysocket的包。為了以後方便添加Jar包,我們建的是maven項目。

第二步:建服務端類

然後建一個服務端類:HelloServer,代碼如下:

package socket;

import java.io.*;
import java.net.*;

public class HelloServer {
\t// 選擇一個端口作為服務端端口
\tprivate static int port = 8888;

\tpublic static void main(String[] args) throws Exception {
\t\t// 創建ServerSocket對象,相當於在服務端(本機)打開一個監聽
\t\tServerSocket serverSocket = new ServerSocket(port);
\t\tSystem.out.println("開始監聽端口:" + port + "...");
\t\t// accept方法阻塞等待客戶端連接,連接成功後得到socket對象
\t\tSocket socket = serverSocket.accept();
\t\t// 獲取服務端socket的輸入流,客戶端通過這個輸入流給服務端傳遞消息
\t\tDataInputStream in = new DataInputStream(socket.getInputStream());
\t\t// 通過服務端socket的輸入流,輸出客戶端發送過來的消息
\t\tSystem.out.println("客戶端消息:"+in.readUTF());
\t\t// 獲取服務端socket的輸出流,服務端端通過這個輸出流給客戶端傳遞消息
\t\tDataOutputStream out = new DataOutputStream(socket.getOutputStream());
\t\t// 通過服務端socket的輸出流,給客戶端發送消息
\t\tout.writeUTF("Hello,jvxb, nice to meet you!Here is Server。");
\t\t// 關閉服務端socket。
\t\tsocket.close();
\t\t// 關閉監聽
\t\tserverSocket.close();
\t}

}

第三步:建客戶端類

然後建一個客戶端類:HelloClient,代碼如下:

package socket;

import java.io.*;
import java.net.*;

public class HelloClient {

\t// 需連接的服務端IP或域名,此例中本機即為服務端。一般都是通過配置文件來設置。
\tprivate static String serverName = "127.0.0.1";
\t// 需連接的服務端端口
\tprivate static int port = 8888;

\tpublic static void main(String[] args) throws Exception {
\t\t// 通過指定服務端IP、服務端端口,連接到服務端,連接成功後獲得客戶端socket.
\t\tSocket clientSocket = new Socket(serverName, port);
\t\t// 通過客戶端socket,獲得客戶端輸出流。
\t\tDataOutputStream out = new DataOutputStream(clientSocket.getOutputStream());
\t\t// 通過客戶端輸出流,向服務端發送消息。
\t\tout.writeUTF("Hello,I am jvxb! Here is Client.");
\t\t// 通過客戶端輸出流,讀取服務端發送過來的消息。
\t\tDataInputStream in = new DataInputStream(clientSocket.getInputStream());
\t\t// 輸出服務端發送過來的消息
\t\tSystem.out.println("服務器響應: " + in.readUTF());
\t\t// 關閉客戶端socket
\t\tclientSocket.close();
\t}
}

第四步:測試

1)運行服務端類

2)運行客戶端類

3)查看輸出結果

可以看到結果如下:

分佈式專題(2)- 分佈式 Java通信

通過以上的例子我們可以看到,只需要簡單的幾行Java代碼,通過Java API我們就能夠實現基於TCP協議的客戶端/服務端通信。同理,通過DatagramSocket對象也能很快速地實現基於UDP協議的客戶端/服務端通信,此處不再展開。當然,我們上面舉的例子只是最基礎的。一般來說服務端不會只與一個客戶端連接,服務端需要監聽多個客戶端連接的話,就得讓accept()方法在while中持續循環,所以服務端的代碼一般都是配合多線程來使用,傳統做法是一個客戶端連接過來就開一個線程去單獨處理,這種處理是比較簡單容易實現,但很明顯客戶端連接一多,性能方面就跟不上了,因為光是線程的切換開銷就挺大的,更不用說每個線程都會佔用挺大的資源。那要怎麼解決性能的問題呢?功力深厚者,可以自己去設計出自己的一套東西去解決,像小兵我這種水平未到家的,我覺得用人家東西也挺不錯的。。比如我們可以直接使用Netty框架。

私信我:“學習”,可免費領:java,架構,大數據,AI等相關學習資料 (免費的哦)。

分佈式專題(2)- 分佈式 Java通信

"

Java實現系統間的通信概覽

我們知道,所謂分佈式,無非就是“將一個系統拆分成多個子系統並散佈到不同設備”的過程而已。在微服務的大潮之中, 我們把系統拆分成了多個服務,根據需要部署在多個機器上,這些服務非常靈活,可以隨著訪問量彈性擴展。本質上而言,實現一個分佈式系統,最核心的部分無非有兩點:

如何拆分——可以有很多方式,核心依據一是業務需求,二是成本限制。這是實踐中構建分佈式系統時最主要的設計依據。

如何連接——光把系統拆開成各個子系統還不夠,關鍵是拆開後的各個子系統之間還要能通信,因此涉及通信協議設計的問題,需要考慮的因素很多,好消息是這部分其實成熟方案很多。

分佈式系統並非靈丹妙藥,解決問題的關鍵還是看你對問題本身的瞭解。通常我們需要使用分佈式的常見理由是:

為了性能擴展——系統負載高,單臺機器無法承載,希望通過使用多臺機器來提高系統的負載能力。

為了增強可靠性——軟件不是完美的,網絡不是完美的,甚至機器本身也不可能是完美的,隨時可能會出錯,為了避免故障,需要將業務分散開保留一定的冗餘度。

本篇要講的是分佈式應用中解決“如何連接”的問題,即Java是如何實現系統間的通信的。先上一張總圖:

分佈式專題(2)- 分佈式 Java通信

上圖中,我們看到圖片左邊的【網絡通信】,是由協議和網絡IO組成。協議如TCP/IP等在上一篇文章中已經介紹過,多出的Multicast(組播)此處也不再延伸介紹,有需要的同學另外自行了解即可。上一篇文章在介紹傳輸層的TCP協議時,已經提到了“TCP提供全雙工通信,會話雙方都可以同時接收和發送數據。都設有接收緩存和發送緩存,用來臨時存放雙向通信的數據”。發送緩存也就是寫緩存,接收緩存也就是讀緩存。在客戶端與服務器經過三次握手建立連接後,在二者之間就相當於打開了一條可以互相傳送數據的道路,道路的兩端就是各自的讀寫緩存和我們所說的套接字Socket,每一個socket都有一個輸出流和一個輸入流。這種跨越網絡的數據IO流,就是我們說的網絡IO。然後可以看到網絡IO還分為了BIO、NIO和AIO,這個我們可以先不管,後面我會再細說。所以TCP連接差不多就是下圖這個樣子。

分佈式專題(2)- 分佈式 Java通信

在瞭解了Socket和網絡IO的含義之後,我們看回第一張圖的右邊,可以看到Java實現系統間的通信方式有基於Java API、基於開源框架、基於遠程通信技術等。下面,我們用Java代碼來一起實現一下這幾種方式。

Socket:socket本身並不是協議,它是應用層與TCP/IP協議族通信的中間軟件抽象層,是一組調用接口(TCP/IP網絡的API函數)。可以看做是對TCP/IP協議的封裝,它把複雜的TCP/IP協議族隱藏在Socket接口後面,它的出現只是使得程序員更方便地使用TCP/IP協議棧而已。

基於Java API

java.net 包中的 API 包含有網絡編程相關的類和接口。java.net 包中能夠找到對TCP協議、UDP協議、Multicast協議的支持。我們仍以基於TCP協議的網絡編程為例。

在編程開始前,我們再次簡單回顧一下計算機網絡中的傳輸層和TCP協議。

傳輸層為應用進程之間提供端口到端口的通信

TCP提供全雙工通信,會話雙方都可以同時接收和發送數據。

(在看API的具體實現之前,思考一個有意思的問題:如果是交給你去實現客戶端與服務器的通信,你會設計多少個對象?如何設計它們的關係?如何做到面向對象設計?多看,多想,多換位思考,如果是你的話,你怎麼處理,這是對提高自己水平很有裨益的事,無論是做人還是做事。)

官方文檔提到:以下步驟在兩臺計算機之間使用套接字建立TCP連接時會出現:

  • 服務器實例化一個 ServerSocket 對象,表示通過服務器上的端口通信。
  • 服務器調用 ServerSocket 類的 accept() 方法,該方法將一直等待,直到客戶端連接到服務器上給定的端口。
  • 服務器正在等待時,一個客戶端實例化一個 Socket 對象,指定服務器名稱和端口號來請求連接。
  • Socket 類的構造函數試圖將客戶端連接到指定的服務器和端口號。如果通信被建立,則在客戶端創建一個 Socket 對象能夠與服務器進行通信。
  • 在服務器端,accept() 方法返回服務器上一個新的 socket 引用,該 socket 連接到客戶端的 socket。
  • 連接建立後,通過使用 I/O 流在進行通信,每一個socket都有一個輸出流和一個輸入流,客戶端的輸出流連接到服務器端的輸入流,而客戶端的輸入流連接到服務器端的輸出流。

上述流程有空就多看幾遍,我們後面講的所有通信都是基於上述流程。各種技術和框架不過是對這些流程不斷封裝、抽象、擴展而已,但是主流程仍是不變的。

現在,打開我們的IDEA或者Eclipse,按照API中的實現步驟,一起來實現下面的小目標。

(終於要回到我們熟悉的代碼部分了,Code Time Begin !)

小目標:對基於Java API的網絡編程有初步的瞭解。具體需求如下:

1)從客戶端把“Hello, I am xxx. Here is Client.”這條消息傳送給服務端;

2)從服務端讀取該消息,並給客戶端返回響應消息:“Hello, xxx, nice to meet you! Here is Server.”

我們可以按照以下步驟實現上述需求:

第一步:建項目

我們先新建一個項目distributed,再建一個名為mysocket的包。為了以後方便添加Jar包,我們建的是maven項目。

第二步:建服務端類

然後建一個服務端類:HelloServer,代碼如下:

package socket;

import java.io.*;
import java.net.*;

public class HelloServer {
\t// 選擇一個端口作為服務端端口
\tprivate static int port = 8888;

\tpublic static void main(String[] args) throws Exception {
\t\t// 創建ServerSocket對象,相當於在服務端(本機)打開一個監聽
\t\tServerSocket serverSocket = new ServerSocket(port);
\t\tSystem.out.println("開始監聽端口:" + port + "...");
\t\t// accept方法阻塞等待客戶端連接,連接成功後得到socket對象
\t\tSocket socket = serverSocket.accept();
\t\t// 獲取服務端socket的輸入流,客戶端通過這個輸入流給服務端傳遞消息
\t\tDataInputStream in = new DataInputStream(socket.getInputStream());
\t\t// 通過服務端socket的輸入流,輸出客戶端發送過來的消息
\t\tSystem.out.println("客戶端消息:"+in.readUTF());
\t\t// 獲取服務端socket的輸出流,服務端端通過這個輸出流給客戶端傳遞消息
\t\tDataOutputStream out = new DataOutputStream(socket.getOutputStream());
\t\t// 通過服務端socket的輸出流,給客戶端發送消息
\t\tout.writeUTF("Hello,jvxb, nice to meet you!Here is Server。");
\t\t// 關閉服務端socket。
\t\tsocket.close();
\t\t// 關閉監聽
\t\tserverSocket.close();
\t}

}

第三步:建客戶端類

然後建一個客戶端類:HelloClient,代碼如下:

package socket;

import java.io.*;
import java.net.*;

public class HelloClient {

\t// 需連接的服務端IP或域名,此例中本機即為服務端。一般都是通過配置文件來設置。
\tprivate static String serverName = "127.0.0.1";
\t// 需連接的服務端端口
\tprivate static int port = 8888;

\tpublic static void main(String[] args) throws Exception {
\t\t// 通過指定服務端IP、服務端端口,連接到服務端,連接成功後獲得客戶端socket.
\t\tSocket clientSocket = new Socket(serverName, port);
\t\t// 通過客戶端socket,獲得客戶端輸出流。
\t\tDataOutputStream out = new DataOutputStream(clientSocket.getOutputStream());
\t\t// 通過客戶端輸出流,向服務端發送消息。
\t\tout.writeUTF("Hello,I am jvxb! Here is Client.");
\t\t// 通過客戶端輸出流,讀取服務端發送過來的消息。
\t\tDataInputStream in = new DataInputStream(clientSocket.getInputStream());
\t\t// 輸出服務端發送過來的消息
\t\tSystem.out.println("服務器響應: " + in.readUTF());
\t\t// 關閉客戶端socket
\t\tclientSocket.close();
\t}
}

第四步:測試

1)運行服務端類

2)運行客戶端類

3)查看輸出結果

可以看到結果如下:

分佈式專題(2)- 分佈式 Java通信

通過以上的例子我們可以看到,只需要簡單的幾行Java代碼,通過Java API我們就能夠實現基於TCP協議的客戶端/服務端通信。同理,通過DatagramSocket對象也能很快速地實現基於UDP協議的客戶端/服務端通信,此處不再展開。當然,我們上面舉的例子只是最基礎的。一般來說服務端不會只與一個客戶端連接,服務端需要監聽多個客戶端連接的話,就得讓accept()方法在while中持續循環,所以服務端的代碼一般都是配合多線程來使用,傳統做法是一個客戶端連接過來就開一個線程去單獨處理,這種處理是比較簡單容易實現,但很明顯客戶端連接一多,性能方面就跟不上了,因為光是線程的切換開銷就挺大的,更不用說每個線程都會佔用挺大的資源。那要怎麼解決性能的問題呢?功力深厚者,可以自己去設計出自己的一套東西去解決,像小兵我這種水平未到家的,我覺得用人家東西也挺不錯的。。比如我們可以直接使用Netty框架。

私信我:“學習”,可免費領:java,架構,大數據,AI等相關學習資料 (免費的哦)。

分佈式專題(2)- 分佈式 Java通信

分佈式專題(2)- 分佈式 Java通信

"

相關推薦

推薦中...