五十一、網(wǎng)絡通信協(xié)議

一、網(wǎng)絡通信協(xié)議概述

通過計算機網(wǎng)絡可以使多臺計算機實現(xiàn)連接,位于同一個網(wǎng)絡中的計算機在進行連接和通信時需要遵守一定的規(guī)則,這就好比在道路中行駛的汽車一定要遵守交通規(guī)則一樣。在計算機網(wǎng)絡中,這些連接和通信的規(guī)則被稱為網(wǎng)絡通信協(xié)議,它對數(shù)據(jù)的傳輸格式、傳輸速率、傳輸步驟等做了統(tǒng)一規(guī)定,通信雙方必須同時遵守才能完成數(shù)據(jù)交換。
網(wǎng)絡通信協(xié)議有很多種,目前應用最廣泛的是TCP/IP協(xié)議(Transmission Control Protocal/Internet Protocal傳輸控制協(xié)議/英特網(wǎng)互聯(lián)協(xié)議),它是一個包括TCP協(xié)議和IP協(xié)議,UDP(User Datagram Protocal)協(xié)議和其它一些協(xié)議的協(xié)議組,在學習具體協(xié)議之前首先了解一下TCP/IP協(xié)議組的層次結(jié)構(gòu)。
在進行數(shù)據(jù)傳輸時,要求發(fā)送的數(shù)據(jù)與收到的數(shù)據(jù)完全一樣,這時,就需要在原有的數(shù)據(jù)上添加很多信息,以保證數(shù)據(jù)在傳輸過程中數(shù)據(jù)格式完全一致。TCP/IP協(xié)議的層次結(jié)構(gòu)比較簡單,共分為四層,如圖所示。


TCP/IP網(wǎng)絡模型

上圖中,TCP/IP協(xié)議中的四層分別是應用層、傳輸層、網(wǎng)絡層和鏈路層,每層分別負責不同的通信功能,接下來針對這四層進行詳細地講解。

鏈路層:鏈路層是用于定義物理傳輸通道,通常是對某些網(wǎng)絡連接設備的驅(qū)動協(xié)議,例如針對光纖、網(wǎng)線提供的驅(qū)動。
網(wǎng)絡層:網(wǎng)絡層是整個TCP/IP協(xié)議的核心,它主要用于將傳輸?shù)臄?shù)據(jù)進行分組,將分組數(shù)據(jù)發(fā)送到目標計算機或者網(wǎng)絡。
傳輸層:主要使網(wǎng)絡程序進行通信,在進行網(wǎng)絡通信時,可以采用TCP協(xié)議,也可以采用UDP協(xié)議。
應用層:主要負責應用程序的協(xié)議,例如HTTP協(xié)議、FTP協(xié)議等。

二、IP地址和端口號

要想使網(wǎng)絡中的計算機能夠進行通信,必須為每臺計算機指定一個標識號,通過這個標識號來指定接受數(shù)據(jù)的計算機或者發(fā)送數(shù)據(jù)的計算機。
在TCP/IP協(xié)議中,這個標識號就是IP地址,它可以唯一標識一臺計算機,目前,IP地址廣泛使用的版本是IPv4,它是由4個字節(jié)大小的二進制數(shù)來表示,如:00001010000000000000000000000001。由于二進制形式表示的IP地址非常不便記憶和處理,因此通常會將IP地址寫成十進制的形式,每個字節(jié)用一個十進制數(shù)字(0-255)表示,數(shù)字間用符號“.”分開,如 “192.168.1.100”。
隨著計算機網(wǎng)絡規(guī)模的不斷擴大,對IP地址的需求也越來越多,IPV4這種用4個字節(jié)表示的IP地址面臨枯竭,因此IPv6 便應運而生了,IPv6使用16個字節(jié)表示IP地址,它所擁有的地址容量約是IPv4的8×1028倍,達到2128個(算上全零的),這樣就解決了網(wǎng)絡地址資源數(shù)量不夠的問題。
通過IP地址可以連接到指定計算機,但如果想訪問目標計算機中的某個應用程序,還需要指定端口號。在計算機中,不同的應用程序是通過端口號區(qū)分的。端口號是用兩個字節(jié)(16位的二進制數(shù))表示的,它的取值范圍是065535,其中,01023之間的端口號用于一些知名的網(wǎng)絡服務和應用,用戶的普通應用程序需要使用1024以上的端口號,從而避免端口號被另外一個應用或服務所占用。
接下來通過一個圖例來描述IP地址和端口號的作用,如下圖所示。

IP地址和端口號的作用

從上圖中可以清楚地看到,位于網(wǎng)絡中一臺計算機可以通過IP地址去訪問另一臺計算機,并通過端口號訪問目標計算機中的某個應用程序。

InetAddress

了解了IP地址的作用,我們看學習下JDK中提供了一個InetAdderss類,該類用于封裝一個IP地址,并提供了一系列與IP地址相關的方法。


InetAddress
InetAddress常用方法.png

上圖中,列舉了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.sxt.cn");
        System.out.println("本機的IP地址:" + local.getHostAddress());
        System.out.println("sxt的IP地址:" + remote.getHostAddress());
        System.out.println("sxt的主機名為:" + remote.getHostName());
    }
}

三、 UDP與TCP協(xié)議

在介紹TCP/IP結(jié)構(gòu)時,提到傳輸層的兩個重要的高級協(xié)議,分別是UDP和TCP,其中UDP是User Datagram Protocol的簡稱,稱為用戶數(shù)據(jù)報協(xié)議,TCP是Transmission Control Protocol的簡稱,稱為傳輸控制協(xié)議。

1.UDP協(xié)議

UDP是無連接通信協(xié)議,即在數(shù)據(jù)傳輸時,數(shù)據(jù)的發(fā)送端和接收端不建立邏輯連接。簡單來說,當一臺計算機向另外一臺計算機發(fā)送數(shù)據(jù)時,發(fā)送端不會確認接收端是否存在,就會發(fā)出數(shù)據(jù),同樣接收端在收到數(shù)據(jù)時,也不會向發(fā)送端反饋是否收到數(shù)據(jù)。
由于使用UDP協(xié)議消耗資源小,通信效率高,所以通常都會用于音頻、視頻和普通數(shù)據(jù)的傳輸例如視頻會議都使用UDP協(xié)議,因為這種情況即使偶爾丟失一兩個數(shù)據(jù)包,也不會對接收結(jié)果產(chǎn)生太大影響。
但是在使用UDP協(xié)議傳送數(shù)據(jù)時,由于UDP的面向無連接性,不能保證數(shù)據(jù)的完整性,因此在傳輸重要數(shù)據(jù)時不建議使用UDP協(xié)議。
UDP的交換過程如下圖所示:

UDP

1.1 DatagramPacket

前面介紹了UDP是一種面向無連接的協(xié)議,因此,在通信時發(fā)送端和接收端不用建立連接。UDP通信的過程就像是貨運公司在兩個碼頭間發(fā)送貨物一樣。在碼頭發(fā)送和接收貨物時都需要使用集裝箱來裝載貨物,UDP通信也是一樣,發(fā)送和接收的數(shù)據(jù)也需要使用“集裝箱”進行打包,為此JDK中提供了一個DatagramPacket類,該類的實例對象就相當于一個集裝箱,用于封裝UDP通信中發(fā)送或者接收的數(shù)據(jù)。
想要創(chuàng)建一個DatagramPacket對象,首先需要了解一下它的構(gòu)造方法。在創(chuàng)建發(fā)送端和接收端的DatagramPacket對象時,使用的構(gòu)造方法有所不同,接收端的構(gòu)造方法只需要接收一個字節(jié)數(shù)組來存放接收到的數(shù)據(jù),而發(fā)送端的構(gòu)造方法不但要接收存放了發(fā)送數(shù)據(jù)的字節(jié)數(shù)組,還需要指定發(fā)送端IP地址和端口號。
接下來根據(jù)API文檔的內(nèi)容,對DatagramPacket的構(gòu)造方法進行逐一詳細地講解:

DatagramPacket1.png

使用該構(gòu)造方法在創(chuàng)建DatagramPacket對象時,指定了封裝數(shù)據(jù)的字節(jié)數(shù)組和數(shù)據(jù)的大小,沒有指定IP地址和端口號。很明顯,這樣的對象只能用于接收端,不能用于發(fā)送端。因為發(fā)送端一定要明確指出數(shù)據(jù)的目的地(ip地址和端口號),而接收端不需要明確知道數(shù)據(jù)的來源,只需要接收到數(shù)據(jù)即可。

DatagramPacket2.png

使用該構(gòu)造方法在創(chuàng)建DatagramPacket對象時,不僅指定了封裝數(shù)據(jù)的字節(jié)數(shù)組和數(shù)據(jù)的大小,還指定了數(shù)據(jù)包的目標IP地址(addr)和端口號(port)。該對象通常用于發(fā)送端,因為在發(fā)送數(shù)據(jù)時必須指定接收端的IP地址和端口號,就好像發(fā)送貨物的集裝箱上面必須標明接收人的地址一樣。

DatagramPacket類中的常用方法:


DatagramPacket3.png
1.2 DatagramSocket

DatagramPacket數(shù)據(jù)包的作用就如同是“集裝箱”,可以將發(fā)送端或者接收端的數(shù)據(jù)封裝起來。然而運輸貨物只有“集裝箱”是不夠的,還需要有碼頭。在程序中需要實現(xiàn)通信只有DatagramPacket數(shù)據(jù)包也同樣不行,為此JDK中提供的一個DatagramSocket類。DatagramSocket類的作用就類似于碼頭,使用這個類的實例對象就可以發(fā)送和接收DatagramPacket數(shù)據(jù)包。
發(fā)送數(shù)據(jù)的過程如下圖所示:

DatagramSocket

在創(chuàng)建發(fā)送端和接收端的DatagramSocket對象時,使用的構(gòu)造方法也有所不同,下面對DatagramSocket類中常用的構(gòu)造方法進行講解:


DatagramPacket發(fā)送端.png

該構(gòu)造方法用于創(chuàng)建發(fā)送端的DatagramSocket對象,在創(chuàng)建DatagramSocket對象時,并沒有指定端口號,此時,系統(tǒng)會分配一個沒有被其它網(wǎng)絡程序所使用的端口號。

DatagramPacket接收端.png

該構(gòu)造方法既可用于創(chuàng)建接收端的DatagramSocket對象,又可以創(chuàng)建發(fā)送端的DatagramSocket對象,在創(chuàng)建接收端的DatagramSocket對象時,必須要指定一個端口號,這樣就可以監(jiān)聽指定的端口。

DatagramSocket類中的常用方法:


DatagramSocket類中的常用方法.png
1.3 UDP網(wǎng)絡程序

了解了DatagramPacket和DatagramSocket的作用,接下來通過一個案例來學習一下它們在程序中的具體用法。
下圖為UDP發(fā)送端與接收端交互圖解:


UDP發(fā)送端與接收端交互圖解.png

要實現(xiàn)UDP通信需要創(chuàng)建一個發(fā)送端程序和一個接收端程序,很明顯,在通信時只有接收端程序先運行,才能避免因發(fā)送端發(fā)送的數(shù)據(jù)無法接收,而造成數(shù)據(jù)丟失。因此,首先需要來完成接收端程序的編寫。

  • 接收端程序編寫步驟:

    1. 創(chuàng)建DatagramSocket對象
    2. 創(chuàng)建DatagramPacket對象
    3. 接收數(shù)據(jù)存儲到DatagramPacket對象中
    4. 獲取DatagramPacket對象的內(nèi)容
    5. 釋放流資源
  • 發(fā)送端程序編寫步驟:

    1. 創(chuàng)建DatagramSocket對象
    2. 創(chuàng)建DatagramPacket對象,并封裝數(shù)據(jù)
    3. 發(fā)送數(shù)據(jù)
    4. 釋放流資源
  • UDP完成數(shù)據(jù)的接收

/*
 * UDP接收端
 * 
 * 1,創(chuàng)建DatagramSocket對象
 * 2,創(chuàng)建DatagramPacket對象
 * 3,接收數(shù)據(jù)存儲到DatagramPacket對象中
 * 4,獲取DatagramPacket對象的內(nèi)容
 * 5,釋放流資源
 */
public class UDPReceive {
    public static void main(String[] args) throws IOException {
        //1,創(chuàng)建DatagramSocket對象,并指定端口號
        DatagramSocket receiveSocket = new DatagramSocket(12306);
        //2,創(chuàng)建DatagramPacket對象, 創(chuàng)建一個空的倉庫
        byte[] buffer = new byte[1024];
        DatagramPacket dp = new DatagramPacket(buffer, 1024);
        //3,接收數(shù)據(jù)存儲到DatagramPacket對象中
        receiveSocket.receive(dp);
        //4,獲取DatagramPacket對象的內(nèi)容
        //誰發(fā)來的數(shù)據(jù)  getAddress()
        InetAddress ipAddress = dp.getAddress();
        String ip = ipAddress.getHostAddress();//獲取到了IP地址
        //發(fā)來了什么數(shù)據(jù)  getData()
        byte[] data = dp.getData();
        //發(fā)來了多少數(shù)據(jù) getLenth()
        int length = dp.getLength();
        //顯示收到的數(shù)據(jù)
        String dataStr = new String(data,0,length);
        System.out.println("IP地址:"+ip+ "數(shù)據(jù)是"+ dataStr);
        //5,釋放流資源
        receiveSocket.close();
    }
}
  • UDP完成數(shù)據(jù)的發(fā)送
/*
* 發(fā)送端
 * 1,創(chuàng)建DatagramSocket對象
 * 2,創(chuàng)建DatagramPacket對象,并封裝數(shù)據(jù)
 * 3,發(fā)送數(shù)據(jù)
 * 4,釋放流資源
 */
public class UDPSend {
    public static void main(String[] args) throws IOException {
        //1,創(chuàng)建DatagramSocket對象
        DatagramSocket sendSocket = new DatagramSocket();
        //2,創(chuàng)建DatagramPacket對象,并封裝數(shù)據(jù)
        //public DatagramPacket(byte[] buf, int length, InetAddress address,  int port)
        //構(gòu)造數(shù)據(jù)報包,用來將長度為 length 的包發(fā)送到指定主機上的指定端口號。
        byte[] buffer = "hello,UDP".getBytes();
        DatagramPacket dp = new DatagramPacket(buffer, buffer.length, InetAddress.getByName("192.168.75.58"), 12306);
        //3,發(fā)送數(shù)據(jù)
        //public void send(DatagramPacket p) 從此套接字發(fā)送數(shù)據(jù)報包
        sendSocket.send(dp);
        //4,釋放流資源
        sendSocket.close();
    }
}


2.TCP協(xié)議

TCP協(xié)議是面向連接的通信協(xié)議,即在傳輸數(shù)據(jù)前先在發(fā)送端和接收端建立邏輯連接,然后再傳輸數(shù)據(jù),它提供了兩臺計算機之間可靠無差錯的數(shù)據(jù)傳輸。
在TCP連接中必須要明確客戶端與服務器端,由客戶端向服務端發(fā)出連接請求,每次連接的創(chuàng)建都需要經(jīng)過“三次握手”。
第一次握手,客戶端向服務器端發(fā)出連接請求,等待服務器確認;第二次握手,服務器端向客戶端回送一個響應,通知客戶端收到了連接請求;第三次握手,客戶端再次向服務器端發(fā)送確認信息,確認連接。
整個交互過程如下圖所示:

TCP協(xié)議.png

由于TCP協(xié)議的面向連接特性,它可以保證傳輸數(shù)據(jù)的安全性,所以是一個被廣泛采用的協(xié)議,例如在下載文件時,如果數(shù)據(jù)接收不完整,將會導致文件數(shù)據(jù)丟失而不能被打開,因此,下載文件時必須采用TCP協(xié)議。

TCP通信同UDP通信一樣,都能實現(xiàn)兩臺計算機之間的通信,通信的兩端都需要創(chuàng)建socket對象。
區(qū)別在于,UDP中只有發(fā)送端和接收端,不區(qū)分客戶端與服務器端,計算機之間可以任意地發(fā)送數(shù)據(jù)。
而TCP通信是嚴格區(qū)分客戶端與服務器端的,在通信時,必須先由客戶端去連接服務器端才能實現(xiàn)通信,服務器端不可以主動連接客戶端,并且服務器端程序需要事先啟動,等待客戶端的連接。
在JDK中提供了兩個類用于實現(xiàn)TCP程序,一個是ServerSocket類,用于表示服務器端,一個是Socket類,用于表示客戶端。
通信時,首先創(chuàng)建代表服務器端的ServerSocket對象,該對象相當于開啟一個服務,并等待客戶端的連接,然后創(chuàng)建代表客戶端的Socket對象向服務器端發(fā)出連接請求,服務器端響應請求,兩者建立連接開始通信。

2.1 ServerSocket

通過前面的學習知道,在開發(fā)TCP程序時,首先需要創(chuàng)建服務器端程序。JDK的java.net包中提供了一個ServerSocket類,該類的實例對象可以實現(xiàn)一個服務器段的程序。通過查閱API文檔可知,ServerSocket類提供了多種構(gòu)造方法。


ServerSocket構(gòu)造方法

ServerSocket的常用方法:


ServerSocket的常用方法

ServerSocket對象負責監(jiān)聽某臺計算機的某個端口號,在創(chuàng)建ServerSocket對象后,需要繼續(xù)調(diào)用該對象的accept()方法,接收來自客戶端的請求。當執(zhí)行了accept()方法之后,服務器端程序會發(fā)生阻塞,直到客戶端發(fā)出連接請求,accept()方法才會返回一個Scoket對象用于和客戶端實現(xiàn)通信,程序才能繼續(xù)向下執(zhí)行。

2.2 Socket

講解了ServerSocket對象可以實現(xiàn)服務端程序,但只實現(xiàn)服務器端程序還不能完成通信,此時還需要一個客戶端程序與之交互,為此JDK提供了一個Socket類,用于實現(xiàn)TCP客戶端程序。
通過查閱API文檔可知Socket類同樣提供了多種構(gòu)造方法:


Socket構(gòu)造方法.png

使用該構(gòu)造方法在創(chuàng)建Socket對象時,會根據(jù)參數(shù)去連接在指定地址和端口上運行的服務器程序,其中參數(shù)host接收的是一個字符串類型的IP地址。


Socket構(gòu)造方法2.png

該方法在使用上與第二個構(gòu)造方法類似,參數(shù)address用于接收一個InetAddress類型的對象,該對象用于封裝一個IP地址。
在以上Socket的構(gòu)造方法中,最常用的是第一個構(gòu)造方法。
接下來學習一下Socket的常用方法:


Socket的常用方法

在Socket類的常用方法中,getInputStream()和getOutStream()方法分別用于獲取輸入流和輸出流。當客戶端和服務端建立連接后,數(shù)據(jù)是以IO流的形式進行交互的,從而實現(xiàn)通信。
接下來通過一張圖來描述服務器端和客戶端的數(shù)據(jù)傳輸:


服務器端和客戶端的數(shù)據(jù)傳輸.png
2.3 簡單的TCP網(wǎng)絡程序

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


TCP通信.png

要實現(xiàn)TCP通信需要創(chuàng)建一個服務器端程序和一個客戶端程序,為了保證數(shù)據(jù)傳輸?shù)陌踩?,首先需要實現(xiàn)服務器端程序。

  • 服務器端實現(xiàn)步驟:

    1. 創(chuàng)建服務器端ServerSocket對象,指定服務器端端口號
    2. 開啟服務器,等待著客戶端Socket對象的連接,如有客戶端連接,返回客戶端的Socket對象
    3. 通過客戶端的Socket對象,獲取客戶端的輸入流,為了實現(xiàn)獲取客戶端發(fā)來的數(shù)據(jù)
    4. 通過客戶端的輸入流,獲取流中的數(shù)據(jù)
    5. 通過客戶端的Socket對象,獲取客戶端的輸出流,為了實現(xiàn)給客戶端反饋信息
    6. 通過客戶端的輸出流,寫數(shù)據(jù)到流中
    7. 關閉流資源
  • 客戶端實現(xiàn)步驟:

    1. 創(chuàng)建客戶端的Socket對象
    2. 獲取Socket的輸出流對象
    3. 寫數(shù)據(jù)給服務器
    4. 獲取Socket的輸入流對象
    5. 使用輸入流,讀反饋信息
    6. 關閉流資源

服務器端程序:

/*
 * TCP 服務器端
 * 
 * 1,創(chuàng)建服務器ServerSocket對象(指定服務器端口號)
 * 2,開啟服務器了,等待客戶端的連接,當客戶端連接后,可以獲取到連接服務器的客戶端Socket對象
 * 3,給客戶端反饋信息
 * 4,關閉流資源
 */
public class TCPServer {
    public static void main(String[] args) throws IOException {
        //1,創(chuàng)建服務器ServerSocket對象(指定服務器端口號)
        ServerSocket ss = new ServerSocket(8888);
        //2,開啟服務器了,等待客戶端的連接,當客戶端連接后,可以獲取到連接服務器的客戶端Socket對象
        Socket s = ss.accept();
        //3,給客戶端反饋信息
        /*
         * a,獲取客戶端的輸出流
         * b,在服務端端,通過客戶端的輸出流寫數(shù)據(jù)給客戶端
         */
        //a,獲取客戶端的輸出流
        OutputStream out = s.getOutputStream();
        //b,在服務端端,通過客戶端的輸出流寫數(shù)據(jù)給客戶端
        out.write("你已經(jīng)連接上了服務器".getBytes());
        //4,關閉流資源
        out.close();
        s.close();
        //ss.close();  服務器流 通常都是不關閉的
    }
}

客戶端程序:

/*
 * TCP 客戶端
 * 
 * 1,創(chuàng)建客戶端Socket對象,(指定要連接的服務器地址與端口號)
 * 2,獲取服務器端的反饋回來的信息
 * 3,關閉流資源
 */
public class TCPClient {
    public static void main(String[] args) throws IOException {
        //1,創(chuàng)建客戶端Socket對象,(指定要連接的服務器地址與端口號)
        Socket s = new Socket("192.168.74.58", 8888);
        //2,獲取服務器端的反饋回來的信息
        InputStream in = s.getInputStream();
        //獲取獲取流中的數(shù)據(jù)
        byte[] buffer = new byte[1024];
        //把流中的數(shù)據(jù)存儲到數(shù)組中,并記錄讀取字節(jié)的個數(shù)
        int length = in.read(buffer);
        //顯示數(shù)據(jù)
        System.out.println( new String(buffer, 0 , length) );
        //3,關閉流資源
        in.close();
        s.close();
    }
}


四、 實例:文件上傳

目前大多數(shù)服務器都會提供文件上傳的功能,由于文件上傳需要數(shù)據(jù)的安全性和完整性,很明顯需要使用TCP協(xié)議來實現(xiàn)。接下來通過一個案例來實現(xiàn)圖片上傳的功能。

1.單線程實現(xiàn)文件上傳

文件上傳.png
  • 首先編寫服務器端程序,用來接收圖片。
/*
 * 文件上傳  服務器端
 *
 */
public class TCPServer {
    public static void main(String[] args) throws IOException {
        //1,創(chuàng)建服務器,等待客戶端連接
        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,創(chuàng)建目的地的字節(jié)輸出流   D:\\upload\\192.168.74.58(1).jpg
        BufferedOutputStream fileOut = new BufferedOutputStream(new FileOutputStream("D:\\upload\\192.168.74.58(1).jpg"));
        //9,把Socket輸入流中的數(shù)據(jù),寫入目的地的字節(jié)輸出流中
        byte[] buffer = new byte[1024];
        int len = -1;
        while((len = in.read(buffer)) != -1){
            //寫入目的地的字節(jié)輸出流中
            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的輸出流,間接的相當于告知了服務器數(shù)據(jù)寫入完畢
 */
public class TCPClient {
    public static void main(String[] args) throws IOException {
        //2,創(chuàng)建客戶端Socket,連接服務器
        Socket socket = new Socket("192.168.74.58", 8888);
        //3,獲取Socket流中的輸出流,功能:用來把數(shù)據(jù)寫到服務器
        OutputStream out = socket.getOutputStream();
        //4,創(chuàng)建字節(jié)輸入流,功能:用來讀取數(shù)據(jù)源(圖片)的字節(jié)
        BufferedInputStream fileIn = new BufferedInputStream(new FileInputStream("D:\\NoDir\\test.jpg"));
        //5,把圖片數(shù)據(jù)寫到Socket的輸出流中(把數(shù)據(jù)傳給服務器)
        byte[] buffer = new byte[1024];
        int len = -1;
        while ((len = fileIn.read(buffer)) != -1){
            //把數(shù)據(jù)寫到Socket的輸出流中
            out.write(buffer, 0, len);
        }
        //6,客戶端發(fā)送數(shù)據(jù)完畢,結(jié)束Socket輸出流的寫入操作,告知服務器端
        socket.shutdownOutput();

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

2.多線程實現(xiàn)文件上傳

實現(xiàn)服務器端可以同時接收多個客戶端上傳的文件,主要修改服務器端。

文件上傳案例多線程版本.png

  • 修改服務器端代碼
/*
 * 文件上傳多線程版本, 服務器端
 */
public class TCPServer {
    public static void main(String[] args) throws IOException {
        //1,創(chuàng)建服務器,等待客戶端連接
        ServerSocket serverSocket = new ServerSocket(6666);
        
        //實現(xiàn)多個客戶端連接服務器的操作
        while(true){
            final Socket clientSocket = serverSocket.accept();
            //啟動線程,完成與當前客戶端的數(shù)據(jù)交互過程
            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,創(chuàng)建目的地的字節(jié)輸出流   D:\\upload\\192.168.74.58(1).jpg
                        BufferedOutputStream fileOut = new BufferedOutputStream(new FileOutputStream("D:\\upload\\"+ip+"("+System.currentTimeMillis()+").jpg"));
                        //9,把Socket輸入流中的數(shù)據(jù),寫入目的地的字節(jié)輸出流中
                        byte[] buffer = new byte[1024];
                        int len = -1;
                        while((len = in.read(buffer)) != -1){
                            //寫入目的地的字節(jié)輸出流中
                            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();
    }
}
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 網(wǎng)絡編程 網(wǎng)絡編程對于很多的初學者來說,都是很向往的一種編程技能,但是很多的初學者卻因為很長一段時間無法進入網(wǎng)絡編...
    程序員歐陽閱讀 2,104評論 1 37
  • 計算機網(wǎng)絡概述 網(wǎng)絡編程的實質(zhì)就是兩個(或多個)設備(例如計算機)之間的數(shù)據(jù)傳輸。 按照計算機網(wǎng)絡的定義,通過一定...
    蛋炒飯_By閱讀 1,365評論 0 10
  • 一、網(wǎng)絡通信協(xié)議 定義:對數(shù)據(jù)的傳輸格式、傳輸效率、傳輸步驟等做了統(tǒng)一規(guī)定,通信雙方必須同時遵守才能完成數(shù)據(jù)交換,...
    聶叼叼閱讀 590評論 0 2
  • 參考:http://www.2cto.com/net/201611/569006.html TCP HTTP UD...
    F麥子閱讀 3,067評論 0 14
  • 1.這篇文章不是本人原創(chuàng)的,只是個人為了對這部分知識做一個整理和系統(tǒng)的輸出而編輯成的,在此鄭重地向本文所引用文章的...
    SOMCENT閱讀 13,352評論 6 174

友情鏈接更多精彩內(nèi)容