天天看點

java網絡程式設計(TCP,UDP)一、UDP與TCP協定二、UDP通信三、TCP通信

一、UDP與TCP協定

1.1 UDP協定

  UDP是無連接配接通信協定,即在資料傳輸時,資料的發送端和接收端不建立邏輯連接配接。簡單來說,當一台計算機向另外一台計算機發送資料時,發送端不會确認接收端是否存在,就會發出資料,同樣接收端在收到資料時,也不會向發送端回報是否收到資料。

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

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

java網絡程式設計(TCP,UDP)一、UDP與TCP協定二、UDP通信三、TCP通信

1.2 TCP協定

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

java網絡程式設計(TCP,UDP)一、UDP與TCP協定二、UDP通信三、TCP通信

由于TCP協定的面向連接配接特性,它可以保證傳輸資料的安全性,是以是一個被廣泛采用的協定,例如在下載下傳檔案時,如果資料接收不完整,将會導緻檔案資料丢失而不能被打開,是以,下載下傳檔案時必須采用TCP協定

二、UDP通信

2.1、DatagramPacket

前面介紹了UDP是一種面向無連接配接的協定,是以,在通信時發送端和接收端不用建立連接配接。UDP通信的過程就像是貨運公司在兩個碼頭間發送貨物一樣。在碼頭發送和接收貨物時都需要使用集裝箱來裝載貨物,UDP通信也是一樣,發送和接收的資料也需要使用“集裝箱”進行打包,為此JDK中提供了一個DatagramPacket類,該類的執行個體對象就相當于一個集裝箱,用于封裝UDP通信中發送或者接收的資料。

想要建立一個DatagramPacket對象,首先需要了解一下它的構造方法。在建立發送端和接收端的DatagramPacket對象時,使用的構造方法有所不同,接收端的構造方法隻需要接收一個位元組數組來存放接收到的資料,而發送端的構造方法不但要接收存放了發送資料的位元組數組,還需要指定發送端IP位址和端口号。

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

java網絡程式設計(TCP,UDP)一、UDP與TCP協定二、UDP通信三、TCP通信

  使用該構造方法在建立DatagramPacket對象時,指定了封裝資料的位元組數組和資料的大小,沒有指定IP位址和端口号。很明顯,這樣的對象隻能用于接收端,不能用于發送端。因為發送端一定要明确指出資料的目的地(ip位址和端口号),而接收端不需要明确知道資料的來源,隻需要接收到資料即可。

java網絡程式設計(TCP,UDP)一、UDP與TCP協定二、UDP通信三、TCP通信

  使用該構造方法在建立DatagramPacket對象時,不僅指定了封裝資料的位元組數組和資料的大小,還指定了資料包的目标IP位址(addr)和端口号(port)。該對象通常用于發送端,因為在發送資料時必須指定接收端的IP位址和端口号,就好像發送貨物的集裝箱上面必須标明接收人的位址一樣。

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

java網絡程式設計(TCP,UDP)一、UDP與TCP協定二、UDP通信三、TCP通信

DatagramPacket資料包的作用就如同是“集裝箱”,可以将發送端或者接收端的資料封裝起來。然而運輸貨物隻有“集裝箱”是不夠的,還需要有碼頭。在程式中需要實作通信隻有DatagramPacket資料包也同樣不行,為此JDK中提供的一個DatagramSocket類。DatagramSocket類的作用就類似于碼頭,使用這個類的執行個體對象就可以發送和接收DatagramPacket資料包,發送資料的過程如下圖所示。

java網絡程式設計(TCP,UDP)一、UDP與TCP協定二、UDP通信三、TCP通信

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

java網絡程式設計(TCP,UDP)一、UDP與TCP協定二、UDP通信三、TCP通信

   該構造方法用于建立發送端的DatagramSocket對象,在建立DatagramSocket對象時,并沒有指定端口号,此時,系統會配置設定一個沒有被其它網絡程式所使用的端口号。

java網絡程式設計(TCP,UDP)一、UDP與TCP協定二、UDP通信三、TCP通信

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

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

java網絡程式設計(TCP,UDP)一、UDP與TCP協定二、UDP通信三、TCP通信

2.2、UDP網絡程式

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

 要實作UDP通信需要建立一個發送端程式和一個接收端程式,很明顯,在通信時隻有接收端程式先運作,才能避免因發送端發送的資料無法接收,而造成資料丢失。是以,首先需要來完成接收端程式的編寫。

UDP完成資料的發送

package com.zhouzhiyao.UDP;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

public class UDPSendDemo {
    public static void main(String[] args) {
        DatagramSocket socket = null;
        try {
            //建立基站
            socket = new DatagramSocket();
            byte[] buf = "hello,UDP".getBytes();
            InetAddress address;
            address = InetAddress.getByName("localhost");
            //建立倉庫
            DatagramPacket packet = new DatagramPacket(buf, buf.length, address,12345);
            //發送資料
            socket.send(packet);
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            //關閉基站
            socket.close();
        }
    }
}
           

 UDP完成資料的接收

package com.zhouzhiyao.UDP;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class UDPReceiveDemo {
    public static void main(String[] args) {
        DatagramSocket socket = null;
        try {
            socket = new DatagramSocket(12345);//建立基站
            byte[] buf = new byte[1024];
            DatagramPacket packet=new DatagramPacket(buf, buf.length);//建立機房
            socket.receive(packet);//開始接受資料

            //擷取對方的主機資訊
            InetAddress address = packet.getAddress();
            System.out.println(address.getHostAddress());
            //擷取資料内容
            byte[] data = packet.getData();
            System.out.println("資料内容:"+new String(data,0,packet.getLength()));
            //擷取資料長度
            int length = packet.getLength();
            System.out.println("資料長度:"+length);

            //擷取接收端口号
            int port = packet.getPort();
            System.out.println("接收端口号是:"+port);

        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            socket.close();
        }
    }

}
           

三、TCP通信

TCP通信同UDP通信一樣,都能實作兩台計算機之間的通信,通信的兩端都需要建立socket對象。

差別在于,UDP中隻有發送端和接收端,不區分用戶端與伺服器端,計算機之間可以任意地發送資料。

而TCP通信是嚴格區分用戶端與伺服器端的,在通信時,必須先由用戶端去連接配接伺服器端才能實作通信,伺服器端不可以主動連接配接用戶端,并且伺服器端程式需要事先啟動,等待用戶端的連接配接。

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

通信時,首先建立代表伺服器端的ServerSocket對象,該對象相當于開啟一個服務,并等待用戶端的連接配接,然後建立代表用戶端的Socket對象向伺服器端發出連接配接請求,伺服器端響應請求,兩者建立連接配接開始通信。

3.1、ServerSocket

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

java網絡程式設計(TCP,UDP)一、UDP與TCP協定二、UDP通信三、TCP通信

使用該構造方法在建立ServerSocket對象時,就可以将其綁定到一個指定的端口号上(參數port就是端口号)。

接下來學習一下ServerSocket的常用方法,如表所示。

java網絡程式設計(TCP,UDP)一、UDP與TCP協定二、UDP通信三、TCP通信

ServerSocket對象負責監聽某台計算機的某個端口号,在建立ServerSocket對象後,需要繼續調用該對象的accept()方法,接收來自用戶端的請求。當執行了accept()方法之後,伺服器端程式會發生阻塞,直到用戶端發出連接配接請求,accept()方法才會傳回一個Scoket對象用于和用戶端實作通信,程式才能繼續向下執行。

3.2、Socket

講解了ServerSocket對象可以實作服務端程式,但隻實作伺服器端程式還不能完成通信,此時還需要一個用戶端程式與之互動,為此JDK提供了一個Socket類,用于實作TCP用戶端程式。

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

java網絡程式設計(TCP,UDP)一、UDP與TCP協定二、UDP通信三、TCP通信

  

使用該構造方法在建立Socket對象時,會根據參數去連接配接在指定位址和端口上運作的伺服器程式,其中參數host接收的是一個字元串類型的IP位址。

java網絡程式設計(TCP,UDP)一、UDP與TCP協定二、UDP通信三、TCP通信

該方法在使用上與第二個構造方法類似,參數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流的形式進行互動的,進而實作通信。

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

java網絡程式設計(TCP,UDP)一、UDP與TCP協定二、UDP通信三、TCP通信

3.3 簡單的TCP網絡程式

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

服務端

package com.zhouzhiyao.TCP;

import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class TCPServer {
    public static void main(String[] args) {
        ServerSocket socket = null;
        OutputStream outputStream = null;
        try {
            //建立基站
            socket = new ServerSocket(8899);
            //開始開啟接收模式,接到後傳回用戶端的socket對象
            Socket client = socket.accept();
            //擷取向用戶端發送消息的對象流
            outputStream = client.getOutputStream();
            //向用戶端寫資料
            outputStream.write("你連上了伺服器...".getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                outputStream.close();
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
           

用戶端

package com.zhouzhiyao.TCP;

import java.io.InputStream;
import java.net.Socket;

public class TCPClient {
    public static void main(String[] args) {
        Socket socket = null;
        InputStream inputStream = null;
        try {
            //建立基站擷取連結位址及端口号
            socket = new Socket("localhost", 8899);
            //擷取伺服器發過來的位元組流
            inputStream = socket.getInputStream();

            //開始解析位元組流
            byte[] b = new byte[1024];
            String str = "";
            int length = -1;
            while ((length = inputStream.read(b, 0, b.length)) != -1) {
                str += new String(b, 0, length);
            }
            System.out.println(str);
            /*
             * int length = inputStream.read(b);
            System.out.println(new String(b, 0, length));
             */

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //最後關閉
            try {
                inputStream.close();
                socket.close();
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
    }
}
           

3.4、檔案上傳案例

服務端

package com.zhouzhiyao.TCPImg;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) {
        ServerSocket socket = null;
        Socket client=null;;
        InputStream in = null;
        FileOutputStream fOutputStream = null;
        OutputStream out = null;
        try {
            socket = new ServerSocket(8899);//建立伺服器,等待用戶端連接配接
            client = socket.accept();//擷取用戶端連接配接
            InetAddress clientInetAddress = client.getInetAddress();//擷取用戶端資訊
            System.out.println(clientInetAddress.getHostAddress()+" 已經連接配接.");

            in = client.getInputStream();//擷取socket輸入流

            //===========将輸入流寫入檔案
            fOutputStream = new FileOutputStream(new File("1.png"));
            byte[] b = new byte[1024];
            int lenght=-1;
            while((lenght = in.read(b))!=-1){
                fOutputStream.write(b, 0, lenght);
            }

            //像用戶端回報資訊
            out = client.getOutputStream();
            out.write("圖檔上傳成功ok!".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            try {
                out.close();
                fOutputStream.close();
                in.close();
                client.close();
                socket.close();
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }

    }
}
           

用戶端

package com.zhouzhiyao.TCPImg;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class Client {

    public static void main(String[] args) {
        Socket socket = null;
        OutputStream out = null;
        FileInputStream fInputStream = null;
        InputStream in = null;
        try {
            //建立socket,連接配接伺服器
            socket = new Socket("localhost", 8899);
            //獲得socket中的輸出流
            out = socket.getOutputStream();
            //擷取本地檔案,并且傳給伺服器
            fInputStream = new FileInputStream(new File("E:/在尚學堂/java基礎/day02/位運算.png"));
            byte[] b=new byte[1024];
            int length=-1;
            while((length = fInputStream.read(b))!=-1){
                out.write(b, 0, length);//寫給伺服器
            }

            socket.shutdownOutput();//用戶端傳送完畢後,必須關閉傳送流,告知伺服器


            //擷取socket中的輸入流,來讀取伺服器發過來的消息
            in = socket.getInputStream();
            byte[] b1=new byte[1024];
            int len = in.read(b1);
            System.out.println(new String(b1,0,len));


        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            try {
                in.close();
                out.close();
                fInputStream.close();
                socket.close();
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
    }
}
           

服務端多線程代碼

package com.zhouzhiyao.TCPImg;

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

public class ThreadServer {
    public static void main(String[] args) {
        ServerSocket socket;
        try {
            socket = new ServerSocket(8899);
            System.out.println("等待連接配接...");
            while (true) {
                final Socket client = socket.accept();
                InetAddress inetAddress = client.getInetAddress();
                System.out.println(inetAddress.getHostAddress()+" 連接配接成功!");
                new Thread() {
                    public void run() {
                        try {
                            InputStream in = client.getInputStream();
                            BufferedOutputStream bufferedOutputStream=new BufferedOutputStream(new FileOutputStream(System.currentTimeMillis()+".jpg"));
                            byte[] b=new byte[1024];
                            int len=-1;
                            while((len=in.read(b))!=-1){
                                bufferedOutputStream.write(b, 0, len);
                            }


                            OutputStream out = client.getOutputStream();
                            out.write("圖檔上傳成功".getBytes());
                            out.close();

                            bufferedOutputStream.close();
                            in.close();
                            client.close();

                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    };
                }.start();
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}