需求分析
“服务端IP地址发生变化后,仍然与客户端保持长链接”
- 服务端IP地址发生变化后,通过广播机制发送ip地址。
- 客户端收到UDP的信息后,重新初始化TCP长链接。
- 客户端校验UDP信息是否重复,避免多次初始化。
- 验证跨网段的广播发送
逻辑过程
- 初始化服务端的广播
- 初始化客户端的广播
- 局域网网段内发广播
- 广播UDP信息协议处理
code 服务端
netty 用的4.0,因为在初始化的过程中 出现了.await()方法,这个方法在源码中是堵塞的,所以初始化的过程应该切换到子线程中,否则会触发ANR响应。引导者只能使用Bootstrap ,测试服务端的ServerBootstrap 失败。以下是初始化的基本配置,具体原理可查看w3c的教学内容 ,此处不做更多解释,只是方便你快速使用。
/**
* 广播初始化
*/
public void initBroadCast(NettyServerInterface serverInterface) {
mUdpChannelHandler = new UDPChannelHandler(serverInterface);
new Thread() {
@Override
public void run() {
super.run();
try {
NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap(); //是否可以和服务端 共用待确认ServerBootstrap
bootstrap.group(eventLoopGroup)
.channel(NioDatagramChannel.class)
.option(ChannelOption.SO_BROADCAST, true)
.handler(mUdpChannelHandler);
updChannel = bootstrap.bind(9999).sync().channel();
updChannel.closeFuture().await();
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
}
code客户端
客户端初始化和服务端基本一致,主要注意如何处理信息发送。
private void initUDP() {
new Thread() {
@Override
public void run() {
super.run();
try {
Bootstrap bootstrap;
EventLoopGroup group;
group = new NioEventLoopGroup();
bootstrap = new Bootstrap();
bootstrap.group(group) //1
.channel(NioDatagramChannel.class)
.option(ChannelOption.SO_BROADCAST, true)
.handler(new UDPChannelHandler(CoreService.this));
Channel channel = bootstrap.bind(9999).sync().channel();
channel.closeFuture().await();
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
}
服务端发送
此处的udpChannel 就是初始化时生成的,广播发送的消息载体规定了用 DatagramPacket(注意导包非java 内的),InetSocketAddress 构造函数内可以注入广播发送的网络范围,255.255.255.255是同一网段的局域网内发送,9999指定端口号接收,即客户端监听也需要是这个端口。code 中加了前后缀就行数据包装!!IP@@。
public static void sendUDP() {
if (updChannel != null) {
String ip = "!!" + localIP + "@@";
ByteBuf byteBuf = Unpooled.copiedBuffer(ip, CharsetUtil.UTF_8);
DatagramPacket packet = new DatagramPacket(byteBuf, new InetSocketAddress("255.255.255.255", 9999)); //跨网段发广播待验证。
updChannel.writeAndFlush(packet);
Log.i(TAG, "sendUDP: " + ip);
}
}
客户端接收
handler 是继承simpleChannelInboundHandler 改类内部有个方法channelRead0,可以提前实现泛型转换,因服务端发送的消息体是DatagramPacket,所以保持一致性,接收端没有做任何加密,解密的操作,直接泛型转换即可,如果你的channelRead0()不执行,很有可能是你导包导错了。
package com.nes.air.controlair.netty;
import android.util.Log;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;
/**
* 广播 channel
*/
public class UDPChannelHandler extends SimpleChannelInboundHandler<DatagramPacket> {
private static final String TAG = "UDPChannelHandler";
private UDPInterface mUdpInterface;
public UDPChannelHandler(UDPInterface udpInterface) {
this.mUdpInterface=udpInterface;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
Log.i(TAG, "channelActive: ");
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
Log.i(TAG, "channelInactive: ");
}
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, DatagramPacket packet) throws Exception {
String data = packet.content().toString(CharsetUtil.UTF_8);
Log.i(TAG, "channelRead0: "+data);
mUdpInterface.channelRead0(data);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
Log.i(TAG, "exceptionCaught: ");
}
}
希望对你有帮助!