注*部分轉來的
第1章 網絡通信協議
通過計算機網絡可以使多台計算機實現連接,位於同一個網絡中的計算機在進行連接和通信時需要遵守一定的規則,這就好比在道路中行駛的汽車一定要遵守交通規則一樣。在計算機網絡中,這些連接和通信的規則被稱為網絡通信協議,它對數據的傳輸格式、傳輸速率、傳輸步驟等做了統一規定,通信雙方必須同時遵守才能完成數據交換。
網絡通信協議有很多種,目前應用最廣泛的是TCP/IP協議(Transmission Control Protocal/Internet Protoal傳輸控制協議/英特網互聯協議),它是一個包括TCP協議和IP協議,UDP(User Datagram Protocol)協議和其它一些協議的協議組,在學習具體協議之前首先了解一下TCP/IP協議組的層次結構。
在進行數據傳輸時,要求發送的數據與收到的數據完全一樣,這時,就需要在原有的數據上添加很多信息,以保證數據在傳輸過程中數據格式完全一致。TCP/IP協議的層次結構比較簡單,共分為四層,如圖所示。
上圖中,TCP/IP協議中的四層分別是應用層、傳輸層、網絡層和鏈路層,每層分別負責不同的通信功能,接下來針對這四層進行詳細地講解。
鏈路層:鏈路層是用於定義物理傳輸通道,通常是對某些網絡連接設備的驅動協議,例如針對光纖、網線提供的驅動。
網絡層:網絡層是整個TCP/IP協議的核心,它主要用於將傳輸的數據進行分組,將分組數據發送到目標計算機或者網絡。
傳輸層:主要使網絡程序進行通信,在進行網絡通信時,可以采用TCP協議,也可以采用UDP協議。
應用層:主要負責應用程序的協議,例如HTTP協議、FTP協議等。
1.1 IP地址和端口號
要想使網絡中的計算機能夠進行通信,必須為每台計算機指定一個標識號,通過這個標識號來指定接受數據的計算機或者發送數據的計算機。
在TCP/IP協議中,這個標識號就是IP地址,它可以唯一標識一台計算機,目前,IP地址廣泛使用的版本是IPv4,它是由4個字節大小的二進制數來表示,如:00001010000000000000000000000001。由於二進制形式表示的IP地址非常不便記憶和處理,因此通常會將IP地址寫成十進制的形式,每個字節用一個十進制數字(0-255)表示,數字間用符號“.”分開,如 “192.168.1.100”。
隨着計算機網絡規模的不斷擴大,對IP地址的需求也越來越多,IPV4這種用4個字節表示的IP地址面臨枯竭,因此IPv6 便應運而生了,IPv6使用16個字節表示IP地址,它所擁有的地址容量約是IPv4的8×1028倍,達到2128個(算上全零的),這樣就解決了網絡地址資源數量不夠的問題。
通過IP地址可以連接到指定計算機,但如果想訪問目標計算機中的某個應用程序,還需要指定端口號。在計算機中,不同的應用程序是通過端口號區分的。端口號是用兩個字節(16位的二進制數)表示的,它的取值范圍是0~65535,其中,0~1023之間的端口號用於一些知名的網絡服務和應用,用戶的普通應用程序需要使用1024以上的端口號,從而避免端口號被另外一個應用或服務所占用。
接下來通過一個圖例來描述IP地址和端口號的作用,如下圖所示。
從上圖中可以清楚地看到,位於網絡中一台計算機可以通過IP地址去訪問另一台計算機,並通過端口號訪問目標計算機中的某個應用程序。
1.1 InetAddress
了解了IP地址的作用,我們看學習下JDK中提供了一個InetAdderss類,該類用於封裝一個IP地址,並提供了一系列與IP地址相關的方法,下表中列出了InetAddress類的一些常用方法。
上圖中,列舉了InetAddress的四個常用方法。其中,前兩個方法用於獲得該類的實例對象,第一個方法用於獲得表示指定主機的InetAddress對象,第二個方法用於獲得表示本地的InetAddress對象。通過InetAddress對象便可獲取指定主機名,IP地址等,接下來通過一個案例來演示InetAddress的常用方法,如下所示。
1 public class Example01 { 2 public static void main(String[] args) throws Exception { 3 InetAddress local = InetAddress.getLocalHost(); 4 InetAddress remote = InetAddress.getByName("www.itcast.cn"); 5 System.out.println("本機的IP地址:" + local.getHostAddress()); 6 System.out.println("itcast的IP地址:" + remote.getHostAddress()); 7 System.out.println("itcast的主機名為:" + remote.getHostName()); 8 } 9 }
第1章 UDP與TCP協議
在介紹TCP/IP結構時,提到傳輸層的兩個重要的高級協議,分別是UDP和TCP,其中UDP是User Datagram Protocol的簡稱,稱為用戶數據報協議,TCP是Transmission Control Protocol的簡稱,稱為傳輸控制協議。
1.1 UDP協議
UDP是無連接通信協議,即在數據傳輸時,數據的發送端和接收端不建立邏輯連接。簡單來說,當一台計算機向另外一台計算機發送數據時,發送端不會確認接收端是否存在,就會發出數據,同樣接收端在收到數據時,也不會向發送端反饋是否收到數據。
由於使用UDP協議消耗資源小,通信效率高,所以通常都會用於音頻、視頻和普通數據的傳輸例如視頻會議都使用UDP協議,因為這種情況即使偶爾丟失一兩個數據包,也不會對接收結果產生太大影響。
但是在使用UDP協議傳送數據時,由於UDP的面向無連接性,不能保證數據的完整性,因此在傳輸重要數據時不建議使用UDP協議。UDP的交換過程如下圖所示。
1.1 TCP協議
TCP協議是面向連接的通信協議,即在傳輸數據前先在發送端和接收端建立邏輯連接,然后再傳輸數據,它提供了兩台計算機之間可靠無差錯的數據傳輸。在TCP連接中必須要明確客戶端與服務器端,由客戶端向服務端發出連接請求,每次連接的創建都需要經過“三次握手”。第一次握手,客戶端向服務器端發出連接請求,等待服務器確認,第二次握手,服務器端向客戶端回送一個響應,通知客戶端收到了連接請求,第三次握手,客戶端再次向服務器端發送確認信息,確認連接。整個交互過程如下圖所示。
由於TCP協議的面向連接特性,它可以保證傳輸數據的安全性,所以是一個被廣泛采用的協議,例如在下載文件時,如果數據接收不完整,將會導致文件數據丟失而不能被打開,因此,下載文件時必須采用TCP協議
第1章 UDP通信
1.1 DatagramPacket
前面介紹了UDP是一種面向無連接的協議,因此,在通信時發送端和接收端不用建立連接。UDP通信的過程就像是貨運公司在兩個碼頭間發送貨物一樣。在碼頭發送和接收貨物時都需要使用集裝箱來裝載貨物,UDP通信也是一樣,發送和接收的數據也需要使用“集裝箱”進行打包,為此JDK中提供了一個DatagramPacket類,該類的實例對象就相當於一個集裝箱,用於封裝UDP通信中發送或者接收的數據。
想要創建一個DatagramPacket對象,首先需要了解一下它的構造方法。在創建發送端和接收端的DatagramPacket對象時,使用的構造方法有所不同,接收端的構造方法只需要接收一個字節數組來存放接收到的數據,而發送端的構造方法不但要接收存放了發送數據的字節數組,還需要指定發送端IP地址和端口號。
接下來根據API文檔的內容,對DatagramPacket的構造方法進行逐一詳細地講解。
使用該構造方法在創建DatagramPacket對象時,指定了封裝數據的字節數組和數據的大小,沒有指定IP地址和端口號。很明顯,這樣的對象只能用於接收端,不能用於發送端。因為發送端一定要明確指出數據的目的地(ip地址和端口號),而接收端不需要明確知道數據的來源,只需要接收到數據即可。
使用該構造方法在創建DatagramPacket對象時,不僅指定了封裝數據的字節數組和數據的大小,還指定了數據包的目標IP地址(addr)和端口號(port)。該對象通常用於發送端,因為在發送數據時必須指定接收端的IP地址和端口號,就好像發送貨物的集裝箱上面必須標明接收人的地址一樣。
上面我們講解了DatagramPacket的構造方法,接下來對DatagramPacket類中的常用方法進行詳細地講解,如下表所示。
1.1 DatagramSocket
DatagramPacket數據包的作用就如同是“集裝箱”,可以將發送端或者接收端的數據封裝起來。然而運輸貨物只有“集裝箱”是不夠的,還需要有碼頭。在程序中需要實現通信只有DatagramPacket數據包也同樣不行,為此JDK中提供的一個DatagramSocket類。DatagramSocket類的作用就類似於碼頭,使用這個類的實例對象就可以發送和接收DatagramPacket數據包,發送數據的過程如下圖所示。
在創建發送端和接收端的DatagramSocket對象時,使用的構造方法也有所不同,下面對DatagramSocket類中常用的構造方法進行講解。
該構造方法用於創建發送端的DatagramSocket對象,在創建DatagramSocket對象時,並沒有指定端口號,此時,系統會分配一個沒有被其它網絡程序所使用的端口號。
該構造方法既可用於創建接收端的DatagramSocket對象,又可以創建發送端的DatagramSocket對象,在創建接收端的DatagramSocket對象時,必須要指定一個端口號,這樣就可以監聽指定的端口。
上面我們講解了DatagramSocket的構造方法,接下來對DatagramSocket類中的常用方法進行詳細地講解。
1.1 UDP網絡程序
講解了DatagramPacket和DatagramSocket的作用,接下來通過一個案例來學習一下它們在程序中的具體用法。
要實現UDP通信需要創建一個發送端程序和一個接收端程序,很明顯,在通信時只有接收端程序先運行,才能避免因發送端發送的數據無法接收,而造成數據丟失。因此,首先需要來完成接收端程序的編寫。
l UDP完成數據的發送

1 package com.xujingyang.UDP; 2 3 import java.net.DatagramPacket; 4 import java.net.DatagramSocket; 5 import java.net.InetAddress; 6 import java.net.Socket; 7 import java.net.UnknownHostException; 8 9 public class UDPSendDemo { 10 public static void main(String[] args) { 11 DatagramSocket socket = null; 12 try { 13 //建立基站 14 socket = new DatagramSocket(); 15 byte[] buf = "hello,UDP".getBytes(); 16 InetAddress address; 17 address = InetAddress.getByName("localhost"); 18 //建立倉庫 19 DatagramPacket packet = new DatagramPacket(buf, buf.length, address,12345); 20 //發送數據 21 socket.send(packet); 22 } catch (Exception e) { 23 e.printStackTrace(); 24 }finally{ 25 //關閉基站 26 socket.close(); 27 } 28 } 29 }
l UDP完成數據的接收

1 package com.xujingyang.UDP; 2 3 import java.net.DatagramPacket; 4 import java.net.DatagramSocket; 5 import java.net.InetAddress; 6 7 public class UDPReceiveDemo { 8 public static void main(String[] args) { 9 DatagramSocket socket = null; 10 try { 11 socket = new DatagramSocket(12345);//建立基站 12 byte[] buf = new byte[1024]; 13 DatagramPacket packet=new DatagramPacket(buf, buf.length);//建立機房 14 socket.receive(packet);//開始接受數據 15 16 //獲取對方的主機信息 17 InetAddress address = packet.getAddress(); 18 System.out.println(address.getHostAddress()); 19 //獲取數據內容 20 byte[] data = packet.getData(); 21 System.out.println("數據內容:"+new String(data,0,packet.getLength())); 22 //獲取數據長度 23 int length = packet.getLength(); 24 System.out.println("數據長度:"+length); 25 26 //獲取接收端口號 27 int port = packet.getPort(); 28 System.out.println("接收端口號是:"+port); 29 30 } catch (Exception e) { 31 e.printStackTrace(); 32 }finally{ 33 socket.close(); 34 } 35 } 36 37 }
第4章 TCP通信
TCP通信同UDP通信一樣,都能實現兩台計算機之間的通信,通信的兩端都需要創建socket對象。
區別在於,UDP中只有發送端和接收端,不區分客戶端與服務器端,計算機之間可以任意地發送數據。
而TCP通信是嚴格區分客戶端與服務器端的,在通信時,必須先由客戶端去連接服務器端才能實現通信,服務器端不可以主動連接客戶端,並且服務器端程序需要事先啟動,等待客戶端的連接。
在JDK中提供了兩個類用於實現TCP程序,一個是ServerSocket類,用於表示服務器端,一個是Socket類,用於表示客戶端。
通信時,首先創建代表服務器端的ServerSocket對象,該對象相當於開啟一個服務,並等待客戶端的連接,然后創建代表客戶端的Socket對象向服務器端發出連接請求,服務器端響應請求,兩者建立連接開始通信。
4.1 ServerSocket
通過前面的學習知道,在開發TCP程序時,首先需要創建服務器端程序。JDK的java.net包中提供了一個ServerSocket類,該類的實例對象可以實現一個服務器段的程序。通過查閱API文檔可知,ServerSocket類提供了多種構造方法,接下來就對ServerSocket的構造方法進行逐一地講解。
使用該構造方法在創建ServerSocket對象時,就可以將其綁定到一個指定的端口號上(參數port就是端口號)。
接下來學習一下ServerSocket的常用方法,如表所示。
ServerSocket對象負責監聽某台計算機的某個端口號,在創建ServerSocket對象后,需要繼續調用該對象的accept()方法,接收來自客戶端的請求。當執行了accept()方法之后,服務器端程序會發生阻塞,直到客戶端發出連接請求,accept()方法才會返回一個Scoket對象用於和客戶端實現通信,程序才能繼續向下執行。
4.2 Socket
講解了ServerSocket對象可以實現服務端程序,但只實現服務器端程序還不能完成通信,此時還需要一個客戶端程序與之交互,為此JDK提供了一個Socket類,用於實現TCP客戶端程序。
通過查閱API文檔可知Socket類同樣提供了多種構造方法,接下來就對Socket的常用構造方法進行詳細講解。
使用該構造方法在創建Socket對象時,會根據參數去連接在指定地址和端口上運行的服務器程序,其中參數host接收的是一個字符串類型的IP地址。
該方法在使用上與第二個構造方法類似,參數address用於接收一個InetAddress類型的對象,該對象用於封裝一個IP地址。
在以上Socket的構造方法中,最常用的是第一個構造方法。
接下來學習一下Socket的常用方法,如表所示。
方法聲明 |
功能描述 |
int getPort() |
該方法返回一個int類型對象,該對象是Socket對象與服務器端連接的端口號 |
InetAddress getLocalAddress() |
該方法用於獲取Socket對象綁定的本地IP地址,並將IP地址封裝成InetAddress類型的對象返回 |
void close() |
該方法用於關閉Socket連接,結束本次通信。在關閉socket之前,應將與socket相關的所有的輸入/輸出流全部關閉,這是因為一個良好的程序應該在執行完畢時釋放所有的資源 |
InputStream getInputStream() |
該方法返回一個InputStream類型的輸入流對象,如果該對象是由服務器端的Socket返回,就用於讀取客戶端發送的數據,反之,用於讀取服務器端發送的數據 |
OutputStream getOutputStream() |
該方法返回一個OutputStream類型的輸出流對象,如果該對象是由服務器端的Socket返回,就用於向客戶端發送數據,反之,用於向服務器端發送數據 |
在Socket類的常用方法中,getInputStream()和getOutStream()方法分別用於獲取輸入流和輸出流。當客戶端和服務端建立連接后,數據是以IO流的形式進行交互的,從而實現通信。
接下來通過一張圖來描述服務器端和客戶端的數據傳輸,如下圖所示。
4.3 簡單的TCP網絡程序
了解了ServerSocket、Socket類的基本用法,為了讓大家更好地掌握這兩個類的使用,接下來通過一個TCP通信的案例來進一步學習。
*服務端

1 package com.xujingyang.TCP; 2 3 import java.io.IOException; 4 import java.io.OutputStream; 5 import java.net.ServerSocket; 6 import java.net.Socket; 7 8 public class TCPServer { 9 public static void main(String[] args) { 10 ServerSocket socket = null; 11 OutputStream outputStream = null; 12 try { 13 //建立基站 14 socket = new ServerSocket(8899); 15 //開始開啟接收模式,接到后返回客戶端的socket對象 16 Socket client = socket.accept(); 17 //獲取向客戶端發送消息的對象流 18 outputStream = client.getOutputStream(); 19 //向客戶端寫數據 20 outputStream.write("你連上了服務器...".getBytes()); 21 } catch (Exception e) { 22 e.printStackTrace(); 23 } finally { 24 try { 25 outputStream.close(); 26 socket.close(); 27 } catch (IOException e) { 28 e.printStackTrace(); 29 } 30 } 31 } 32 }
*客戶端

1 package com.xujingyang.TCP; 2 3 import java.io.InputStream; 4 import java.net.Socket; 5 6 public class TCPClient { 7 public static void main(String[] args) { 8 Socket socket = null; 9 InputStream inputStream = null; 10 try { 11 //建立基站獲取鏈接地址及端口號 12 socket = new Socket("localhost", 8899); 13 //獲取服務器發過來的字節流 14 inputStream = socket.getInputStream(); 15 16 //開始解析字節流 17 byte[] b = new byte[1024]; 18 String str = ""; 19 int length = -1; 20 while ((length = inputStream.read(b, 0, b.length)) != -1) { 21 str += new String(b, 0, length); 22 } 23 System.out.println(str); 24 /* 25 * int length = inputStream.read(b); 26 System.out.println(new String(b, 0, length)); 27 */ 28 29 } catch (Exception e) { 30 e.printStackTrace(); 31 } finally { 32 //最后關閉 33 try { 34 inputStream.close(); 35 socket.close(); 36 } catch (Exception e2) { 37 e2.printStackTrace(); 38 } 39 } 40 } 41 }
4.3.1文件上傳案例
服務端

1 package com.xujingyang.TCPImg; 2 3 import java.io.File; 4 import java.io.FileOutputStream; 5 import java.io.IOException; 6 import java.io.InputStream; 7 import java.io.OutputStream; 8 import java.net.InetAddress; 9 import java.net.ServerSocket; 10 import java.net.Socket; 11 12 public class Server { 13 public static void main(String[] args) { 14 ServerSocket socket = null; 15 Socket client=null;; 16 InputStream in = null; 17 FileOutputStream fOutputStream = null; 18 OutputStream out = null; 19 try { 20 socket = new ServerSocket(8899);//創建服務器,等待客戶端連接 21 client = socket.accept();//獲取客戶端連接 22 InetAddress clientInetAddress = client.getInetAddress();//獲取客戶端信息 23 System.out.println(clientInetAddress.getHostAddress()+" 已經連接."); 24 25 in = client.getInputStream();//獲取socket輸入流 26 27 //===========將輸入流寫入文件 28 fOutputStream = new FileOutputStream(new File("1.png")); 29 byte[] b = new byte[1024]; 30 int lenght=-1; 31 while((lenght = in.read(b))!=-1){ 32 fOutputStream.write(b, 0, lenght); 33 } 34 35 //像客戶端反饋信息 36 out = client.getOutputStream(); 37 out.write("圖片上傳成功ok!".getBytes()); 38 } catch (IOException e) { 39 e.printStackTrace(); 40 }finally{ 41 try { 42 out.close(); 43 fOutputStream.close(); 44 in.close(); 45 client.close(); 46 socket.close(); 47 } catch (Exception e2) { 48 e2.printStackTrace(); 49 } 50 } 51 52 } 53 }
客戶端

1 package com.xujingyang.TCPImg; 2 3 import java.io.File; 4 import java.io.FileInputStream; 5 import java.io.InputStream; 6 import java.io.OutputStream; 7 import java.net.Socket; 8 9 public class Client { 10 11 public static void main(String[] args) { 12 Socket socket = null; 13 OutputStream out = null; 14 FileInputStream fInputStream = null; 15 InputStream in = null; 16 try { 17 //創建socket,連接服務器 18 socket = new Socket("localhost", 8899); 19 //獲得socket中的輸出流 20 out = socket.getOutputStream(); 21 //獲取本地文件,並且傳給服務器 22 fInputStream = new FileInputStream(new File("E:/在尚學堂/java基礎/day02/位運算.png")); 23 byte[] b=new byte[1024]; 24 int length=-1; 25 while((length = fInputStream.read(b))!=-1){ 26 out.write(b, 0, length);//寫給服務器 27 } 28 29 socket.shutdownOutput();//客戶端傳送完畢后,必須關閉傳送流,告知服務器 30 31 32 //獲取socket中的輸入流,來讀取服務器發過來的消息 33 in = socket.getInputStream(); 34 byte[] b1=new byte[1024]; 35 int len = in.read(b1); 36 System.out.println(new String(b1,0,len)); 37 38 39 } catch (Exception e) { 40 e.printStackTrace(); 41 }finally{ 42 try { 43 in.close(); 44 out.close(); 45 fInputStream.close(); 46 socket.close(); 47 } catch (Exception e2) { 48 e2.printStackTrace(); 49 } 50 } 51 } 52 }
服務端多線程代碼

1 package com.xujingyang.TCPImg; 2 3 import java.io.BufferedOutputStream; 4 import java.io.FileOutputStream; 5 import java.io.IOException; 6 import java.io.InputStream; 7 import java.io.OutputStream; 8 import java.net.InetAddress; 9 import java.net.ServerSocket; 10 import java.net.Socket; 11 12 public class ThreadServer { 13 public static void main(String[] args) { 14 ServerSocket socket; 15 try { 16 socket = new ServerSocket(8899); 17 System.out.println("等待連接..."); 18 while (true) { 19 final Socket client = socket.accept(); 20 InetAddress inetAddress = client.getInetAddress(); 21 System.out.println(inetAddress.getHostAddress()+" 連接成功!"); 22 new Thread() { 23 public void run() { 24 try { 25 InputStream in = client.getInputStream(); 26 BufferedOutputStream bufferedOutputStream=new BufferedOutputStream(new FileOutputStream(System.currentTimeMillis()+".jpg")); 27 byte[] b=new byte[1024]; 28 int len=-1; 29 while((len=in.read(b))!=-1){ 30 bufferedOutputStream.write(b, 0, len); 31 } 32 33 34 OutputStream out = client.getOutputStream(); 35 out.write("圖片上傳成功".getBytes()); 36 out.close(); 37 38 bufferedOutputStream.close(); 39 in.close(); 40 client.close(); 41 42 } catch (Exception e) { 43 e.printStackTrace(); 44 } 45 }; 46 }.start(); 47 } 48 49 } catch (Exception e) { 50 e.printStackTrace(); 51 } 52 } 53 }
第1章 總結
1.1 知識點總結
l IP地址:用來唯一表示我們自己的電腦的,是一個網絡標示
l 端口號: 用來區別當前電腦中的應用程序的
l UDP: 傳送速度快,但是容易丟數據,如視頻聊天,語音聊天
l TCP: 傳送穩定,不會丟失數據,如文件的上傳、下載
l UDP程序交互的流程
l 發送端
1,創建DatagramSocket對象
2,創建DatagramPacket對象,並封裝數據
3,發送數據
4,釋放流資源
l 接收端
1,創建DatagramSocket對象
2,創建DatagramPacket對象
3,接收數據存儲到DatagramPacket對象中
4,獲取DatagramPacket對象的內容
5,釋放流資源
l TCP程序交互的流程
l 客戶端
1,創建客戶端的Socket對象
2,獲取Socket的輸出流對象
3,寫數據給服務器
4,獲取Socket的輸入流對象
5,使用輸入流,讀反饋信息
6,關閉流資源
l 服務器端
1,創建服務器端ServerSocket對象,指定服務器端端口號
2,開啟服務器,等待着客戶端Socket對象的連接,如有客戶端連接,返回客戶端的Socket對象
3,通過客戶端的Socket對象,獲取客戶端的輸入流,為了實現獲取客戶端發來的數據
4,通過客戶端的輸入流,獲取流中的數據
5,通過客戶端的Socket對象,獲取客戶端的輸出流,為了實現給客戶端反饋信息
6,通過客戶端的輸出流,寫數據到流中
7,關閉流資源