Java語法進階14-網絡編程

網絡編程

軟件結構

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

B/S結構 :全稱爲Browser/Server結構,是指瀏覽器和服務器結構。程序員

網絡通訊協議

網絡通訊協議:位於同一個網絡中的計算機在進行鏈接和通訊時須要遵照必定的規則,它對數據的傳輸格式、傳輸速率、傳輸步驟等作了統一規定編程

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

  • 應用層:網絡服務與最終用戶的一個接口。協議有:HTTP、FTP、SMTP、DNS、TELNET、HTTPS、POP3等等。安全

  • 表示層:數據的表示、安全、壓縮。格式有:JPEG、ASCll、DECOIC、加密格式等。服務器

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

  • 傳輸層:定義傳輸數據的協議端口號,以及流控和差錯校驗。協議有:TCP、UDP。socket

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

  • 數據鏈路層:創建邏輯鏈接、進行硬件地址尋址、差錯校驗等功能。將比特組合成字節進而組合成幀,用MAC地址訪問介質,錯誤發現但不能糾正。函數

  • 物理層:創建、維護、斷開物理鏈接。

IP(internet protocal)又稱爲互聯網協議。IP的責任就是把數據從源傳送到目的地。它在源地址和目的地址之間傳送一種稱之爲數據包的東西,它還提供對數據大小的從新組裝功能,以適應不一樣網絡對包大小的要求。常常與IP協議放在一塊兒的還有TCP(Transmission Control Protocol)協議

TCP與UDP協議

UDP:用戶數據報協議(User Datagram Protocol)。

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

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

  • 面向鏈接的,可靠的:在發送端和接收端創建邏輯鏈接,而後再傳輸數據,是一種面向鏈接的、可靠的、基於字節流的傳輸層的通訊協議,能夠連續傳輸大量的數據。TCP協議保證了數據包在傳送中準確無誤,TCP協議使用重發機制,須要收到另外一個通訊實體的確認信息
  • 三次握手

  • 四次揮手

網絡編程三要素

一、協議:如上

二、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地址,方便記憶。

三、端口號

端口號能夠找到惟一標識設備中的進程(應用程序)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協議的抽象,對外提供了一套接口,經過這個接口就能夠統1、方便的使用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網絡編程

一、服務器端

  • 調用 ServerSocket(int port) :建立一個服務器端套接字,並綁定到指定端口上。用於監聽客戶端的請求。

  • 調用 accept() :監聽鏈接請求,若是客戶端請求鏈接,則接受鏈接,返回通訊套接字對象。

  • 調用 該Socket 類對象的 getOutputStream() 和 getInputStream () :獲取輸出流和輸入流,開始網絡數據的發送和接收。

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

二、客戶端

  • 建立 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 {
        // 一、鏈接服務器
        Socket socket = new Socket("127.0.0.1", 9999);
        // 二、開啓兩個線程,一個收消息,一個發消息
        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 {
        //一、開啓服務器
        ServerSocket server = new ServerSocket(9999);
        
        while(true){
            //二、接收客戶端的鏈接
            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();
                    }
                }
            }
        }
    }
}
相關文章
相關標籤/搜索