天天看點

網絡程式設計基礎---UDP

       上一篇部落客要是為了這一部分服務的,最近學到網絡程式設計部分 剛好計算機網絡也講到ip位址 也就惡補了一些IP傳輸的知識。現在正式來總結一下UDP和TCP兩大傳輸協定吧

網絡通訊的協定:

udp通訊協定

tcp通訊協定。
           

網絡通訊的三要素:

1. IP 傳輸的源位址和目标位址

2. 端口号。

3. 協定.

端口号是沒有類描述的。
    端口号的範圍: 0~65535
    從0到1023,系統緊密綁定于一些服務。 
    1024~65535  我們可以使用....
    常用端口       
    HTTPS端口 :443  
  Telnet端口:23 (遠端登入)
  HTTP端口:80 (超文本傳輸協定) 
  FTP 端口:21 (檔案傳輸協定)
  DNS 端口:53 (域名服務系統)
  POP3端口:110(郵筒協定)
  SMTP端口:25 (簡單郵件傳輸協定)            

Socket

socket的英文原義是“孔”或“插座”。作為4BDS UNIX的程序通信機制,取後一種意思。通常也稱作“套接字”,用于描述IP位址和端口,是一個通信鍊的句柄(引用)。

每個插座就是一個應用程式。

注意:

不同的通信規則需要定義不同的插座。

UDP:DatagramSocket 、 DatagramPacket

TCP:ServerSocket 、Socket

UDP:

在java中網絡通訊業稱作為Socket(插座)通訊,要求通訊 的兩台器都必須要安裝Socket。

不同的協定就有不同 的插座(Socket)

相當于碼頭 沒有尋址作用 隻是把資料包傳輸出去

UDP通訊協定的特點:

1. 将資料極封裝為資料包,面向無連接配接。

2. 每個資料包大小限制在64K中

3.因為無連接配接,是以不可靠

4. 因為不需要建立連接配接,是以速度快

5.udp 通訊是不分服務端與用戶端的,隻分發送端與接收端。

比如: 物管的對講機, 飛Q聊天、 遊戲... 
你知道為什麼遊戲的時候會莫名其妙卡死嗎?
就是因為是用UDP來傳輸 導緻資料包丢失 為什麼用UDP 那是因為速度快響應快
           

先來講一下:

InetAddress(IP類)

常用的方法:

getLocalHost(); 擷取本機的IP位址

getByName(“IP或者主機名”) 根據一個IP位址的字元串形式或者是一個主機名生成一個IP位址對象。 (用于擷取别人的IP位址對象) 一般用IP位址

getHostAddress() 傳回一個IP位址的字元串表示形式。

getHostName() 傳回計算機的主機名。(主機名可以修改)

getAllByName() 傳回主機的全部ip位址 有些域名對應着多個ip位址

就比如百度 百度是一個域名 但是背後是多個伺服器 是叢集模式

InetAddress[] inetAddress=
InetAddress.getAllByName("www.baidu.com");
for(int i=0;i<inetAddress.length;i++)
System.out.println(inetAddress[i].getHostAddress());

輸出:
119.75.213.61
119.75.216.20           
//getLocalHost 擷取本機的IP位址對象
        /*InetAddress address = InetAddress.getLocalHost();
        System.out.println("IP位址:"+address.getHostAddress());
        System.out.println("主機名:"+address.getHostName());*/

        //擷取别人機器的IP位址對象。


        //可以根據一個IP位址的字元串形式或者是一個主機名生成一個IP位址對象。
        InetAddress address = InetAddress.getByName("Jolly-pc140116");
        System.out.println("IP位址:"+address.getHostAddress());
        System.out.println("主機名:"+address.getHostName());



        InetAddress[]  arr = InetAddress.getAllByName("www.baidu.com");//域名           

udp協定下的Socket(也可以叫做套接字):

DatagramSocket(udp插座服務)
DatagramPacket(資料包類)
    DatagramPacket(buf, length, address, port)
    buf: 發送的資料内容
    length : 發送資料内容的大小。
    address : 發送的目的IP位址對象
    port : 端口号。
           

發送端的使用步驟:

1. 建立udp的服務。

2. 準備資料,把資料封裝到資料包中發送。 發送端的資料包要帶上ip位址與端口号。

3. 調用udp的服務,發送資料。

4. 關閉資源。

來看一下UDP兩台電腦間是如何通信吧
           
//發送端
public class Demo1Sender {

    public static void main(String[] args) throws IOException {
        //建立udp的服務
        DatagramSocket datagramSocket = new DatagramSocket();
        //準備資料,把資料封裝到資料包中。
        String data = "這個是我第一個udp的例子..";
        //建立了一個資料包
        DatagramPacket packet = new DatagramPacket(data.getBytes(), data.getBytes().length,InetAddress.getLocalHost() , 9090);
        //調用udp的服務發送資料包
        datagramSocket.send(packet);
        //關閉資源 ---實際上就是釋放占用的端口号
        datagramSocket.close();

    }

}           
//接收端
    //建立udp的服務 ,并且要監聽一個端口。
        DatagramSocket  socket = new DatagramSocket(9090);

        //準備空的資料包用于存放資料。
        byte[] buf = new byte[1024];
        DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length); // 1024
        //調用udp的服務接收資料
        socket.receive(datagramPacket); //receive是一個阻塞型的方法,沒有接收到資料包之前會一直等待。 資料實際上就是存儲到了byte的自己數組中了。
        System.out.println("接收端接收到的資料:"+ new String(buf,0,datagramPacket.getLength())); // getLength() 擷取資料包存儲了幾個位元組。
        //關閉資源
        socket.close();           

總結:

1. 要先打開接收端 再打開發送端 不然就會造成資料丢失

2. receive是一個阻塞型的方法,沒有接收到資料包之前會一直等待。相當于sacnner 就是等待輸入 意思就是說 接收方隻有接收到資料才會往下走

3. 注意:資料包發送字元串傳輸的時候看仔細是發送位元組的長度而不是字元串的長度

//對的
 String data = "這個是我第一個udp的例子..";
DatagramPacket packet = new DatagramPacket(data.getBytes(),   
data.getBytes().length,InetAddress.getLocalHost() , 9090);           
//錯的 長度取字元串的長度了  顯然長度要小于實際長度 就會資料丢失
 String data = "這個是我第一個udp的例子..";
DatagramPacket packet = new DatagramPacket(data.getBytes(),   
data.length,InetAddress.getLocalHost() , 9090);           

4.端口号可以随意 隻是辨別符 但是要在範圍内 如1024~65535 我們可以使用…. 本地伺服器的端口一般設為80 這是題外話

5. 在udp協定中,有一個IP位址稱作為廣播位址,廣播位址就是主機号為255位址。 隻有udp有廣播位址 TCP沒有 給廣播IP位址發送消息的時候,在同一個網絡段的機器都可以接收 到資訊。

接下來看一個群聊的UDP實作方法:

//群聊發送端
public class ChatSender extends Thread {

    @Override
    public void run() {
        try {
            //建立udp的服務
            DatagramSocket socket = new DatagramSocket();
            //準備資料,把資料封裝到資料包中發送
            BufferedReader keyReader = new BufferedReader(new InputStreamReader(System.in));
            String line = null;
            DatagramPacket packet  = null;
            while((line = keyReader.readLine())!=null){
                //把資料封裝 到資料資料包中,然後發送資料。
                packet = new DatagramPacket(line.getBytes(), line.getBytes().length, InetAddress.getByName("192.168.15.255"), 9090);
                //把資料發送出去
                socket.send(packet);
            }
            //關閉 資源
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


}           
//群聊接收端
public class ChatReceive extends Thread {

    @Override
    public void run() {
        try {
            //建立udp的服務,要監聽一個端口
            DatagramSocket socket = new DatagramSocket(9090);
            //準備空的資料包存儲資料
            byte[] buf = new byte[1024];
            DatagramPacket packet = new DatagramPacket(buf, buf.length);
            boolean flag = true;
            while(flag){
                socket.receive(packet);
                // packet.getAddress() 擷取對方資料 包的IP位址對象。
                System.out.println(packet.getAddress().getHostAddress()+"說:"+new String(buf,0,packet.getLength()));
            }
            //關閉資源
            socket.close();

        }catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }


    }

}           
//主函數
public class ChatMain {

    public static void main(String[] args) {
        ChatReceive chatReceive = new ChatReceive();
        chatReceive.start();

        ChatSender chatSender = new ChatSender();
        chatSender.start();


    }

}
           

輸出:

總結:1. 不能重複開啟接收端 因為端口被占用了 如果重複啟動main函數 相當于監聽了兩次該端口 就會報這樣的錯誤

2. 用多線程操作群聊的時候 while循環要有個退出條件 不然就無限循環 Close的時候會報錯 是以要用While+flag變量來控制退出 (具體的可以看看我之前寫的多線程的中斷)

3. 多線程操作群聊的時候 不能抛出異常 隻能用Try catch異常 因為Thread是線程作為父類沒有抛出異常 而作為子類是不可以抛出異常的 (具體的可以異常最後的總結部分)

還有個問題得注意下:這個東西把我搞半天 後來發現問題的所在……

如果你是用UTF-8程式設計的發送方代碼 而對方是GBK預設的接受方代碼 這個時候傳輸漢字的時候就會亂碼 字元不影響

那怎麼解決呢

這個可以用來判斷你的檔案編碼格式 getBytes()預設編碼成Byte格式 預設是GBK 如果eclipse設定了 UTF-8格式的話 那它預設就是UTF-8格式

也可以指定編碼格式name.getBytes(“utf-8”) 就把name字元串按照utf-8的格式轉換成byte數組

new String(name.getBytes(“GBK”),”utf-8”)) 以 GBK格式編碼byte又以UTF-8格式轉換成字元串

String name="我愛你";
        if(name.equals(new String(name.getBytes(),"utf-8"))) {
            System.out.println(name);
        }
}           

回到群聊中:

packet = new DatagramPacket(line.getBytes("GBK"), line.getBytes().length, InetAddress.getByName("192.168.15.255"), 9090);           

這樣子會報錯的 要這樣子才可以 得統一

packet = new DatagramPacket(line.getBytes("GBK"), line.getBytes("GBK").length, InetAddress.getByName("192.168.15.255"), 9090);           

群聊接收方: 兩種方法都可以

String name=new String(datagramPacket.getData(),datagramPacket.getLength());
System.out.println(new String(buffer,0,datagramPacket.getLength()));都可以           

接受對方還可以得到發送方的資訊

packet.getAddress().getHostAddress() 得到發送方的ip位址

packet.getAddress().getHostName() 得到發送方的電腦名(可修改)

題外話 :

指令行 來運作class檔案的時候

java 要加包名 而且是 bin目錄下 java myjar.ww myjar是包名

UDP是無連接配接協定,發送方的端口是由發送方随機生成的(根據不同的網絡環境,發送方的IP也有可能是在位址池中随機選取的),發送完以後,該IP+端口就已經無效了。

注意:發送端發送資料的端口是随機的

參考:

https://blog.csdn.net/napolun007/article/details/6050241 各種協定

繼續閱讀