UDP是用户数据报协议(User Datagram Protocol,UDP)的简称,其主要做用是将网络数据流量压缩成数据报形式,提供面向事务的简单信息传送服务。与TCP协议不一样,UDP协议直接利用IP协议进行UDP数据报的传输,UDP提供的是面向无链接的、不可靠的数据报投递服务。当使用UDP协议传输信息时,用户应用程序必须负责解决数据报丢失、重复、排序,差错确认等问题。因为UDP具备资源消耗小、处理速度快的优势,因此一般视频、音频等可靠性要求不高的数据传输通常会使用UDP,即使有必定的丢包率,也不会对功能形成严重的影响。java
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(); } }