1. 程式人生 > >Java語法進階14-網路程式設計

Java語法進階14-網路程式設計

網路程式設計

軟體結構

C/S結構 :全稱為Client/Server結構,是指客戶端和伺服器結構。

B/S結構 :全稱為Browser/Server結構,是指瀏覽器和伺服器結構。

網路通訊協議

網路通訊協議:位於同一個網路中的計算機在進行連線和通訊時需要遵守一定的規則,它對資料的傳輸格式、傳輸速率、傳輸步驟等做了統一規定

TCP/IP協議:它定義了計算機如何連入因特網,以及資料如何在它們之間傳輸的標準。它的內部包含一系列的用於處理資料通訊的協議,每一層都呼叫它的下一層所提供的協議來完成自己的需求

  • 應用層:網路服務與終端使用者的一個介面。協議有:HTTP、FTP、SMTP、DNS、TELNET、HTTPS、POP3等等。

  • 表示層:資料的表示、安全、壓縮。格式有:JPEG、ASCll、DECOIC、加密格式等。

  • 會話層:建立、管理、終止會話。對應主機程序,指本地主機與遠端主機正在進行的會話

  • 傳輸層:定義傳輸資料的協議埠號,以及流控和差錯校驗。協議有:TCP、UDP。

  • 網路層:進行邏輯地址定址,實現不同網路之間的路徑選擇。協議有:ICMP、IGMP、IP(IPV4 IPV6)、ARP、RARP。

  • 資料鏈路層:建立邏輯連線、進行硬體地址定址、差錯校驗等功能。將位元組合成位元組進而組合成幀,用MAC地址訪問介質,錯誤發現但不能糾正。

  • 物理層:建立、維護、斷開物理連線。

IP(internet protocal)又稱為網際網路協議。IP的責任就是把資料從源傳送到目的地。它在源地址和目的地址之間傳送一種稱之為資料包的東西,它還提供對資料大小的重新組裝功能,以適應不同網路對包大小的要求。經常與IP協議放在一起的還有TCP(Transmission Control Protocol)協議

TCP與UDP協議

UDP:使用者資料報協議(User Datagram Protocol)。

  • 非面向連線的,不可靠的:傳送端不會確認接收端是否存在,就會發出資料,同樣接收端在收到資料時,也不會向傳送端反饋是否收到資料。
  • 大小限制的:資料被限制在64kb以內,超出這個範圍就不能傳送了。
  • 資料報(Datagram):網路傳輸的基本單位

TCP:傳輸控制協議 (Transmission Control Protocol)。

  • 面向連線的,可靠的:在傳送端和接收端建立邏輯連線,然後再傳輸資料,是一種面向連線的、可靠的、基於位元組流的傳輸層的通訊協議,可以連續傳輸大量的資料。TCP協議保證了資料包在傳送中準確無誤,TCP協議使用重發機制,需要收到另一個通訊實體的確認資訊
  • 三次握手

  • 四次揮手

網路程式設計三要素

1、協議:如上

2、IP地址

IP地址用來給一個網路中的計算機裝置做唯一的編號,

IPv4:32位整數,8位一組最多可以表示42億個

IPv6:採用128位地址長度,每16個位元組一組,分成8組十六進位制數

公網地址( 全球資訊網使用)和 私有地址( 區域網使用)。192.168.開頭的就是私有址址,範圍即為192.168.0.0--192.168.255.255,專門為組織機構內部使用

特殊的IP地址:

  • 本地迴環地址(hostAddress):127.0.0.1

  • 主機名(hostName):localhost

域名:域名伺服器(DNS)負責將域名轉化成IP地址,方便記憶。

3、埠號

埠號可以找到唯一標識裝置中的程序(應用程式)0~65535,動態/ 私有埠:49152~65535

如果埠號被另外一個服務或應用所佔用,會導致當前程式啟動失敗。

利用協議+IP地址+埠號 三元組合,就可以標識網路中的程序了,那麼程序間的通訊就可以利用這個標識與其它程序進行互動。

InetAddress類

InetAddress類主要表示IP地址,兩個子類:Inet4Address、Inet6Address。

lInetAddress 類沒有提供公共的構造器,而是提供 了 如下幾個 靜態方法來獲取InetAddress 例項

  • public static InetAddress getLocalHost()                              【返回本地主機】

  • public static InetAddress getByName(String host)                【在給定主機名的情況下確定主機的 IP 地址】

  • public static InetAddress getByAddress(byte[] addr)            【在給定原始 IP 地址的情況下,返回 InetAddress 物件】

  • 例:byte[] addr = {(byte)192,(byte)168,24,56}; 其內部用一個int儲存

InetAddress 提供瞭如下幾個常用的方法

  • public String getHostAddress() :                                   【返回 IP 地址字串(以文字表現形式)】

  • public String getHostName() :                                            【獲取此 IP 地址的主機名】

  • public String getCanonicalHostName():                             【獲取此 IP 地址的完全限定域名】
  • boolean isReachable(int timeout)                                         【測試是否可以達到該地址。】

Socket

socket 可理解為一個介於應用層與協議層之間的一個抽象層,它遮蔽了各個協議的通訊細節,使得程式設計師無需關注協議本身,直接使用socket提供的介面來進行互聯的不同主機間的程序的通訊。

就是提供了tcp/ip協議的抽象,對外提供了一套介面,通過這個介面就可以統一、方便的使用tcp/ip協議的功能了

通訊的兩端都要有Socket(也可以叫“套接字”),是兩臺機器間通訊的端點。網路通訊其實就是Socket間的通訊。也是負責和網絡卡驅動程式溝通的物件。socket工作流程

socket是兩個主機通訊的關鍵,先理解IO流的工作流程有助於理解網路間的通訊,socket資料的傳送與接收也可簡單的理解為:

客戶端將要傳送的資料通過send()傳送給客戶端的tcp/udp協議的緩衝區,由客戶端協議傳送給服務端的tcp/udp協議,服務端的receive()會讀取服務端的協議緩衝區接收到的資料,

如需返回資料再經服務端的send()傳送給服務端的協議緩衝區,服務端的協議再發送給客戶端的協議,客戶端的receive()會讀取客戶端協議緩衝區中的資料如此迴圈,直到close()

send()與receive()函式

  • ServerSocket:此類實現TCP伺服器套接字。伺服器套接字等待請求通過網路傳入。流套接字

  • Socket:此類實現客戶端套接字(也可以就叫“套接字”)。套接字是兩臺機器間通訊的端點。流套接字

  • DatagramSocket:此類表示用來發送和接收UDP資料報包的套接字。資料報套接字

TCP網路程式設計

1、伺服器端

  • 呼叫 ServerSocket(int port) :建立一個伺服器端套接字,並繫結到指定埠上。用於監聽客戶端的請求。

  • 呼叫 accept() :監聽連線請求,如果客戶端請求連線,則接受連線,返回通訊套接字物件。

  • 呼叫 該Socket 類物件的 getOutputStream() 和 getInputStream () :獲取輸出流和輸入流,開始網路資料的傳送和接收。

  • 關閉Socket 物件:客戶端訪問結束,關閉通訊套接字。

2、客戶端

  • 建立 Socket:根據指定服務端的 IP 地址或埠號構造 Socket 類物件。若伺服器端響應,則建立客戶端到伺服器的通訊線路。若連線失敗,會出現異常。

  • 開啟連線到 Socket 的輸入/出流: 使用 getInputStream()方法獲得輸入流,使用getOutputStream()方法獲得輸出流,進行資料傳輸

  • 按照一定的協議對 Socket 進行讀/ 寫操作:通過輸入流讀取伺服器放入線路的資訊(但不能讀取自己放入線路的資訊),通過輸出流將資訊寫入線路。

  • 關閉 Socket:斷開客戶端到伺服器的連線,釋放線路

API

ServerSocket類的構造方法:

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

ServerSocket類的常用方法:

  • Socket accept()                                                             【偵聽並接受到此套接字的連線。】 

  • InetAddress getInetAddress()                                       【返回此伺服器套接字的本地地址。】
  • int getLocalPort()                                                          【返回此套接字在其上偵聽的埠。】 
  • void close():                                                                  【關閉此套接字。】

Socket類的常用構造方法:

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

  • public Socket(String host,int port):                              【建立一個流套接字並將其連線到指定主機上的指定埠號】

Socket類的常用方法:

  • public InputStream getInputStream():                         【返回此套接字的輸入流,可以用於接收訊息】

  • public OutputStream getOutputStream():                   【返回此套接字的輸出流,可以用於傳送訊息】

  • public InetAddress getInetAddress():                         【返回此套接字連線到的遠端 IP 地址;如果套接字是未連線的,則返回 null】

  • public InetAddress getLocalAddress():                       【獲取套接字繫結的本地地址】

  • public int getPort():                                                     【返回此套接字連線到的遠端埠號;如果尚未連線套接字,則返回 0】

  • public int getLocalPort():                                           【返回此套接字繫結到的本地埠。如果尚未繫結套接字,則返回 -1】

  • public void close():             【關閉套接字(即無法重新連線或重新繫結) 同時也將會關閉該套接字的 InputStream 和 OutputStream】

  • public void shutdownInput():

如果在套接字上呼叫 shutdownInput() 後從套接字輸入流讀取內容,則流將返回 EOF(檔案結束符)。 即不能在從此套接字的輸入流中接收任何資料。關閉輸入流

  • public void shutdownOutput():

禁用此套接字的輸出流。對於 TCP 套接字,任何以前寫入的資料都將被髮送,並且後跟 TCP 的正常連線終止序列。 如果在套接字上呼叫 shutdownOutput() 後寫入套接字輸出流,則該流將丟擲 IOException。 即不能通過此套接字的輸出流傳送任何資料。關閉輸出流

  • boolean isInputShutdown() :                                      【返回是否關閉套接字連線的半讀狀態 (read-half)】
  • boolean isOutputShutdown() :                                   【返回是否關閉套接字連線的半寫狀態 (write-half)】

注意:先後呼叫Socket的shutdownInput()和shutdownOutput()方法,僅僅關閉了輸入流和輸出流,並不等於呼叫Socket的close()方法。在通訊結束後,仍然要呼叫Scoket的close()方法,因為只有該方法才會釋放Socket佔用的資源,比如佔用的本地埠號等。

如果伺服器端要“同時”處理多個客戶端的請求,因此伺服器端需要為每一個客戶端單獨分配一個執行緒來處理,否則無法實現“同時”。

UDP網路程式設計

UDP(User Datagram Protocol,使用者資料報協議)特點:

在正式通訊前不必與對方先建立連線,至於對方是否可以接收到這些資料內容,UDP協議無法控制,無連線的好處就是快,省記憶體空間和流量,沒有TCP的確認機制、重傳機制,如果因為網路原因沒有傳送到對端,UDP也不會給應用層返回錯誤資訊。

UDP協議是面向資料報文的資訊傳送服務。UDP在傳送端沒有緩衝區,對於應用層交付下來的報文在添加了首部之後就直接交付於ip層,不會進行合併,也不會進行拆分,而是一次交付一個完整的報文。

UDP協議沒有擁塞控制,所以當網路出現的擁塞不會導致主機發送資料的速率降低。雖然UDP的接收端有緩衝區,但是這個緩衝區只負責接收,並不會保證UDP報文的到達順序是否和傳送的順序一致。

因此UDP適用於一次只傳送少量資料、對可靠性要求不高的應用環境,資料報大小限制在64K以下。

API

基於UDP協議的網路程式設計仍然需要在通訊例項的兩端各建立一個Socket,但這兩個Socket之間並沒有虛擬鏈路,這兩個Socket只是傳送、接收資料報的物件

DatagramSocket 類的常用方法:

  • public DatagramSocket(int port)

建立資料報套接字並將其繫結到本地主機上的指定埠。套接字將被繫結到萬用字元地址,IP 地址由核心來選擇。

  • public DatagramSocket(int port,InetAddress laddr)

建立資料報套接字,將其繫結到指定的本地地址。本地埠必須在 0 到 65535 之間(包括兩者)。如果 IP 地址為 0.0.0.0,套接字將被繫結到萬用字元地址,IP 地址由核心選擇。

  • public void send(DatagramPacket p)

從此套接字傳送資料報包。DatagramPacket 包含的資訊指示:將要傳送的資料、其長度、遠端主機的 IP 地址和遠端主機的埠號。

  • public void receive(DatagramPacket p)

從此套接字接收資料報包。當此方法返回時,DatagramPacket 的緩衝區填充了接收的資料。資料報包也包含傳送方的 IP 地址和傳送方機器上的埠號。 此方法在接收到資料報前一直阻塞。資料報包物件的 length 欄位包含所接收資訊的長度。如果資訊比包的長度長,該資訊將被截短。

  • public void close()                                               【關閉此資料報套接字。】

DatagramPacket類的常用方法:

  • public DatagramPacket(byte[] buf,int length)

構造 DatagramPacket,用來接收長度為 length 的資料包。 length 引數必須小於等於 buf.length。

  • public DatagramPacket(byte[] buf,int length,InetAddress address,int port)

構造資料報包,用來將長度為 length 的包傳送到指定主機上的指定埠號。length 引數必須小於等於 buf.length。

  • public int getLength()                                           【返回將要傳送或接收到的資料的長度】

多點廣播

Datagram只允許資料報傳送給指定的目標地址,而MulticastSocket可以將資料報以廣播方式傳送到數量不等的多個客戶端。

IP協議為多點廣播提供了這批特殊的IP地址,這些IP地址的範圍是224.0.0.0至239.255.255.255。

MulticastSocket常用的方法:

  • MulticastSocket(int port) :

建立多播套接字並將其繫結到特定埠。建立一個MulticastSocket物件後,還需要將該MulticastSocket加入到指定的多點廣播地址,如果結束也需要脫離多點廣播地址。

  • void joinGroup(InetAddress mcastaddr) :【加入多播組。】

  • void leaveGroup(InetAddress mcastaddr) :【離開多播組。】

  • void setLoopbackMode(boolean disable) :【啟用/禁用多播資料報的本地回送。true 表示禁用LoopbackMode。】

TCP網路程式設計示例:群聊

客戶端未導包

public class Client {
    public static void main(String[] args) throws UnknownHostException, IOException {
        // 1、連線伺服器
        Socket socket = new Socket("127.0.0.1", 9999);
        // 2、開啟兩個執行緒,一個收訊息,一個發訊息
        SendThread st = new SendThread(socket);
        ReceiveThread rt = new ReceiveThread(socket);
st.start(); rt.start(); // 等傳送執行緒停下來再往下走 try { st.join(); } catch (InterruptedException e) { e.printStackTrace(); } // 等接收執行緒停下來,再往下走,斷開連線 try { rt.join(); } catch (InterruptedException e) { e.printStackTrace(); } socket.close(); } static class SendThread extends Thread { private Socket socket; public SendThread(Socket socket) { super(); this.socket = socket; } public void run() { try { // 鍵盤輸入 Scanner input = new Scanner(System.in); OutputStream out = socket.getOutputStream(); PrintStream ps = new PrintStream(out); while (true) { // 從鍵盤輸入 System.out.print("請輸入要傳送的訊息:"); String content = input.nextLine(); // 給伺服器傳送 ps.println(content); // 如果bye,就結束髮送 if ("bye".equals(content)) { break; } } input.close(); } catch (IOException e) { e.printStackTrace(); } } } static class ReceiveThread extends Thread { private Socket socket; public ReceiveThread(Socket socket) { super(); this.socket = socket; } public void run() { try { InputStream in = socket.getInputStream(); InputStreamReader isr = new InputStreamReader(in); BufferedReader br = new BufferedReader(isr); while (true) { String line = br.readLine(); if("bye".equals(line)){ break; } System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } } } }

伺服器端

public class Server {
    private static ArrayList<Socket> online = new ArrayList<Socket>();
    
    public static void main(String[] args) throws IOException {
        //1、開啟伺服器
        ServerSocket server = new ServerSocket(9999);
        
        while(true){
            //2、接收客戶端的連線
            Socket socket = server.accept();
            
            //把這個客戶端加入到online中
            online.add(socket);
            
            //每一個客戶端獨立的執行緒
            MessageHandler mh = new MessageHandler(socket);
            mh.start();
        }
    }

    private static class MessageHandler extends Thread{
        private Socket socket;
        private String ip;
        
        public MessageHandler(Socket socket) {
            super();
            this.socket = socket;
            this.ip = socket.getInetAddress().getHostAddress();
        }

        public void run(){
            //這個客戶端的一連線成功,執行緒一啟動,就可以告訴其他人我上線了
            sendToOthers(ip+"上線了");
            
            //(1)接收當前的客戶端傳送的訊息
            try {
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in);
                BufferedReader br = new BufferedReader(isr);
                
                String content;
                while((content = br.readLine()) !=null){
                    //收到一句,轉發一句
                    sendToOthers(ip+"說:" + content);
                    
                    if("bye".equals(content)){
                        //給自己發一句bye
                        OutputStream out = socket.getOutputStream();
                        PrintStream ps = new PrintStream(out);
                        ps.println("bye");
                        
                        break;
                    }
                }
                sendToOthers(ip+"下線了");
            } catch (IOException e) {
                sendToOthers(ip+"掉線了");
            }
        }
        
        //因為轉發的程式碼也很長,獨立為一個方法
        public void sendToOthers(String str){
            //遍歷所有online的客戶端
            Iterator<Socket> iterator = online.iterator();
            while(iterator.hasNext()){
                Socket on = iterator.next();
                if(!on.equals(socket)){//只給其他客戶端轉發
                    try {
                        OutputStream out = on.getOutputStream();
                        PrintStream ps = new PrintStream(out);
                        
                        ps.println(str);
                    } catch (IOException e) {
                        //說明on這個客戶端要麼下線了,要麼掉線了
                        iterator.remove();
                    }
                }
            }
        }
    }
}