java基礎(31):網絡通訊協議、UDP、TCP

1. 網絡通訊協議

經過計算機網絡可使多臺計算機實現鏈接,位於同一個網絡中的計算機在進行鏈接和通訊時須要遵照必定的規則,這就比如在道路中行駛的汽車必定要遵照交通規則同樣。在計算機網絡中,這些鏈接和通訊的規則被稱爲網絡通訊協議,它對數據的傳輸格式、傳輸速率、傳輸步驟等作了統一規定,通訊雙方必須同時遵照才能完成數據交換。java

網絡通訊協議有不少種,目前應用最普遍的是TCP/IP協議(Transmission Control Protocal/Internet Protoal傳輸控制協議/英特網互聯協議),它是一個包括TCP協議和IP協議,UDPUser Datagram Protocol)協議和其它一些協議的協議組,在學習具體協議以前首先了解一下TCP/IP協議組的層次結構。數組

在進行數據傳輸時,要求發送的數據與收到的數據徹底同樣,這時,就須要在原有的數據上添加不少信息,以保證數據在傳輸過程當中數據格式徹底一致。TCP/IP協議的層次結構比較簡單,共分爲四層,如圖所示。安全

上圖中,TCP/IP協議中的四層分別是應用層、傳輸層、網絡層和鏈路層,每層分別負責不一樣的通訊功能,接下來針對這四層進行詳細地講解。服務器

鏈路層:鏈路層用於定義物理傳輸通道,一般是對某些網絡鏈接設備的驅動協議,例如針對光纖、網線提供的驅動。網絡

網絡層:網絡層是整個TCP/IP協議的核心,它主要用於將傳輸的數據進行分組,將分組數據發送到目標計算機或者網絡。多線程

輸層:主要使網絡程序進行通訊,在進行網絡通訊時,能夠採用TCP協議,也能夠採用UDP協議。socket

應用層:主要負責應用程序的協議,例如HTTP協議、FTP協議等。學習

 

1.1  IP地址和端口號

 

 

要想使網絡中的計算機可以進行通訊,必須爲每臺計算機指定一個標識號,經過這個標識號來指定接受數據的計算機或者發送數據的計算機。spa

 

TCP/IP協議中,這個標識號就是IP地址,它能夠惟一標識一臺計算機,目前,IP地址普遍使用的版本是IPv4,它是由4個字節大小的二進制數來表示,如:00001010000000000000000000000001。因爲二進制形式表示的IP地址很是不便記憶和處理,所以一般會將IP地址寫成十進制的形式,每一個字節用一個十進制數字(0-255)表示,數字間用符號「.」分開,如 「192.168.1.100」。.net

 

隨着計算機網絡規模的不斷擴大,對IP地址的需求也愈來愈多,IPV4這種用4個字節表示的IP地址面臨枯竭,所以IPv6 便應運而生了,IPv6使用16個字節表示IP地址,它所擁有的地址容量約是IPv48×1028倍,達到2128個(算上全零的),這樣就解決了網絡地址資源數量不夠的問題。

 

經過IP地址能夠鏈接到指定計算機,但若是想訪問目標計算機中的某個應用程序,還須要指定端口號。在計算機中,不一樣的應用程序是經過端口號區分的。端口號是用兩個字節(16位的二進制數)表示的,它的取值範圍是0~65535,其中,0~1023之間的端口號用於一些知名的網絡服務和應用,用戶的普通應用程序須要使用1024以上的端口號,從而避免端口號被另一個應用或服務所佔用。

 

接下來經過一個圖例來描述IP地址和端口號的做用,以下圖所示。

從上圖中能夠清楚地看到,位於網絡中一臺計算機能夠經過IP地址去訪問另外一臺計算機,並經過端口號訪問目標計算機中的某個應用程序。

 

 

 

1.2  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());
    }
}

 

2. UDPTCP協議

 

 

在介紹TCP/IP結構時,提到傳輸層的兩個重要的高級協議,分別是UDPTCP,其中UDPUser Datagram Protocol的簡稱,稱爲用戶數據報協議,TCPTransmission Control Protocol的簡稱,稱爲傳輸控制協議。

 

2.1  UDP協議

 

 

 

UDP是無鏈接通訊協議,即在數據傳輸時,數據的發送端和接收端不創建邏輯鏈接。簡單來講,當一臺計算機向另一臺計算機發送數據時,發送端不會確認接收端是否存在,就會發出數據,一樣接收端在收到數據時,也不會向發送端反饋是否收到數據。

 

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

 

可是在使用UDP協議傳送數據時,因爲UDP的面向無鏈接性,不能保證數據的完整性,所以在傳輸重要數據時不建議使用UDP協議。UDP的交換過程以下圖所示。

2.2  TCP協議

 

 

 

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

 

因爲TCP協議的面向鏈接特性,它能夠保證傳輸數據的安全性,因此是一個被普遍採用的協議,例如在下載文件時,若是數據接收不完整,將會致使文件數據丟失而不能被打開,所以,下載文件時必須採用TCP協議。

 

 

3. UDP通訊

 

 

3.1  DatagramPacket

 

 

前面介紹了UDP是一種面向無鏈接的協議,所以,在通訊時發送端和接收端不用創建鏈接。UDP通訊的過程就像是貨運公司在兩個碼頭間發送貨物同樣。在碼頭髮送和接收貨物時都須要使用集裝箱來裝載貨物,UDP通訊也是同樣,發送和接收的數據也須要使用「集裝箱」進行打包,爲此JDK中提供了一個DatagramPacket類,該類的實例對象就至關於一個集裝箱,用於封裝UDP通訊中發送或者接收的數據。

 

想要建立一個DatagramPacket對象,首先須要瞭解一下它的構造方法。在建立發送端和接收端的DatagramPacket對象時,使用的構造方法有所不一樣,接收端的構造方法只須要接收一個字節數組來存放接收到的數據,而發送端的構造方法不但要接收存放了發送數據的字節數組,還須要指定發送端IP地址和端口號。

 

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

使用該構造方法在建立DatagramPacket對象時,指定了封裝數據的字節數組和數據的大小,沒有指定IP地址和端口號。很明顯,這樣的對象只能用於接收端,不能用於發送端。由於發送端必定要明確指出數據的目的地(ip地址和端口號)而接收端不須要明確知道數據的來源,只須要接收到數據便可。

 

 

使用該構造方法在建立DatagramPacket對象時,不只指定了封裝數據的字節數組和數據的大小,還指定了數據包的目標IP地址(addr)和端口號(port)。該對象一般用於發送端,由於在發送數據時必須指定接收端的IP地址和端口號,就好像發送貨物的集裝箱上面必須標明接收人的地址同樣。

上面咱們講解了DatagramPacket的構造方法,接下來對DatagramPacket類中的經常使用方法進行詳細地講解,以下表所示。

 

3.2  DatagramSocket

 

 

DatagramPacket數據包的做用就如同是「集裝箱」,能夠將發送端或者接收端的數據封裝起來。然而運輸貨物只有「集裝箱」是不夠的,還須要有碼頭。在程序中須要實現通訊只有DatagramPacket數據包也一樣不行,爲此JDK中提供的一個DatagramSocket類。DatagramSocket類的做用就相似於碼頭,使用這個類的實例對象就能夠發送和接收DatagramPacket數據包,發送數據的過程以下圖所示。

 

在建立發送端和接收端的DatagramSocket對象時,使用的構造方法也有所不一樣,下面對DatagramSocket類中經常使用的構造方法進行講解。

 

該構造方法用於建立發送端的DatagramSocket對象,在建立DatagramSocket對象時,並無指定端口號,此時,系統會分配一個沒有被其它網絡程序所使用的端口號。

 

該構造方法既可用於建立接收端的DatagramSocket對象,又能夠建立發送端的DatagramSocket對象,在建立接收端的DatagramSocket對象時,必需要指定一個端口號,這樣就能夠監聽指定的端口。

上面咱們講解了DatagramSocket的構造方法,接下來對DatagramSocket類中的經常使用方法進行詳細地講解。

 

3.3  UDP網絡程序

 

 

講解了DatagramPacketDatagramSocket的做用,接下來經過一個案例來學習一下它們在程序中的具體用法。

下圖爲UDP發送端與接收端交互圖解

 

要實現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完成數據的接收

 

 

/*
 * 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();
    }
}

 

4. TCP通訊

 

 

TCP通訊同UDP通訊同樣,都能實現兩臺計算機之間的通訊,通訊的兩端都須要建立socket對象。

 

區別在於,UDP中只有發送端和接收端,不區分客戶端與服務器端,計算機之間能夠任意地發送數據。

 

TCP通訊是嚴格區分客戶端與服務器端的,在通訊時,必須先由客戶端去鏈接服務器端才能實現通訊,服務器端不能夠主動鏈接客戶端,而且服務器端程序須要事先啓動,等待客戶端的鏈接。

 

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

 

通訊時,首先建立表明服務器端的ServerSocket對象,該對象至關於開啓一個服務,並等待客戶端的鏈接,而後建立表明客戶端的Socket對象向服務器端發出鏈接請求,服務器端響應請求,二者創建鏈接開始通訊。

 

 

4.1  ServerSocket

 

 

經過前面的學習知道,在開發TCP程序時,首先須要建立服務器端程序。JDKjava.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網絡程序

 

 

瞭解了ServerSocketSocket類的基本用法,爲了讓你們更好地掌握這兩個類的使用,接下來經過一個TCP通訊的案例來進一步學習。以下圖所示。

 

要實現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();
    }
}

 

4.4  文件上傳案例

 

 

目前大多數服務器都會提供文件上傳的功能,因爲文件上傳須要數據的安全性和完整性,很明顯須要使用TCP協議來實現。接下來經過一個案例來實現圖片上傳的功能。以下圖所示。原圖:文件上傳.bmp

 

 

 

首先編寫服務器端程序,用來接收圖片。

 

 

/*
 * 文件上傳  服務器端
 *
 */
public class TCPServer {
    public static void main(String[] args) throws IOException {
        //1,建立服務器,等待客戶端鏈接
        ServerSocket serverSocket = new ServerSocket(8888);
        Socket clientSocket = serverSocket.accept();
        //顯示哪一個客戶端Socket鏈接上了服務器
        InetAddress ipObject = clientSocket.getInetAddress();//獲得IP地址對象
        String ip = ipObject.getHostAddress(); //獲得IP地址字符串
        System.out.println("小樣,抓到你了,鏈接我!!" + "IP:" + ip);
        
        //7,獲取Socket的輸入流
        InputStream in = clientSocket.getInputStream();
        //8,建立目的地的字節輸出流   D:\\upload\\192.168.74.58(1).jpg
        BufferedOutputStream fileOut = new BufferedOutputStream(new FileOutputStream("D:\\upload\\192.168.74.58(1).jpg"));
        //9,把Socket輸入流中的數據,寫入目的地的字節輸出流中
        byte[] buffer = new byte[1024];
        int len = -1;
        while((len = in.read(buffer)) != -1){
            //寫入目的地的字節輸出流中
            fileOut.write(buffer, 0, len);
        }
        
        //-----------------反饋信息---------------------
        //10,獲取Socket的輸出流, 做用:寫反饋信息給客戶端
        OutputStream out = clientSocket.getOutputStream();
        //11,寫反饋信息給客戶端
        out.write("圖片上傳成功".getBytes());
        
        out.close();
        fileOut.close();
        in.close();
        clientSocket.close();
        //serverSocket.close();
    }
}

 

編寫客戶端,完成上傳圖片

 

 

/*
 * 文件上傳 客戶端
 * 
 * public void shutdownOutput()  禁用此Socket的輸出流,間接的至關於告知了服務器數據寫入完畢
 */
public class TCPClient {
    public static void main(String[] args) throws IOException {
        //2,建立客戶端Socket,鏈接服務器
        Socket socket = new Socket("192.168.74.58", 8888);
        //3,獲取Socket流中的輸出流,功能:用來把數據寫到服務器
        OutputStream out = socket.getOutputStream();
        //4,建立字節輸入流,功能:用來讀取數據源(圖片)的字節
        BufferedInputStream fileIn = new BufferedInputStream(new FileInputStream("D:\\NoDir\\test.jpg"));
        //5,把圖片數據寫到Socket的輸出流中(把數據傳給服務器)
        byte[] buffer = new byte[1024];
        int len = -1;
        while ((len = fileIn.read(buffer)) != -1){
            //把數據寫到Socket的輸出流中
            out.write(buffer, 0, len);
        }
        //6,客戶端發送數據完畢,結束Socket輸出流的寫入操做,告知服務器端
        socket.shutdownOutput();

        //-----------------反饋信息---------------------
        //12,獲取Socket的輸入流  做用: 讀反饋信息
        InputStream in = socket.getInputStream();
        //13,讀反饋信息
        byte[] info = new byte[1024];
        //把反饋信息存儲到info數組中,並記錄字節個數
        int length = in.read(info);
        //顯示反饋結果
        System.out.println( new String(info, 0, length) );
        
        //關閉流
        in.close();
        fileIn.close();
        out.close();
        socket.close();
    }
}

 

4.5  文件上傳案例多線程版本

 

 

 

 實現服務器端能夠同時接收多個客戶端上傳的文件。

咱們要修改服務器端代碼

 

 

/*
 * 文件上傳多線程版本, 服務器端
 */
public class TCPServer {
    public static void main(String[] args) throws IOException {
        //1,建立服務器,等待客戶端鏈接
        ServerSocket serverSocket = new ServerSocket(6666);
        
        //實現多個客戶端鏈接服務器的操做
        while(true){
            final Socket clientSocket = serverSocket.accept();
            //啓動線程,完成與當前客戶端的數據交互過程
            new Thread(){
                public void run() {
                    try{
                        //顯示哪一個客戶端Socket鏈接上了服務器
                        InetAddress ipObject = clientSocket.getInetAddress();//獲得IP地址對象
                        String ip = ipObject.getHostAddress(); //獲得IP地址字符串
                        System.out.println("小樣,抓到你了,鏈接我!!" + "IP:" + ip);
                        
                        //7,獲取Socket的輸入流
                        InputStream in = clientSocket.getInputStream();
                        //8,建立目的地的字節輸出流   D:\\upload\\192.168.74.58(1).jpg
                        BufferedOutputStream fileOut = new BufferedOutputStream(new FileOutputStream("D:\\upload\\"+ip+"("+System.currentTimeMillis()+").jpg"));
                        //9,把Socket輸入流中的數據,寫入目的地的字節輸出流中
                        byte[] buffer = new byte[1024];
                        int len = -1;
                        while((len = in.read(buffer)) != -1){
                            //寫入目的地的字節輸出流中
                            fileOut.write(buffer, 0, len);
                        }
                        
                        //-----------------反饋信息---------------------
                        //10,獲取Socket的輸出流, 做用:寫反饋信息給客戶端
                        OutputStream out = clientSocket.getOutputStream();
                        //11,寫反饋信息給客戶端
                        out.write("圖片上傳成功".getBytes());
                        
                        out.close();
                        fileOut.close();
                        in.close();
                        clientSocket.close();
                    } catch(IOException e){
                        e.printStackTrace();
                    }
                };
            }.start();
        }

        //serverSocket.close();
    }
}
相關文章
相關標籤/搜索