Netty之UDP協議開發

UDP協議簡介
UDP是用戶數據報協議(User Datagrame Protocol,UDP)的簡稱,主要作用是將網絡數據流壓縮成數據報的形式,提供面向事務的簡單信息傳送服務。

UDP與TCP協議比較:


UDP協議格式:


通過UDP協議的格式圖我們可以看出:

1.UDP協議是由首部和數據組成。
2.首部部分很簡單,只有8個字節,由四個字段組成,每個字段都是兩個字節。各個字段意義分別是:
    a.源端口號,這是在源主機上運行的進程所使用的端口號,如果源主機是客戶端(發起請求的一方),則在大多數情況下這個端口號是臨時端口號,如果源主機是服務器端(發送響應時),則在大多數情況下這個端口號時熟知端口號。
    b.目的端口號,這是在目的主機上運行的進程所使用的端口號,如果目的主機是客戶端(發起請求的一方),則在大多數情況下這個端口號是臨時端口號,服務器需要將這個臨時端口號複製下來,如果目的主機是服務器端(發送響應時),則在大多數情況下這個端口號時熟知端口號。
    c.長度,定義了用戶數據報的總長度,首部加上數據,數據部分的長度範圍時0~65507。
    d.檢驗和,這個字段用來檢驗這個用戶數據報(首部加上數據)出現的差錯。
3.僞首部:
    a.僞首部是用來校驗的,它必須和首部中的校驗和結合起來使用。
    b.在計算檢驗和時臨時加上去的,僞首部既不向下傳送也不向上提交,而僅僅時爲了計算檢驗和
    c.在計算檢驗和時,需要在UDP用戶數據報之前增加12個字節的僞首部。這個僞首部並不是UDP真正的首部,這是在計算檢驗和時臨時和UDP用戶數據報連接在一起,得到一個過渡的UDP用戶數據報,檢驗和就是按照這個過渡的UDP用戶數據報來計算的。僞首部既不向下傳送也不向上提交,而僅僅時爲了計算檢驗和。UDP計算檢驗和的方法和計算IP數據報首部檢驗和的方法相似,不同的是,IP數據報的檢驗和只是檢驗IP數據報的首部,但是UDP的檢驗和是將首部和數據部分一起都檢驗。
1
2
3
4
5
6
7
8
9
10
UDP協議的特點:
1.UDP傳送數據前並不與對方建立連接,即UDP是無連接的。
2.UDP接收到的數據報不發送確認信號,發送端不知道數據是否被正確接收
3.UDP傳送數據比TCP快,系統開銷也少;
1
2
3
UDP協議開發
服務端開發
由於UDP通信雙方不需要建立鏈路,所以,代碼相對於TCP更加簡單一些,代碼如下:
啓動類 ChineseProverbServer類

/**
 * @author 作者 YYD
 * @version 創建時間:2016年11月18日 下午8:38:30
 * @function 未添加
 */
public class ChineseProverbServer {
    public void run(int port) throws Exception{
        EventLoopGroup group = new NioEventLoopGroup();
        Bootstrap b = new Bootstrap();
        //由於我們用的是UDP協議,所以要用NioDatagramChannel來創建
        b.group(group).channel(NioDatagramChannel.class)
        .option(ChannelOption.SO_BROADCAST, true)//支持廣播
        .handler(new ChineseProverbServerHandler());//ChineseProverbServerHandler是業務處理類
        b.bind(port).sync().channel().closeFuture().await();
    }
    public static void main(String [] args) throws Exception{
        int port = 8080;
        new ChineseProverbServer().run(port);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
由於使用UDP通信,在創建Channel的時候通過NioDatagramChannel來創建,隨後設置Socket參數支持廣播,最後設置處理handler ChineseProverbServerHandler.
相比於TCP通信,UDP不存在客戶端和服務器端的實際連接,因此不需要爲連接(channelPipeline)設置handler,對於服務端,只需設置啓動輔助類的handler即可。

下面看ChineseProverbServerHandler的實現

/**
 * @author 作者 YYD
 * @version 創建時間:2016年11月18日 下午8:43:10
 * @function 未添加
 */
public class ChineseProverbServerHandler extends
        SimpleChannelInboundHandler<DatagramPacket> {
    //諺語列表
    private static final String[] DICTIONARY = { "只要功夫深,鐵棒磨成針。",
            "舊時王謝堂前燕,飛入尋常百姓家。", "洛陽親友如相問,一片冰心在玉壺。", "一寸光陰一寸金,寸金難買寸光陰。",
            "老驥伏櫪,志在千里,烈士暮年,壯心不已" };
    private String nextQuote(){
        //返回0-DICTIONARY.length中的一個整數。
        int quoteId = ThreadLocalRandom.current().nextInt(DICTIONARY.length);
        return DICTIONARY[quoteId];//將諺語列表中對應的諺語返回
    }
    /**
     * 在這個方法中,形參packet客戶端發過來的DatagramPacket對象
     * DatagramPacket 類解釋
     * 1.官網是這麼說的:
     * The message container that is used for {@link DatagramChannel} to communicate with the remote peer.
     * 翻譯:DatagramPacket 是消息容器,這個消息容器被 DatagramChannel使用,作用是用來和遠程設備交流
     * 2.看它的源碼我們發現DatagramPacket是final類不能被繼承,只能被使用。我們還發現DatagramChannel最終實現了AddressedEnvelope接口,接下來我們看一下AddressedEnvelope接口。
     * AddressedEnvelope接口官網解釋如下:
     * A message that wraps another message with a sender address and a recipient address.
     * 翻譯:這是一個消息,這個消息包含發送者和接受者消息
     * 3.那我們知道了DatagramPacket它包含了發送者和接受者的消息,
     * 通過content()來獲取消息內容
     * 通過sender();來獲取發送者的消息
     * 通過recipient();來獲取接收者的消息。
     * 
     * 4.public DatagramPacket(ByteBuf data, InetSocketAddress recipient) {}
     *  這個DatagramPacket其中的一個構造方法,data 是發送內容;是發送都信息。
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet)
            throws Exception {

        String req = packet.content().toString(CharsetUtil.UTF_8);//上面說了,通過content()來獲取消息內容
        System.out.println(req);
        if("諺語字典查詢?".equals(req)){//如果消息是“諺語字典查詢?”,就隨機獲取一條消息發送出去。
            /**
             * 重新 new 一個DatagramPacket對象,我們通過packet.sender()來獲取發送者的消息。
             * 重新發達出去!
             */
            ctx.writeAndFlush(new DatagramPacket(Unpooled.copiedBuffer("諺語查詢結果:"+nextQuote(),CharsetUtil.UTF_8), packet.sender()));
        }
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        ctx.close();
        cause.printStackTrace();
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
解釋:請看channelRead0方法,netty對UDP進行了封裝,接收到的是DatagramPacket對象,然後通過packet.content().toString(CharsetUtil.UTF_8)獲取packet的內容。如果是“諺語字典查詢?”字符串則隨機取一個諺語返回。DatagramPacket有二個參數,第一個是發送的內容,另一個是接收者的相關信息,這個可以通過packet.sender()獲取。

客戶端開發
UDP程序的客戶端和服務器端代碼非常相似,唯一不同是UDP客戶端會主動構造請求消息,向本網段內的所有主機請求消息,對於服務端而言接收到廣播消息之後向廣播消息的發起方進行定點發送。

啓動類ChineseProverbClient類

/**
 * @author 作者 YYD
 * @version 創建時間:2016年11月18日 下午9:00:11
 * @function 未添加
 */
public class ChineseProverbClient {
    public void run(int port) throws Exception{

        EventLoopGroup group  = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group).channel(NioDatagramChannel.class)
            .option(ChannelOption.SO_BROADCAST,true)//允許廣播
            .handler(new ChineseProverClientHandler());//設置消息處理器
            Channel ch = b.bind(0).sync().channel();
            //向網段內的所有機器廣播UDP消息。
            ch.writeAndFlush(new DatagramPacket(Unpooled.copiedBuffer("諺語字典查詢?",CharsetUtil.UTF_8), new InetSocketAddress("255.255.255.255",port))).sync();
            if(!ch.closeFuture().await(15000)){
                System.out.println("查詢超時!");
            }
        } catch (Exception e) {
            group.shutdownGracefully();
        }
    }
    public static void main(String [] args) throws Exception{
        int port = 8080;

        new ChineseProverbClient().run(port);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
解釋:創建UDPChannel和設置支持廣播屬性等與服務端完全一致,由於不需要和服務端建立鏈路,UDP Channel 創建完成之後,客戶端就要主動發送廣播消息。而TCP客戶端是在客戶端和服務端鏈路建立成功之後由客戶端的業務handler發送消息,這就是二者最大區別。

ChineseProverClientHandler類

/**
 * @author 作者 YYD
 * @version 創建時間:2016年11月18日 下午9:09:18
 * @function 未添加
 */
public class ChineseProverClientHandler extends
        SimpleChannelInboundHandler<DatagramPacket> {
/**
 * DatagramPacket的詳細介紹,看服務器的代碼註釋,這裏不重複了。
 */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg)
            throws Exception {
        String response = msg.content().toString(CharsetUtil.UTF_8);
        if (response.startsWith("諺語查詢結果:")) {
            System.out.println(response);
            ctx.close();
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
代碼非常簡單,接收到服務端的消息之後將其轉成字符串然後判斷是否以“諺語查詢結果:”開頭,如果沒有發生丟包等問題,數據是完整的,就打印查詢結果,然後釋放資源。

運行結果:
先啓動UDP服務端,然後啓動客戶端(運行二次),運行結果如下:

服務器運行結果:

客戶端1運行結果:

客戶端2運行結果:

通過上圖我們可以看出,客戶端每次運行結果都不一樣,說明UDP服務器的查詢功能正確,並且客戶端成功的接受到了服務器的應答,說明整個過程沒有丟包和亂序問題。

結果
文章的結尾奉獻上代碼,方便大家對照學習。

在技術上自己依舊是個小渣渣,加油勉勵自己!
 

發佈了50 篇原創文章 · 獲贊 99 · 訪問量 102萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章