1. 程式人生 > >java----day31(網路程式設計)

java----day31(網路程式設計)

網路通訊協議

通過計算機網路可以使多臺計算機實現連線,位於同一個網路中的計算機在進行連線和通訊時需要遵守一定的規則,這就好比在道路中行駛的汽車一定要遵守交通規則一樣。在計算機網路中,這些連線和通訊的規則被稱為網路通訊協議,它對資料的傳輸格式、傳輸速率、傳輸步驟等做了統一規定,通訊雙方必須同時遵守才能完成資料交換。

網路通訊協議有很多種,目前應用最廣泛的是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協議等。

  • 地址和埠號

要想使網路中的計算機能夠進行通訊,必須為每臺計算機指定一個標識號,通過這個標識號來指定接受資料的計算機或者傳送資料的計算機。

在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地址去訪問另一臺計算機,並通過埠號訪問目標計算機中的某個應用程式。

  • InetAddress

瞭解了IP地址的作用,我們看學習下JDK中提供了一個InetAdderss類,該類用於封裝一個IP地址,並提供了一系列與IP地址相關的方法,下表中列出了InetAddress類的一些常用方法。

上圖中,列舉了InetAddress的四個常用方法。其中,前兩個方法用於獲得該類的例項物件,第一個方法用於獲得表示指定主機的InetAddress物件,第二個方法用於獲得表示本地的InetAddress物件。通過InetAddress物件便可獲取指定主機名,IP地址等,接下來通過一個案例來演示InetAddress的常用方法,如下所示。

public class Example01 {
	public static void main(String[] args) throws Exception {
		InetAddress local = InetAddress.getLocalHost();
		InetAddress remote = InetAddress.getByName("www.itcast.cn");
		System.out.println("本機的IP地址:" + local.getHostAddress());
		System.out.println("itcast的IP地址:" + remote.getHostAddress());
		System.out.println("itcast的主機名為:" + remote.getHostName());
	}
}

UDP、TCP協議

在介紹TCP/IP結構時,提到傳輸層的兩個重要的高階協議,分別是UDP和TCP,其中UDP是User Datagram Protocol的簡稱,稱為使用者資料報協議,TCP是Transmission Control Protocol的簡稱,稱為傳輸控制協議。

 

  • UDP協議使用者資料報協議

UDP是無連線通訊協議,即在資料傳輸時,資料的傳送端和接收端不建立邏輯連線。簡單來說,當一臺計算機向另外一臺計算機發送資料時,傳送端不會確認接收端是否存在,就會發出資料,同樣接收端在收到資料時,也不會向傳送端反饋是否收到資料。

由於使用UDP協議消耗資源小,通訊效率高,所以通常都會用於音訊、視訊和普通資料的傳輸例如視訊會議都使用UDP協議,因為這種情況即使偶爾丟失一兩個資料包,也不會對接收結果產生太大影響。

但是在使用UDP協議傳送資料時,由於UDP的面向無連線性,不能保證資料的完整性,因此在傳輸重要資料時不建議使用UDP協議。UDP的交換過程如下圖所示。

  • TCP協議傳輸控制協議

TCP協議是面向連線的通訊協議,即在傳輸資料前先在傳送端和接收端建立邏輯連線,然後再傳輸資料,它提供了兩臺計算機之間可靠無差錯的資料傳輸。在TCP連線中必須要明確客戶端與伺服器端,由客戶端向服務端發出連線請求,每次連線的建立都需要經過“三次握手”。第一次握手,客戶端向伺服器端發出連線請求,等待伺服器確認,第二次握手,伺服器端向客戶端回送一個響應,通知客戶端收到了連線請求,第三次握手,客戶端再次向伺服器端傳送確認資訊,確認連線。整個互動過程如下圖所示。

由於TCP協議的面向連線特性,它可以保證傳輸資料的安全性,所以是一個被廣泛採用的協議,例如在下載檔案時,如果資料接收不完整,將會導致檔案資料丟失而不能被開啟,因此,下載檔案時必須採用TCP協議。

 

UDP通訊

  • DatagramPacket

前面介紹了UDP是一種面向無連線的協議(傳送資料前無需知道接收方是否存在),因此,在通訊時傳送端和接收端不用建立連線。UDP通訊的過程就像是貨運公司在兩個碼頭間傳送貨物一樣。在碼頭髮送和接收貨物時都需要使用集裝箱來裝載貨物,UDP通訊也是一樣,傳送和接收的資料也需要使用“集裝箱”進行打包,為此JDK中提供了一個DatagramPacket類,該類的例項物件就相當於一個集裝箱,用於封裝UDP通訊中傳送或者接收的資料。

想要建立一個DatagramPacket物件,首先需要了解一下它的構造方法。在建立傳送端和接收端的DatagramPacket物件時,使用的構造方法有所不同,接收端的構造方法只需要接收一個位元組陣列來存放接收到的資料,而傳送端的構造方法不但要接收存放了傳送資料的位元組陣列,還需要指定傳送端IP地址和埠號。

接下來根據API文件的內容,對DatagramPacket的構造方法進行逐一詳細地講解。

DatagramPacket(byte[] buf, int length)
          構造 DatagramPacket,用來接收長度為 length 的資料包。

使用該構造方法在建立DatagramPacket物件時,指定了封裝資料的位元組陣列和資料的大小,沒有指定IP地址和埠號。很明顯,這樣的物件只能用於接收端,不能用於傳送端。因為傳送端一定要明確指出資料的目的地(ip地址和埠號),而接收端不需要明確知道資料的來源,只需要接收到資料即可。

DatagramPacket(byte[] buf, int length, InetAddress address, int port)
          構造資料報包,用來將長度為 length 的包傳送到指定主機上的指定埠號。

使用該構造方法在建立DatagramPacket物件時,不僅指定了封裝資料的位元組陣列和資料的大小,還指定了資料包的目標IP地址(addr)和埠號(port)。該物件通常用於傳送端,因為在傳送資料時必須指定接收端的IP地址和埠號,就好像傳送貨物的集裝箱上面必須標明接收人的地址一樣。

  • DatagramSocket

DatagramPacket資料包的作用就如同是“集裝箱”,可以將傳送端或者接收端的資料封裝起來。然而運輸貨物只有“集裝箱”是不夠的,還需要有碼頭。在程式中需要實現通訊只有DatagramPacket資料包也同樣不行,為此JDK中提供的一個DatagramSocket類。DatagramSocket類的作用就類似於碼頭,使用這個類的例項物件就可以傳送和接收DatagramPacket資料包,傳送資料的過程如下圖所示。

在建立傳送端和接收端的DatagramSocket物件時,使用的構造方法也有所不同,下面對DatagramSocket類中常用的構造方法進行講解。

構造方法摘要
  DatagramSocket()
          構造資料報套接字並將其繫結到本地主機上任何可用的埠。
protected DatagramSocket(DatagramSocketImpl impl)
          建立帶有指定 DatagramSocketImpl 的未繫結資料報套接字。
  DatagramSocket(int port)
          建立資料報套接字並將其繫結到本地主機上的指定埠。
  DatagramSocket(int port, InetAddress laddr)
          建立資料報套接字,將其繫結到指定的本地地址。
  DatagramSocket(SocketAddress bindaddr)
          建立資料報套接字,將其繫結到指定的本地套接字地址。

常用方法

 

 void receive(DatagramPacket p)
          從此套接字接收資料報包。
 void send(DatagramPacket p)
          從此套接字傳送資料報包。
  • UDP網路程式

講解了DatagramPacket和DatagramSocket的作用,接下來通過一個案例來學習一下它們在程式中的具體用法。

下圖為UDP傳送端與接收端互動圖解

要實現UDP通訊需要建立一個傳送端程式和一個接收端程式,很明顯,在通訊時只有接收端程式先執行,才能避免因傳送端傳送的資料無法接收,而造成資料丟失。因此,首先需要來完成接收端程式的編寫。

/*
* 傳送端
 * 1,建立DatagramSocket物件
 * 2,建立DatagramPacket物件,並封裝資料
 * 3,傳送資料
 * 4,釋放流資源
 */
public class UDPSend {
	public static void main(String[] args) throws IOException {
		//1,建立DatagramSocket物件
		DatagramSocket sendSocket = new DatagramSocket();
		//2,建立DatagramPacket物件,並封裝資料
		//public DatagramPacket(byte[] buf, int length, InetAddress address,  
int port)
		//構造資料報包,用來將長度為 length 的包傳送到指定主機上的指定埠號。
		byte[] buffer = "hello,UDP".getBytes();
		DatagramPacket dp = new DatagramPacket(buffer, 
buffer.length, InetAddress.getByName("192.168.75.58"), 12306);
		//3,傳送資料
		//public void send(DatagramPacket p) 從此套接字傳送資料報包
		sendSocket.send(dp);
		//4,釋放流資源
		sendSocket.close();
	}
}
/*
 * UDP接收端
 * 
 * 1,建立DatagramSocket物件
 * 2,建立DatagramPacket物件
 * 3,接收資料儲存到DatagramPacket物件中
 * 4,獲取DatagramPacket物件的內容
 * 5,釋放流資源
 */
public class UDPReceive {
	public static void main(String[] args) throws IOException {
		//1,建立DatagramSocket物件,並指定埠號
		DatagramSocket receiveSocket = new DatagramSocket(12306);
		//2,建立DatagramPacket物件, 建立一個空的倉庫
		byte[] buffer = new byte[1024];
		DatagramPacket dp = new DatagramPacket(buffer, 1024);
		//3,接收資料儲存到DatagramPacket物件中
		receiveSocket.receive(dp);
		//4,獲取DatagramPacket物件的內容
		//誰發來的資料  getAddress()
		InetAddress ipAddress = dp.getAddress();
		String ip = ipAddress.getHostAddress();//獲取到了IP地址
		//發來了什麼資料  getData()
		byte[] data = dp.getData();
		//發來了多少資料 getLenth()
		int length = dp.getLength();
		//顯示收到的資料
		String dataStr = new String(data,0,length);
		System.out.println("IP地址:"+ip+ "資料是"+ dataStr);
		//5,釋放流資源
		receiveSocket.close();
	}
}

TCP通訊

TCP通訊同UDP通訊一樣,都能實現兩臺計算機之間的通訊,通訊的兩端都需要建立socket物件。

區別在於,UDP中只有傳送端和接收端,不區分客戶端與伺服器端,計算機之間可以任意地傳送資料。

而TCP通訊是嚴格區分客戶端與伺服器端的,在通訊時,必須先由客戶端去連線伺服器端才能實現通訊,伺服器端不可以主動連線客戶端,並且伺服器端程式需要事先啟動,等待客戶端的連線。

在JDK中提供了兩個類用於實現TCP程式,一個是ServerSocket類,用於表示伺服器端,一個是Socket類,用於表示客戶端

通訊時,首先建立代表伺服器端的ServerSocket物件,該物件相當於開啟一個服務,並等待客戶端的連線,然後建立代表客戶端的Socket物件向伺服器端發出連線請求,伺服器端響應請求,兩者建立連線開始通訊。

  • ServerSocket

通過前面的學習知道,在開發TCP程式時,首先需要建立伺服器端程式。JDK的java.net包中提供了一個ServerSocket類,該類的例項物件可以實現一個伺服器段的程式。通過查閱API文件可知,ServerSocket類提供了多種構造方法,接下來就對ServerSocket的構造方法進行逐一地講解。

ServerSocket(int port)
          建立繫結到特定埠的伺服器套接字。

使用該構造方法在建立ServerSocket物件時,就可以將其繫結到一個指定的埠號上(引數port就是埠號)。

常用方法:

 

 Socket accept()
          偵聽並接受到此套接字的連線。
 InetAddress getInetAddress()
          返回此伺服器套接字的本地地址

ServerSocket物件負責監聽某臺計算機的某個埠號,在建立ServerSocket物件後,需要繼續呼叫該物件的accept()方法,接收來自客戶端的請求。當執行了accept()方法之後,伺服器端程式會發生阻塞,直到客戶端發出連線請求,accept()方法才會返回一個Scoket物件用於和客戶端實現通訊,程式才能繼續向下執行。

  • Socket

講解了ServerSocket物件可以實現服務端程式,但只實現伺服器端程式還不能完成通訊,此時還需要一個客戶端程式與之互動,為此JDK提供了一個Socket類,用於實現TCP客戶端程式。

通過查閱API文件可知Socket類同樣提供了多種構造方法,接下來就對Socket的常用構造方法進行詳細講解。

Socket(InetAddress address, int port)
          建立一個流套接字並將其連線到指定 IP 地址的指定埠號。

常用方法

方法摘要
 void close()
          關閉此套接字。
 InetAddress getInetAddress()
          返回套接字連線的地址。
 InputStream getInputStream()
          返回此套接字的輸入流。
 InetAddress getLocalAddress()
          獲取套接字繫結的本地地址。
 int getLocalPort()
          返回此套接字繫結到的本地埠。
 SocketAddress getLocalSocketAddress()
          返回此套接字繫結的端點的地址,如果尚未繫結則返回 null
 OutputStream getOutputStream()
          返回此套接字的輸出流。
 int getPort()
          返回此套接字連線到的遠端埠。

在Socket類的常用方法中,getInputStream()和getOutStream()方法分別用於獲取輸入流和輸出流。當客戶端和服務端建立連線後,資料是以IO流的形式進行互動的,從而實現通訊。

接下來通過一張圖來描述伺服器端和客戶端的資料傳輸,如下圖所示。

  • 簡單的TCP網路程式

瞭解了ServerSocket、Socket類的基本用法,為了讓大家更好地掌握這兩個類的使用,接下來通過一個TCP通訊的案例來進一步學習。如下圖所示。

/*
 * TCP 伺服器端
 * 
 * 1,建立伺服器ServerSocket物件(指定伺服器埠號)
 * 2,開啟伺服器了,等待客戶端的連線,當客戶端連線後,可以獲取到連線伺服器的客戶端Socket物件
 * 3,給客戶端反饋資訊
 * 4,關閉流資源
 */
public class TCPServer {
	public static void main(String[] args) throws IOException {
		//1,建立伺服器ServerSocket物件(指定伺服器埠號)
		ServerSocket ss = new ServerSocket(8888);
		//2,開啟伺服器了,等待客戶端的連線,當客戶端連線後,可以獲取到連線伺服器的客戶端Socket物件
		Socket s = ss.accept();
		//3,給客戶端反饋資訊
		/*
		 * a,獲取客戶端的輸出流
		 * b,在服務端端,通過客戶端的輸出流寫資料給客戶端
		 */
		//a,獲取客戶端的輸出流
		OutputStream out = s.getOutputStream();
		//b,在服務端端,通過客戶端的輸出流寫資料給客戶端
		out.write("你已經連線上了伺服器".getBytes());
		//4,關閉流資源
		out.close();
		s.close();
		//ss.close();  伺服器流 通常都是不關閉的
	}
}
/*
 * TCP 客戶端
 * 
 * 1,建立客戶端Socket物件,(指定要連線的伺服器地址與埠號)
 * 2,獲取伺服器端的反饋回來的資訊
 * 3,關閉流資源
 */
public class TCPClient {
	public static void main(String[] args) throws IOException {
		//1,建立客戶端Socket物件,(指定要連線的伺服器地址與埠號)
		Socket s = new Socket("192.168.74.58", 8888);
		//2,獲取伺服器端的反饋回來的資訊
		InputStream in = s.getInputStream();
		//獲取獲取流中的資料
		byte[] buffer = new byte[1024];
		//把流中的資料儲存到陣列中,並記錄讀取位元組的個數
		int length = in.read(buffer);
		//顯示資料
		System.out.println( new String(buffer, 0 , length) );
		//3,關閉流資源
		in.close();
		s.close();
	}
}

總結

  • IP地址:用來唯一表示我們自己的電腦的,是一個網路標示
  • 埠號: 用來區別當前電腦中的應用程式的
  • UDP: 傳送速度快,但是容易丟資料,如視訊聊天,語音聊天
  • TCP: 傳送穩定,不會丟失資料,如檔案的上傳、下載

UDP程式互動的流程

傳送端

1,建立DatagramSocket物件

2,建立DatagramPacket物件,並封裝資料

3,傳送資料

4,釋放流資源

接收端

1,建立DatagramSocket物件

2,建立DatagramPacket物件

3,接收資料儲存到DatagramPacket物件中

4,獲取DatagramPacket物件的內容

 5,釋放流資源

TCP程式互動的流程

客戶端

1,建立客戶端的Socket物件

2,獲取Socket的輸出流物件

3,寫資料給伺服器

4,獲取Socket的輸入流物件

5,使用輸入流,讀反饋資訊

6,關閉流資源

伺服器端

1,建立伺服器端ServerSocket物件,指定伺服器端埠號

2,開啟伺服器,等待著客戶端Socket物件的連線,如有客戶端連線,返回客戶端的Socket物件

3,通過客戶端的Socket物件,獲取客戶端的輸入流,為了實現獲取客戶端發來的資料

4,通過客戶端的輸入流,獲取流中的資料

5,通過客戶端的Socket物件,獲取客戶端的輸出流,為了實現給客戶端反饋資訊

6,通過客戶端的輸出流,寫資料到流中

7,關閉流資源