UDP協議開發


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

UDP協議簡介

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

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

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

(2)目的端口:目的端口號,2個字節,最大值為65535;

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

(4)校驗和:2字節,用於校驗UDP數據報的數字段和包含UDP數據報首部的“偽首部”。其校驗方法類似於IP分組首部中的首部校驗和。

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

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

UDP協議的特點如下。

(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();
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM