Netty应用UDP协议

本文介绍了UDP协议的特性,如无连接、不可靠传输,并通过Netty框架展示了UDP服务端和客户端的实现示例,包括UDP数据报的格式、NettyUDPServer和NettyUDPClient的处理流程。

UDP是用户数据报协议(User Datagram protocol)的简称,其主要作用是将网络数据封装成数据报形式,提供面向服务的简单信息传送服务。

与TCP协议不同,UDP协议直接利用IP协议进行UDP数据报的传输,UDP提供的是面向无连接的、不可靠的数据报投递服务。当使用UDP协议传输信息时,用户应用程序必须负责解决数据报丢失、重复、排序,差错确认等问题。

UDP协议的特点如下。
(1)UDP传送数据前并不与对方建立连接,即UDP是无连接的。在传输数据前,发
送方和接收方相互交换信息使双方同步;
(2)UDP对接收到的数据报不发送确认信号,发送端不知道数据是否被正确接收,也
不会重发数据;
(3)UDP传送数据比TCP快速,系统开销也少:UDP比较简单,UDP头包含了源端口、目的端口、消息长度和校验和等很少的字节。由于UDP比TCP简单、灵活,常用于可靠性要求不高的数据传输,如视频、图片以及简单文件传输系统(TFTP)等。TCP则适用于可靠性要求很高但实时性要求不高的应用,如文件传输协议FTP、超文本传输协议HTTP、简单邮件传输协议SMTP等。

UDP数据报

UDP数据报格式有首部和数据两个部分,首部很简单,为8个字节。
(1)源端口:源端口号,2个字节,最大值为65535;
(2)目的端口:目的端口号,2个字节,最大值为65535;
(3)长度:2字节,UDP用户数据报总长度;
(4)检验和:2字节,用于校验UDP数据报的数字段和包含UDP数据报首部的“伪首部”。

示意图如下:

在这里插入图片描述

伪首部,又称伪包头:是在TCP的分段或UDP的数据报格式中,在数据报首部前面增加源IP地址、目的IP地址、IP分组的协议字段(上图为17,因为UDP在IP报文的协议号是17)、TCP或UDP数据报的总长度等。

Netty应用UDP协议示例

UDP服务端NettyUDPServerTest类:

package test.netty.udp_test;

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 NettyUDPServerTest {
	
	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 NettyUDPServerHandler());
			b.bind(port).sync().channel().closeFuture().await();
		} finally {
			group.shutdownGracefully();
		}
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int port = 8080;
		try {
			new NettyUDPServerTest().run(port);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

UDP通信双方不需要建立链路,所以代码也相对简洁了很多。服务端只构造了一个NioEventLoopGroup线程,需要将其中的Channel对应的设置为NioDatagramChannel,并设置Socket参数为支持广播。

UDP服务端NettyUDPServerHandler类:

package test.netty.udp_test;

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 NettyUDPServerHandler extends SimpleChannelInboundHandler<DatagramPacket> {
	
	private static final String[] DICTIONARY = {"只要功夫深,铁棒磨成针。", "旧时王谢堂前燕,飞入寻常百姓家。", 
			"洛阳亲友如相问,一片冰心在玉壶。", "一寸光阴一寸金,寸金难买寸光阴。", "老骥伏枥,志在千里。烈士暮年,壮心不已!"};
	
	private String nextQuote() {
		int quoteId = ThreadLocalRandom.current().nextInt(DICTIONARY.length);
		return DICTIONARY[quoteId];
	}

	@Override
	protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception {
		// TODO Auto-generated method stub
		String req = msg.content().toString(CharsetUtil.UTF_8);
		System.out.println(req);
		if ("谚语字典查询?".equals(req)) {
			ctx.writeAndFlush(new DatagramPacket(Unpooled.copiedBuffer("谚语查询结果:" + nextQuote(), CharsetUtil.UTF_8), msg.sender()));
		}
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		// TODO Auto-generated method stub
		ctx.close();
		cause.printStackTrace();
	}
}

Netty对UDP进行了封装,所以接收到的信息都是Netty封装后的DatagramPacket对象。所以在收到DatagramPacket对象后需要先将packet内容转换为字符串(利用ByteBuf的toString(Charset)方法),然后对请求消息进行合法性判断:如果是“谚语字典查询?”,则构造应答消息返回。DatagramPacket有两个参数:第一个是需要发送的内容,为ByteBuf;另一个是目的地址,包括IP和端口,可以直接从发送的报文DatagramPacket中获取。

服务端发送的内容是利用了Netty的线程安全随机类ThreadLocalRandom,随机从字符串数组中选取元素构造消息返回。

UDP客户端NettyUDPClientTest类:

package test.netty.udp_test;

import java.net.InetSocketAddress;

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;

public class NettyUDPClientTest {
	
	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 NettyUDPClientHandler());
			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("查询超时!");
			}
		} finally {
			group.shutdownGracefully();
		}
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int port = 8080;
		try {
			new NettyUDPClientTest().run(port);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

UDP代码跟服务端代码差不多,但是需要主动构造请求消息,向网段内的所有主机广播请求消息,对于服务端而言,接收到广播请求消息之后会向广播消息的发起方进行定点发送。

客户端广播消息的IP需要设置为“255.255.255.255”,消息广播之后,客户端会等待15s用于接收服务端的消息,然后退出并释放资源。

UDP客户端NettyUDPClientHandler类:

package test.netty.udp_test;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket;
import io.netty.util.CharsetUtil;

public class NettyUDPClientHandler extends SimpleChannelInboundHandler<DatagramPacket> {

	
	
	@Override
	protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception {
		// TODO Auto-generated method stub
		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 {
		// TODO Auto-generated method stub
		cause.printStackTrace();
		ctx.close();
	}
	
}

客户端Handler的主要操作只是将接收到的消息解析并打印。

运行结果:

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值