UDP協議開發

UDP是用戶數據報協議(User Datagram Protocol,UDP)的簡稱,其主要做用是將網絡數據流量壓縮成數據報形式,提供面向事務的簡單信息傳送服務。與TCP協議不一樣,UDP協議直接利用IP協議進行UDP數據報的傳輸,UDP提供的是面向無鏈接的、不可靠的數據報投遞服務。當使用UDP協議傳輸信息時,用戶應用程序必須負責解決數據報丟失、重複、排序,差錯確認等問題。因爲UDP具備資源消耗小、處理速度快的優勢,因此一般視頻、音頻等可靠性要求不高的數據傳輸通常會使用UDP,即使有必定的丟包率,也不會對功能形成嚴重的影響。java

UDP協議簡介

UDP是無鏈接的,通訊雙方不須要創建物理鏈路鏈接。在網絡中它用於處理數據包,在OSI模型中,它處於第四層傳輸層,即位於IP協議的上一層。它不對數據報分組、組裝、校驗和排序,所以是不可靠的。報文的發送者不知道報文是否被對方正確接收。bootstrap

UDP數據報格式有首部和數據兩個部分,首部很簡單,爲8個字節,包括如下部分:安全

(1)源端口:源端口號,2個字節,最大值爲65535;網絡

(2)目的端口:目的端口號,2個字節,最大值爲65535;多線程

(3)長度:2字節,UDP用戶數據報的總長度;併發

(4)校驗和:2字節,用於校驗UDP數據報的數字段和包含UDP數據報首部的「僞首部」。其校驗方法相似於IP分組首部中的首部校驗和。dom

僞首部,又稱爲僞包頭(Pseudo Header):是指在TCP的分段或UDP的數據報格式中,在數據報首部前面增長源IP地址、目的IP地址、IP分組的協議字段、TCP或UDP數據報的總長度等,共12字節,所構成的擴展首部結構。此僞首部是一個臨時的結構,它既不向上也不向下傳遞,僅僅是爲了保證能夠校驗套接字的正確性。socket

UDP協議數據報格式示意圖如圖:ide

UDP協議的特色以下。oop

(1)UDP傳送數據前並不與對方創建鏈接,即UDP是無鏈接的。在傳輸數據前,發送方和接收方相互交換信息使雙方同步;

(2)UDP對接收到的數據報不發送確認信號,發送端不知道數據是否被正確接收,也不會重發數據;

(3)UDP傳送數據比TCP快速,系統開銷也少:UDP比較簡單,UDP頭包含了源端口、目的端口、消息長度和校驗和等不多的字節。因爲UDP比TCP簡單、靈活,經常使用於可靠性要求不高的數據傳輸,如視頻、圖片以及簡單文件傳輸系統(TFTP)等。TCP則適用於可靠性要求很高但實時性要求不高的應用,如文件傳輸協議FTP、超文本傳輸協議HTTP、簡單郵件傳輸協議SMTP等。

服務端開發

 

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;

public class ChineseProverbServer {
    public void run(int port) throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            //因爲使用UDP通訊,在建立Channel的時候須要經過NioDatagramChannel來建立
            b.group(group).channel(NioDatagramChannel.class)
                    //隨後設置Socket參數支持廣播,
                    .option(ChannelOption.SO_BROADCAST, true)
                    //最後設置業務處理handler。
                    //相比於TCP通訊,UDP不存在客戶端和服務端的實際鏈接,
                    //所以不須要爲鏈接(ChannelPipeline)設置handler,
                    //對於服務端,只須要設置啓動輔助類的handler便可。
                    .handler(new ChineseProverbServerHandler());
            b.bind(port).sync().channel().closeFuture().await();
        } finally {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        int port = 8080;
        if (args.length > 0) {
            try {
                port = Integer.parseInt(args[0]);
            } catch (NumberFormatException e) {
                e.printStackTrace();
            }
        }
        new ChineseProverbServer().run(port);
    }
}

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket;
import io.netty.util.CharsetUtil;
import io.netty.util.internal.ThreadLocalRandom;

public class ChineseProverbServerHandler extends SimpleChannelInboundHandler {
    // 諺語列表
    private static final String[] DICTIONARY = {"只要功夫深,鐵棒磨成針。",
            "舊時王謝堂前燕,飛入尋常百姓家。", "洛陽親友如相問,一片冰心在玉壺。", "一寸光陰一寸金,寸金難買寸光陰。",
            "老驥伏櫪,志在千里。烈士暮年,壯心不已!"};

    private String nextQuote() {
        //因爲ChineseProverbServerHandler存在多線程併發操做的可能,
        //因此使用了Netty的線程安全隨機類ThreadLocalRandom。
        // 若是使用的是JDK7,能夠直接使用JDK7的java.util.concurrent.ThreadLocalRandom。
        int quoteId = ThreadLocalRandom.current().nextInt(DICTIONARY.length);
        return DICTIONARY[quoteId];
    }

    @Override
    public void messageReceived(ChannelHandlerContext ctx, Object msg)throws Exception {
        //Netty對UDP進行了封裝,所以,接收到的是Netty封裝後的io.netty. channel.socket.DatagramPacket對象。
        DatagramPacket packet = (DatagramPacket) msg;
        //將packet內容轉換爲字符串(利用ByteBuf的toString(Charset)方法),
        String req = packet.content().toString(CharsetUtil.UTF_8);
        System.out.println(req);
        // 而後對請求消息進行合法性判斷:若是是「諺語字典查詢?」,則構造應答消息返回。
        // DatagramPacket有兩個參數:第一個是須要發送的內容,爲ByteBuf;
        // 另外一個是目的地址,包括IP和端口,能夠直接從發送的報文DatagramPacket中獲取。
        if ("諺語字典查詢?".equals(req)) {
            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();
    }
}

客戶端開發 

UDP程序的客戶端和服務端代碼很是類似,惟一不一樣之處是UDP客戶端會主動構造請求消息,向本網段內的全部主機廣播請求消息,對於服務端而言,接收到廣播請求消息以後會向廣播消息的發起方進行定點發送。

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.util.CharsetUtil;

import java.net.InetSocketAddress;

public class ChineseProverbClient {

    public void run(int port) throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            //建立UDP Channel和設置支持廣播屬性等與服務端徹底一致。
            // 因爲不須要和服務端創建鏈路,UDP Channel建立完成以後,客戶端就要主動發送廣播消息;
            // TCP客戶端是在客戶端和服務端鏈路創建成功以後由客戶端的業務handler發送消息,這就是二者最大的區別。
            b.group(group).channel(NioDatagramChannel.class)
                    .option(ChannelOption.SO_BROADCAST, true)
                    .handler(new ChineseProverbClientHandler());
            Channel ch = b.bind(0).sync().channel();
            // 向網段內的全部機器廣播UDP消息
            // 用於構造DatagramPacket發送廣播消息,
            // 注意,廣播消息的IP設置爲「255.255.255.255」。
            // 消息廣播以後,客戶端等待15s用於接收服務端的應答消息,而後退出並釋放資源。
            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("查詢超時!");
            }
        } finally {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        int port = 8080;
        if (args.length > 0) {
            try {
                port = Integer.parseInt(args[0]);
            } catch (NumberFormatException e) {
                e.printStackTrace();
            }
        }
        new ChineseProverbClient().run(port);
    }
}
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket;
import io.netty.util.CharsetUtil;

public class ChineseProverbClientHandler extends SimpleChannelInboundHandler {

    @Override
    public void messageReceived(ChannelHandlerContext ctx, Object o)
            throws Exception {
        //接收到服務端的消息以後將其轉成字符串,而後判斷是否以「諺語查詢結果:」開頭,
        //若是沒有發生丟包等問題,數據是完整的,就打印查詢結果,而後釋放資源。
        DatagramPacket msg = (DatagramPacket)o;
        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();
    }
}
相關文章
相關標籤/搜索