初步接觸 Java Net 網絡編程

本文目的是大概了解 Java 網絡編程體系,需要一點點 Java IO 基礎,推薦教程 系統學習 Java IO。主要參考 JavaDoc 和 Jakob Jenkov 的英文教程《Java Networking》 http://tutorials.jenkov.com/java-networking/index.html

Java 網絡編程概覽

Java 有一個相當容易使用的內置網絡 API,可以很容易地通過互聯網上的 TCP / IP 套接字或 UDP 套接字進行通信。 TCP 通常比 UDP 使用得更頻繁。

即使 Java Networking API 允許通過套接字打開和關閉網絡連接,但所有通信都通過 Java IO 類 InputStream 和 OutputStream 實現的。
或者,我們可以使用 Java NIO API 中的網絡類。 用法類似于 Java Networking API 中的類,但 Java NIO API 可以在非阻塞模式下工作。 在某些情況下,非阻塞模式可提升性能。

Java TCP 網絡基礎

通常,客戶端會打開與服務器的 TCP / IP 連接,然后開始與服務器通信,當通信結束后客戶端關閉連接。如下圖:


image

客戶端可以通過一個已打開的連接發送多個請求,實際上,客戶端可以向服務器發送盡可能多的數據。 當然,如果需要,服務器也可以關閉連接。

Java 中 Socket 類和 ServerSocket 類

當客戶端想要打開到服務器的 TCP / IP 連接時,它使用 Java Socket 類來實現。 套接字被告知連接到哪個 IP 地址和 TCP 端口,其余部分由 Java 完成。

如果要啟動服務器以偵聽來自某個 TCP 端口上的客戶端的傳入連接,則必須使用 Java ServerSocket 類。 當客戶端通過客戶端套接字連接到服務器的 ServerSocket 時,服務器上會為該連接分配一個 Socket 。 客戶端和服務器的通信就是 Socket 到 Socket 的通信了。

Socket和ServerSocket在后面的文本中有更詳細的介紹。

Java UDP 網絡基礎

UDP 的工作方式與 TCP 略有不同。 使用 UDP ,客戶端和服務器之間沒有連接。 客戶端可以向服務器發送數據,并且服務器可以(或可以不)接收該數據。 客戶端永遠不會知道數據是否在另一端收到。 從服務器到客戶端發送的數據也是如此。
由于無法保證數據傳輸,因此 UDP 協議的協議開銷較小。

在一些情況下,無連接 UDP 模型優于 TCP ,比如傳輸視頻等多媒體文件,缺少一些數據是不影響觀看的。

TCP Socket(套接字)

為了通過 Internet 連接到服務器(通過TCP / IP),需要創建一個 Socket 并將其連接到服務器。 或者,如果您更喜歡使用 Java NIO ,則可以使用 Java NIO SocketChannel 。

創建一個Socket
Socket socket = new Socket("baidu.com", 80);

第一個參數是地址,可以是 ip 或者域名字符串,第二個參數是端口,端口80是Web服務器端口。

寫入 Socket

要寫入 Socket,必須獲取其 OutputStream :

Socket socket = new Socket("baidu.com", 80);
OutputStream outputStream = socket.getOutputStream();

outputStream.write("some data".getBytes());
outputStream.flush();
outputStream.close();

socket.close();

當真的希望通過互聯網向服務器發送數據時,不要忘記調用 flush() 。操作系統中的底層 TCP / IP 實現會先緩沖數據,緩沖塊的大小是與 TCP ??/ IP 數據包的大小相適應的,這就是說,調用 flush() 只是通知系統發送,但系統并不是立即就幫忙發出去。

從 Socket 讀取

要從 Socket 讀取,需要獲取其 InputStream :

Socket socket = new Socket("baidu.com", 80);
InputStream in = socket.getInputStream();

int data = in.read();
//... read more data...

in.close();
socket.close();

記住,在讀取時我們不能使用讀取 InputStream 返回 -1 來判斷數據讀取結束 ,因為只有在服務器關閉連接時才返回 -1 。 但是服務器可能并不總是關閉連接,比如通過同一連接發送多個請求。 在這種情況下,關閉連接將是非常愚蠢的。

相反,必須知道從 Socket 的 InputStream 中讀取多少字節。 服務器會告知 Socket 它發送的字節數,或者通過查找特殊的數據結束字符來完成。

使用 Socket 后,必須關閉它以關閉與服務器的連接,這可以通過調用 Socket 對象的 close() 方法完成。

ServerSocket

可以使用 ServerSocket 來實現 Java 服務器,這樣就可以通過 TCP / IP 偵聽來自客戶端的傳入連接。如果更喜歡使用 Java NIO 而不是 Java Networking(標準API),那么也可以使用 ServerSocketChannel 。

創建一個 ServerSocket

這是一個簡單的代碼示例,它創建一個偵聽端口 9000 的 ServerSocket:

ServerSocket serverSocket = new ServerSocket(9000);
監聽傳入的連接

要接受傳入連接,必須調用 ServerSocket.accept() 方法。 accept() 方法返回一個 Socket ,其行為類似于普通的 Socket ,示例:

ServerSocket serverSocket = new ServerSocket(9000);
boolean isStopped = false;
while(!isStopped){
    Socket clientSocket = serverSocket.accept();
    //do something with clientSocket
}

每次調用 accept() 方法時只打開一個傳入連接。
此外,只有在運行服務器的線程調用 accept() 時才能接受傳入連接。 線程在此方法之外執行的所有時間都沒有客戶端可以連接。 因此,“accept”線程通常將傳入連接(Socket)傳遞給工作線程池,然后工作線程與客戶端進行通信。 有關多線程服務器設計的更多信息,請參閱教程跟蹤 Java 多線程服務器。

關閉客戶端 Sockets

一旦客戶端請求完成,并且不會從該客戶端收到進一步的請求,必須關閉該Socket,就像關閉普通客戶端Socket一樣。調用:socket.close();

關閉服務端 Sockets

一旦服務器關閉,就需要關閉 ServerSocket 。 調用:serverSocket.close();

UDP DatagramSocket(UDP數據報套接字)

DatagramSocket 是 Java 通過 UDP 而不是 TCP 進行網絡通信的機制。 UDP 也是 IP 協議的上層。 可以使用 DatagramSocket 來發送和接收 UPD 數據報。

UDP 對比 TCP

通過 TCP 發送數據時,首先要創建連接。 建立 TCP 連接后,TCP 保證數據到達另一端,或者它會告訴你發生了錯誤。

使用 UDP,只需將數據包(數據報)發送到網絡上的某個 IP 地址。 無法保證數據會到達,也無法保證 UDP 數據包到達的順序。 這意味著 UDP 比 TCP 具有更少的協議開銷(沒有流完整性檢查)。

UDP 適用于數據傳輸,如果數據包在轉換過程中丟失則無關緊要。 例如,想象一下通過互聯網傳輸直播電視信號,如果一兩幀丟失,這是無關緊要的。我們更不希望直播延遲只是為了確保所有幀都顯示出來。 寧愿跳過錯過的幀,并直接查看最新的幀。

還有實時監控視頻,寧愿丟失一兩幀,也不想延遲于現實 30 秒。與攝像機錄像的存儲有點不同,將圖像從相機錄制到磁盤時, 為了保證完整性,可能不希望丟失單幀,而是更愿意稍微延遲。

DatagramPacket 類

此類表示數據報包。數據報包用來實現無連接包投遞服務。

Java 使用 DatagramSocket 代表 UDP 協議的 Socket ,DatagramSocket 本身只是碼頭,不維護狀態,不能產生IO流,它的唯一作用就是接收和發送數據報,使用 DatagramPacket 來代表數據報,DatagramSocket 接收和發送的數據都是通過 DatagramPacket 對象完成的。
每條報文僅根據該包中包含的信息從一臺機器路由到另一臺機器。從一臺機器發送到另一臺機器的多個包可能選擇不同的路由,也可能按不同的順序到達。不對包投遞做出保證。
引用自 李剛《瘋狂Java講義(第2版)》

其所有構造器如下:

方法 描述
DatagramPacket(byte[] buf, int length) 構造 DatagramPacket,用來接收長度為 length 的數據包。
DatagramPacket(byte[] buf, int length, InetAddress address, int port) 構造數據報包,用來將長度為 length 的包發送到指定主機上的指定端口號。
DatagramPacket(byte[] buf, int length, SocketAddress address) 構造數據報包,用來將長度為 length 的包發送到指定主機上的指定端口號。
以上3個在 byte[] buf 參數后面追加 int offset 為長度為 length 的包設置偏移量為 offset

其中

  • InetAddress 類表示互聯網協議 (IP) 地址,可以通過靜態方法 getByName(String host) 獲得其對象。
  • SocketAddress 類里面什么都沒有。其子類 InetSocketAddress是(IP地址+端口號)類型,也就是端口地址類型,同樣可以使用靜態方法 createUnresolved(String host, int port) 獲取對象,另外也能由構造函數 InetSocketAddress(InetAddress addr, int port) 創建,其中 InetAddress 對象可省略,也可用字符串代替。
通過 DatagramSocket 發送數據(DatagramPacket )

要通過 DatagramSocket 發送數據,必須首先創建一個 DatagramPacket :

byte[] buffer = new byte[65508];
InetAddress address = InetAddress.getByName("baidu.com");

DatagramPacket packet = new DatagramPacket(buffer, buffer.length, address, 9000);

字節緩沖區(字節數組)是要在 UDP 數據報中發送的數據。 上述緩沖區的長度(65508字節)是可以在單個 UDP 數據包中發送的最大數據量。

DatagramPacket 構造函數中的 buffer.length 是要發送的緩沖區中數據的長度,忽略該數據量之后緩沖區中的所有數據。

InetAddress 實例包含發送 UDP 數據包的節點(例如服務器)的地址。 InetAddress 類表示 IP 地址(Internet地址)。 getByName() 方法返回一個 InetAddress 實例,其 IP 地址與給定的主機名匹配。

port 參數是服務器接收數據正在偵聽的 UDP 端口,UDP 和 TCP 端口是不一樣的。同一臺計算機可以有不同的線程同時監聽 UDP 的 80 端口和 TCP 中的 80 端口。不同協議下,端口號互不干擾,端口只是應用程序的標識。

創建一個 DatagramSocket :

DatagramSocket datagramSocket = new DatagramSocket();

要發送數據,請調用 send() 方法,如下所示:

datagramSocket.send(packet);

這是一個完整的例子:


public class DatagramExample {
    public static void main(String[] args) throws Exception {
        DatagramSocket datagramSocket = new DatagramSocket();

        byte[] buffer = "123456789".getBytes();
        InetAddress receiverAddress = InetAddress.getLocalHost();

        DatagramPacket packet = new DatagramPacket(buffer, buffer.length, receiverAddress, 80);
        datagramSocket.send(packet);
    }
}
通過 DatagramSocket 接收數據 (DatagramPacket )

通過 DatagramSocket 接收數據是通過首先創建 DatagramPacket 然后通過 DatagramSocket 的 receive() 方法接收數據來完成的。 這是一個例子:

DatagramSocket socket = new DatagramSocket(80);
byte[] buffer = new byte[10];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

socket.receive(packet);

使用傳遞給構造函數的參數值 80 來實例化 DatagramSocket , 此參數是 DatagramSocket 接收 UDP 數據包的 UDP 端口。 如前所述,TCP 和 UDP 端口不相同,因此不重疊。 可以在 TCP 和 UDP 80 端口上偵聽兩個不同的進程,而不會發生任何沖突。

其次,創建字節緩沖區和 DatagramPacket 。 注意 DatagramPacket 沒有關于要發送數據的節點的信息,就像創建 DatagramPacket 用于發送數據時一樣。 這是因為我們將使用 DatagramPacket 接收數據而不是發送數據,因此,不需要目標地址。

最后調用 DatagramSocket 的 receive() 方法。 此方法將一直阻塞,直到收到 DatagramPacket 。

收到的數據位于 DatagramPacket 的字節緩沖區中。 這個緩沖區可以通過調用如下代碼獲取:

byte[] buffer = packet.getData();

緩沖區會接收多少數據應該由你找到答案。 正在使用的協議應指定每個 UDP 數據包發送的數據量,或指定可以查找到的數據結束標記。真正的服務器程序可能會在循環中調用 receive() 方法,并將所有收到的 DatagramPacket 傳遞給工作線程池,就像 TCP 服務器對傳入連接一樣。

URL + URLConnection

java.net 包中兩個有趣的類:URL 類和 URLConnection 類,這些類可用于創建與 Web 服務器(HTTP 服務器)的客戶端連接。 這是一個簡單的代碼示例:

public class URLExample {
    public static void main(String[] args) throws IOException {
        URL url = new URL("http://baidu.com");

        URLConnection urlConnection = url.openConnection();
        InputStream inputStream = urlConnection.getInputStream();

        int data = inputStream.read();
        while (data != -1) {
            System.out.print((char) data);
            data = inputStream.read();
        }
        inputStream.close();
    }
}

將會輸出

<html>
<meta http-equiv="refresh" content="0;url=http://www.baidu.com/">
</html>
HTTP GET 和 POST

URLConnection 類的作用是構造一個到指定 URL 的 URL 連接。它只有一個構造函數:URLConnection(URL url) 。
默認情況下,URLConnection 向 Web 服務器發送 HTTP GET 請求,即查詢數據。如果要發送 HTTP POST 請求提交數據,請調用URLConnection.setDoOutput(true) 方法,如下所示:

URL url = new URL("http://baidu.com");
URLConnection urlConnection = url.openConnection();
urlConnection.setDoOutput(true);

一旦設置了 setDoOutput(true) ,因為要提交數據,所以需要輸出流。可以打開 URLConnection 的 OutputStream ,如下所示:

OutputStream output = urlConnection.getOutputStream();

使用此 OutputStream ,可以在 HTTP 請求的正文中編寫所需的任何數據。 請記住對其進行 URL 編碼(參考 【基礎進階】URL詳解與URL編碼 ,并記得在完成向其寫入數據后關閉 OutputStream 。

本地文件的URL

URL 類還可用于訪問本地文件系統中的文件。 因此,如果需要代碼處理來源不明的文件,比如是來自網絡還是本地文件系統,則 URL 類是打開文件的便捷方式。
以下是使用 URL 類在本地文件系統中打開文件的示例:

URL url = new URL("file:/D:/test/test.txt");

URLConnection urlConnection = url.openConnection();
InputStream input = urlConnection.getInputStream();

int data = input.read();
while(data != -1){
    System.out.print((char) data);
    data = input.read();
}
input.close();

請注意,這和通過 HTTP 訪問 Web 服務器上的文件的唯一區別是 URL :"file:/D:/test/test.txt""http://baidu.com"

JarURLConnection

JarURLConnection 類用于連接 Java Jar 文件。 連接后可以獲取有關 Jar 文件內容的信息。 這是一個簡單的例子:

String urlString = "http://butterfly.jenkov.com/"
                 + "container/download/"
                 + "jenkov-butterfly-container-2.9.9-beta.jar";

URL jarUrl = new URL(urlString);
JarURLConnection connection = new JarURLConnection(jarUrl);

Manifest manifest = connection.getManifest();
JarFile jarFile = connection.getJarFile();
//do something with Jar file...
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,892評論 2 372

推薦閱讀更多精彩內容

  • 計算機網絡概述 網絡編程的實質就是兩個(或多個)設備(例如計算機)之間的數據傳輸。 按照計算機網絡的定義,通過一定...
    蛋炒飯_By閱讀 1,235評論 0 10
  • 網絡編程 網絡編程對于很多的初學者來說,都是很向往的一種編程技能,但是很多的初學者卻因為很長一段時間無法進入網絡編...
    程序員歐陽閱讀 2,032評論 1 37
  • 7.2 面向套接字編程我們已經通過了解Socket的接口,知其所以然,下面我們就將通過具體的案例,來熟悉Socke...
    lucas777閱讀 1,194評論 0 2
  • 一、網絡通信協議 定義:對數據的傳輸格式、傳輸效率、傳輸步驟等做了統一規定,通信雙方必須同時遵守才能完成數據交換,...
    聶叼叼閱讀 519評論 0 2
  • 目標:我可以輕松的在2019年1月份以前支付銀行2-3萬元,然后2019年以后的每一個月的工資,將都是完完全全的屬...
    殷琴閱讀 143評論 0 2