您的位置:首页 > 其它

Netty之UDP协议开发

2016-11-21 20:49 309 查看
文章的开头奉献上代码,方便大家对照学习。

UDP协议简介

UDP是用户数据报协议(User Datagrame Protocol,UDP)的简称,主要作用是将网络数据流压缩成数据报的形式,提供面向事务的简单信息传送服务。

UDP与TCP协议比较:



UDP协议格式:



通过UDP协议的格式图我们可以看出:

1.UDP协议是由首部和数据组成。
2.首部部分很简单,只有8个字节,由四个字段组成,每个字段都是两个字节。各个字段意义分别是:
a.源端口号,这是在源主机上运行的进程所使用的端口号,如果源主机是客户端(发起请求的一方),则在大多数情况下这个端口号是临时端口号,如果源主机是服务器端(发送响应时),则在大多数情况下这个端口号时熟知端口号。
b.目的端口号,这是在目的主机上运行的进程所使用的端口号,如果目的主机是客户端(发起请求的一方),则在大多数情况下这个端口号是临时端口号,服务器需要将这个临时端口号复制下来,如果目的主机是服务器端(发送响应时),则在大多数情况下这个端口号时熟知端口号。
c.长度,定义了用户数据报的总长度,首部加上数据,数据部分的长度范围时0~65507。
d.检验和,这个字段用来检验这个用户数据报(首部加上数据)出现的差错。
3.伪首部:
a.伪首部是用来校验的,它必须和首部中的校验和结合起来使用。
b.在计算检验和时临时加上去的,伪首部既不向下传送也不向上提交,而仅仅时为了计算检验和
c.在计算检验和时,需要在UDP用户数据报之前增加12个字节的伪首部。这个伪首部并不是UDP真正的首部,这是在计算检验和时临时和UDP用户数据报连接在一起,得到一个过渡的UDP用户数据报,检验和就是按照这个过渡的UDP用户数据报来计算的。伪首部既不向下传送也不向上提交,而仅仅时为了计算检验和。UDP计算检验和的方法和计算IP数据报首部检验和的方法相似,不同的是,IP数据报的检验和只是检验IP数据报的首部,但是UDP的检验和是将首部和数据部分一起都检验。


UDP协议的特点:

1.UDP传送数据前并不与对方建立连接,即UDP是无连接的。
2.UDP接收到的数据报不发送确认信号,发送端不知道数据是否被正确接收
3.UDP传送数据比TCP快,系统开销也少;


UDP协议开发

服务端开发

由于UDP通信双方不需要建立链路,所以,代码相对于TCP更加简单一些,代码如下:

启动类 ChineseProverbServer类

/**
* @author 作者 YYD
* @version 创建时间:2016年11月18日 下午8:38:30
* @function 未添加
*/
public class ChineseProverbServer {
public void run(int port) throws Exception{
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap b = new Bootstrap();
//由于我们用的是UDP协议,所以要用NioDatagramChannel来创建
b.group(group).channel(NioDatagramChannel.class)
.option(ChannelOption.SO_BROADCAST, true)//支持广播
.handler(new ChineseProverbServerHandler());//ChineseProverbServerHandler是业务处理类
b.bind(port).sync().channel().closeFuture().await();
}
public static void main(String [] args) throws Exception{
int port = 8080;
new ChineseProverbServer().run(port);
}
}


由于使用UDP通信,在创建Channel的时候通过NioDatagramChannel来创建,随后设置Socket参数支持广播,最后设置处理handler ChineseProverbServerHandler.

相比于TCP通信,UDP不存在客户端和服务器端的实际连接,因此不需要为连接(channelPipeline)设置handler,对于服务端,只需设置启动辅助类的handler即可。

下面看ChineseProverbServerHandler的实现

/**
* @author 作者 YYD
* @version 创建时间:2016年11月18日 下午8:43:10
* @function 未添加
*/
public class ChineseProverbServerHandler extends
SimpleChannelInboundHandler<DatagramPacket> {
//谚语列表
private static final String[] DICTIONARY = { "只要功夫深,铁棒磨成针。",
"旧时王谢堂前燕,飞入寻常百姓家。", "洛阳亲友如相问,一片冰心在玉壶。", "一寸光阴一寸金,寸金难买寸光阴。",
"老骥伏枥,志在千里,烈士暮年,壮心不已" };
private String nextQuote(){
//返回0-DICTIONARY.length中的一个整数。
int quoteId = ThreadLocalRandom.current().nextInt(DICTIONARY.length);
return DICTIONARY[quoteId];//将谚语列表中对应的谚语返回
}
/**
* 在这个方法中,形参packet客户端发过来的DatagramPacket对象
* DatagramPacket 类解释
* 1.官网是这么说的:
* The message container that is used for {@link DatagramChannel} to communicate with the remote peer.
* 翻译:DatagramPacket 是消息容器,这个消息容器被 DatagramChannel使用,作用是用来和远程设备交流
* 2.看它的源码我们发现DatagramPacket是final类不能被继承,只能被使用。我们还发现DatagramChannel最终实现了AddressedEnvelope接口,接下来我们看一下AddressedEnvelope接口。
* AddressedEnvelope接口官网解释如下:
* A message that wraps another message with a sender address and a recipient address.
* 翻译:这是一个消息,这个消息包含发送者和接受者消息
* 3.那我们知道了DatagramPacket它包含了发送者和接受者的消息,
* 通过content()来获取消息内容
* 通过sender();来获取发送者的消息
* 通过recipient();来获取接收者的消息。
*
* 4.public DatagramPacket(ByteBuf data, InetSocketAddress recipient) {}
*  这个DatagramPacket其中的一个构造方法,data 是发送内容;是发送都信息。
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet)
throws Exception {

String req = packet.content().toString(CharsetUtil.UTF_8);//上面说了,通过content()来获取消息内容
System.out.println(req);
if("谚语字典查询?".equals(req)){//如果消息是“谚语字典查询?”,就随机获取一条消息发送出去。
/**
* 重新 new 一个DatagramPacket对象,我们通过packet.sender()来获取发送者的消息。
* 重新发达出去!
*/
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();
}

}


解释:请看channelRead0方法,netty对UDP进行了封装,接收到的是DatagramPacket对象,然后通过packet.content().toString(CharsetUtil.UTF_8)获取packet的内容。如果是“谚语字典查询?”字符串则随机取一个谚语返回。DatagramPacket有二个参数,第一个是发送的内容,另一个是接收者的相关信息,这个可以通过packet.sender()获取。

客户端开发

UDP程序的客户端和服务器端代码非常相似,唯一不同是UDP客户端会主动构造请求消息,向本网段内的所有主机请求消息,对于服务端而言接收到广播消息之后向广播消息的发起方进行定点发送。

启动类ChineseProverbClient类

/**
* @author 作者 YYD
* @version 创建时间:2016年11月18日 下午9:00:11
* @function 未添加
*/
public class ChineseProverbClient {
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 ChineseProverClientHandler());//设置消息处理器
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("查询超时!");
}
} catch (Exception e) {
group.shutdownGracefully();
}
}
public static void main(String [] args) throws Exception{
int port = 8080;

new ChineseProverbClient().run(port);
}
}


解释:创建UDPChannel和设置支持广播属性等与服务端完全一致,由于不需要和服务端建立链路,UDP Channel 创建完成之后,客户端就要主动发送广播消息。而TCP客户端是在客户端和服务端链路建立成功之后由客户端的业务handler发送消息,这就是二者最大区别。

ChineseProverClientHandler类

/**
* @author 作者 YYD
* @version 创建时间:2016年11月18日 下午9:09:18
* @function 未添加
*/
public class ChineseProverClientHandler extends
SimpleChannelInboundHandler<DatagramPacket> {
/**
* DatagramPacket的详细介绍,看服务器的代码注释,这里不重复了。
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg)
throws Exception {
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();
}
}


代码非常简单,接收到服务端的消息之后将其转成字符串然后判断是否以“谚语查询结果:”开头,如果没有发生丢包等问题,数据是完整的,就打印查询结果,然后释放资源。

运行结果:

先启动UDP服务端,然后启动客户端(运行二次),运行结果如下:

服务器运行结果:



客户端1运行结果:



客户端2运行结果:



通过上图我们可以看出,客户端每次运行结果都不一样,说明UDP服务器的查询功能正确,并且客户端成功的接受到了服务器的应答,说明整个过程没有丢包和乱序问题。

结果

文章的结尾奉献上代码,方便大家对照学习。

在技术上自己依旧是个小渣渣,加油勉励自己!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  UDP netty UDP协议