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();
                    }
                }
            }
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章