Netty UDP协议栈开发
- 介绍
- 协议简介
- 伪首部
- UDP协议的特点
- 开发
- jar依赖
- UDP 服务端启动类
- 服务端业务处理类
- 客户端启动类
- 客户端业务处理类
- 代码说明
- 测试
- 服务端打印截图:
- 客户端打印截图:
- 测试结果
- 总结
介绍
UDP 是用户数据报协议(User Datagram Protocol) 的简称,其主要作用是将网络数据流量压缩成数据报形式,提供面向事物的简单信息传送服务。与TCP协议不同,UDP协议直接利用IP协议进行UDP数据报的传输,UDP提供的是面向无连接的,不可靠的数据报投递服务。当使用UDP协议传输信息时,用户程序必须负责解决数据报丢失,重复,排序,差错确认等问题。
由于UDP具有资源消耗小,处理速度快的优点,故通常视频,音频等可靠性要求不高的数据传输一般会使用UDP,即使有一定的丢包率,也不会对功能造成严重的影响。
协议简介
UDP是无连接的,通信双方不需要建立物理链路连接。在网络中他用于处理数据包,在IOS模型中,它处于第四层传输,位于IP协议的上一层。他不对数据报分组,组装,校验,排序。因此是不可靠的。报文的发送者不知道报文是否被对方正确接收。
UDP数据报格式有首部和数据两个部分,首部很简单,为8个字节,包括以下几点:
(1)源端口,2个字节,最大值 为 65535
(2)目的端口,两个字节,最大值 65535
(3)长度,2个字节,UDP用户数据报的总长度
(4)校验和,2字节,用于校验UDP数据报的数字段和包含UDP数据报首部的"伪首部"。其校验方法类似于IP分组首部中的首部校验和。
伪首部
又称为伪包头(Pseudo Header):是指在TCP的分段或UDP的数据报格式中,在数据报首部前面增加源IP地址,目的IP地址,IP分组的协议字段,TCP或UDP数据报的总长度,共12字节,所构成的扩展首部结构。此伪首部是一个临时的结构,它既不向上也不向下传递,仅仅是为了保证可以校验套接字的正确性。
UDP协议数据报格式如图:
UDP协议的特点
(1)UDP传送数据前并不与对方建立连接,即无连接。在传输数据前,发送方和接收方互相交换信息使双方同步
(2)UDP对接收到的数据报不发送确认信号,发送端不知道数据是否被正确接收,也不会重发数据。
(3)UDP传送数据比TCP快速,系统开销也少。UDP比较简单,UDP头包含了源端口,目的端口,消息长度和校验和等很少的字节。由于UDP比TCP简单,灵活,常用于可靠性要求不高的数据传输,如视频,图片以及简单文件传输系统(TFTP)。TCP则适合可靠性要求很高但实时性要求不高的应用,如文件传输协议FTP,超文本传输协议HTTP,简单邮件传输协议SMTP等。
开发
jar依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId> <!-- Use 'netty5-all' for 5.0-->
<version>5.0.0.Alpha1</version>
<scope>compile</scope>
</dependency>
UDP 服务端启动类
public class ITTechnologyServer {
public void run(int port){
//UDP 通信不需要建立链路,所以代码相对TCP 更加简单一些。
EventLoopGroup group=new NioEventLoopGroup();
try {
Bootstrap bootstrap=new Bootstrap();
//因为不存在连接,故不用设置ChannelPipeline
bootstrap.group(group)
//UDP 采用 NioDatagramChannel
.channel(NioDatagramChannel.class)
.option(ChannelOption.SO_BROADCAST,true)
//业务处理类
.handler(new ITTechnologyServerHandler());
bootstrap.bind(port).sync().channel().closeFuture().await();
System.out.println("unp server is started....");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) {
new ITTechnologyServer().run(8080);
}
}
服务端业务处理类
public class ITTechnologyServerHandler extends SimpleChannelInboundHandler<DatagramPacket> {
//技术列表
private static final String[] IT_DICTIONARY={"Java","Redis","MySql","Netty","TCP","Oracle",
"RabbitMQ","ElasticSearch","SpringBoot","Dubbo"};
public static final String IT_Technology_LEARN="学习哪项 IT技术?";
public static final String IT_Technology_RESULT="IT 技术学习成果";
@Override
protected void messageReceived(ChannelHandlerContext channelHandlerContext, DatagramPacket datagramPacket) throws Exception {
String req=datagramPacket.content().toString(CharsetUtil.UTF_8);
System.out.println(req);
if (IT_Technology_LEARN.equals(req)){
channelHandlerContext.writeAndFlush(
new DatagramPacket(Unpooled.copiedBuffer(IT_Technology_RESULT+":"+nextITTechnology(),
CharsetUtil.UTF_8),datagramPacket.sender()));
}
}
private String nextITTechnology(){
//采用安全随机类,随机获取一个技术
int ITTechnologyId= ThreadLocalRandom.current().nextInt(IT_DICTIONARY.length);
return IT_DICTIONARY[ITTechnologyId];
}
public void exceptionCaught(ChannelHandlerContext context,Throwable cause){
context.close();
cause.printStackTrace();
}
}
客户端启动类
public class ITTechnologyClient {
private static final String IP="255.255.255.255";
public void run(int port){
EventLoopGroup group=new NioEventLoopGroup();
try {
Bootstrap bootstrap=new Bootstrap();
bootstrap.group(group)
.channel(NioDatagramChannel.class)
.option(ChannelOption.SO_BROADCAST,true)
.handler(new ITTechnologyClientHandler());
//该应用占用8888端口,不能和server 的端口相同
Channel channel=bootstrap.bind(8888).sync().channel();
//向网段内的所有机器广播UDP消息
channel.writeAndFlush(new DatagramPacket(
Unpooled.copiedBuffer(ITTechnologyServerHandler.IT_Technology_LEARN, CharsetUtil.UTF_8),
new InetSocketAddress(IP,port))).sync();
if (!channel.closeFuture().await(15000)){
System.out.println("查询超时!");
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) {
new ITTechnologyClient().run(8080);
}
}
客户端业务处理类
public class ITTechnologyClientHandler extends SimpleChannelInboundHandler<DatagramPacket> {
public void exceptionCaught(ChannelHandlerContext context,Throwable cause){
cause.printStackTrace();
context.close();
}
@Override
protected void messageReceived(ChannelHandlerContext channelHandlerContext, DatagramPacket datagramChannel) throws Exception {
String response=datagramChannel.content().toString(CharsetUtil.UTF_8);
if (response.startsWith(ITTechnologyServerHandler.IT_Technology_RESULT)){
System.out.println(response);
channelHandlerContext.close();
}
}
}
代码说明
代码逻辑比较简单。相对于TCP协议,启动类也简单多了。需要注意的是,启动类 server 和client 需要占用不同的端口。并且没有了连接的步骤。也不用添加解码器,Netty 已经支持。
测试
服务端打印截图:
客户端打印截图:
测试结果
每次结果都不一样,并且打印结果正常。说明在这个过程中没有发送丢包和乱序的问题。Netty 对其做了一些优化控制。
总结
本章详细介绍了利用Netty进行UDP服务端和客户端开发。代码比较简单。
然后对UDP协议进行了介绍。大家可以动手试试。