Channel
1.关闭
try {
if (channel != null) {
channel.close();
channel.eventLoop().shutdownGracefully();
}
} catch (Exception e) {
e.printStackTrace();
System.err.println("关闭channel出错,reason:" + e.getMessage());
} finally {
channel = null;
}
Bootstrap
1.关闭
bootstrap.group().shutdownGracefully();
2.初始化
EventLoopGroup loopGroup = new NioEventLoopGroup(4);
bootstrap = new Bootstrap();
bootstrap.group(loopGroup).channel(NioSocketChannel.class);
// 设置该选项以后,如果在两小时内没有数据的通信时,TCP会自动发送一个活动探测数据报文
bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
// 设置禁用nagle算法
bootstrap.option(ChannelOption.TCP_NODELAY, true);
// 设置连接超时时长
bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, getConnectTimeout());
// 设置初始化Channel
bootstrap.handler(new TCPChannelInitializerHandler(this));
ChannelInitializer<Channel>
通道的初始化,添加消息处理器
public class TCPChannelInitializerHandler extends ChannelInitializer<Channel> {
private NettyTcpClient imsClient;
public TCPChannelInitializerHandler(NettyTcpClient imsClient) {
this.imsClient = imsClient;
}
@Override
protected void initChannel(Channel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
// netty提供的自定义长度解码器,解决TCP拆包/粘包问题
pipeline.addLast("frameEncoder", new LengthFieldPrepender(2));
pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(65535,
0, 2, 0, 2));
// 增加protobuf编解码支持
pipeline.addLast(new ProtobufEncoder());
pipeline.addLast(new ProtobufDecoder(MessageProtobuf.Msg.getDefaultInstance()));
// 握手认证消息响应处理handler
pipeline.addLast(LoginAuthRespHandler.class.getSimpleName(), new LoginAuthRespHandler(imsClient));
// 心跳消息响应处理handler
pipeline.addLast(HeartbeatRespHandler.class.getSimpleName(), new HeartbeatRespHandler(imsClient));
// 接收消息处理handler
pipeline.addLast(TCPReadHandler.class.getSimpleName(), new TCPReadHandler(imsClient));
}
}
ChannelInboundHandlerAdapter
继承该类来实现消息处理
示例
public class LoginAuthRespHandler extends ChannelInboundHandlerAdapter {
private NettyTcpClient imsClient;
public LoginAuthRespHandler(NettyTcpClient imsClient) {
this.imsClient = imsClient;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Log.d("消息日志", "loginauth收到消息");
MessageProtobuf.Msg handshakeRespMsg = (MessageProtobuf.Msg) msg;
if (handshakeRespMsg == null || handshakeRespMsg.getHead() == null) {
return;
}
MessageProtobuf.Msg handshakeMsg = imsClient.getHandshakeMsg();
if (handshakeMsg == null || handshakeMsg.getHead() == null) {
return;
}
int handshakeMsgType = handshakeMsg.getHead().getMsgType();
if (handshakeMsgType == handshakeRespMsg.getHead().getMsgType()) {
System.out.println("收到服务端握手响应消息,message=" + handshakeRespMsg);
int status = -1;
try {
JSONObject jsonObj = JSON.parseObject(handshakeRespMsg.getHead().getExtend());
status = jsonObj.getIntValue("status");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (status == 1) {
// 握手成功,马上先发送一条心跳消息,至于心跳机制管理,交由HeartbeatHandler
MessageProtobuf.Msg heartbeatMsg = imsClient.getHeartbeatMsg();
if (heartbeatMsg == null) {
return;
}
// 握手成功,检查消息发送超时管理器里是否有发送超时的消息,如果有,则全部重发
imsClient.getMsgTimeoutTimerManager().onResetConnected();
System.out.println("发送心跳消息:" + heartbeatMsg + "当前心跳间隔为:" + imsClient.getHeartbeatInterval() + "ms\n");
imsClient.sendMsg(heartbeatMsg);
// 添加心跳消息管理handler
imsClient.addHeartbeatHandler();
} else {
imsClient.resetConnect(false);// 握手失败,触发重连
}
}
} else {
// 消息透传
ctx.fireChannelRead(msg);
}
}
}
- ChannelHandlerContext#fireChannelRead(msg) 若不处理消息,使用该方法把消息往后传递
简易服务端代码
public class NettyServerDemo {
public static void main(String[] args) {
//boss线程监听端口,worker线程负责数据读写
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup worker = new NioEventLoopGroup();
try {
//辅助启动类
ServerBootstrap bootstrap = new ServerBootstrap();
//设置线程池
bootstrap.group(boss, worker);
//设置socket工厂
bootstrap.channel(NioServerSocketChannel.class);
//设置管道工厂
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//获取管道
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast("frameEncoder", new LengthFieldPrepender(2));
pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(65535,
0, 2, 0, 2));
pipeline.addLast(new ProtobufDecoder(MessageProtobuf.Msg.getDefaultInstance()));
pipeline.addLast(new ProtobufEncoder());
//处理类
pipeline.addLast(new ServerHandler());
}
});
//设置TCP参数
//1.链接缓冲池的大小(ServerSocketChannel的设置)
bootstrap.option(ChannelOption.SO_BACKLOG, 1024);
//维持链接的活跃,清除死链接(SocketChannel的设置)
bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
//关闭延迟发送
bootstrap.childOption(ChannelOption.TCP_NODELAY, true);
//绑定端口
ChannelFuture future = bootstrap.bind(8855).sync();
System.out.println("server start ...... ");
//等待服务端监听端口关闭
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//优雅退出,释放线程池资源
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
class ServerHandler extends ChannelInboundHandlerAdapter {
private static final String TAG = ServerHandler.class.getSimpleName();
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
System.out.println("ServerHandler channelActive()" + ctx.channel().remoteAddress());
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
System.out.println("ServerHandler channelInactive()");
// 用户断开连接后,移除channel
ChannelContainer.getInstance().removeChannelIfConnectNoActive(ctx.channel());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
System.out.println("ServerHandler exceptionCaught()");
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
super.userEventTriggered(ctx, evt);
System.out.println("ServerHandler userEventTriggered()");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
MessageProtobuf.Msg message = (MessageProtobuf.Msg) msg;
System.out.println("收到来自客户端的消息:" + message);
int msgType = message.getHead().getMsgType();
switch (msgType) {
// 握手消息
case 1001: {
String fromId = message.getHead().getFromId();
JSONObject jsonObj = JSON.parseObject(message.getHead().getExtend());
String token = jsonObj.getString("token");
JSONObject resp = new JSONObject();
if (token.equals("token_" + fromId)) {
resp.put("status", 1);
// 握手成功后,保存用户通道
ChannelContainer.getInstance().saveChannel(new NettyChannel(fromId, ctx.channel()));
} else {
resp.put("status", -1);
ChannelContainer.getInstance().removeChannelIfConnectNoActive(ctx.channel());
}
message = message.toBuilder().setHead(message.getHead().toBuilder().setExtend(resp.toString()).build()).build();
ChannelContainer.getInstance().getActiveChannelByUserId(fromId).getChannel().writeAndFlush(message);
break;
}
// 心跳消息
case 1002: {
// 收到心跳消息,原样返回
String fromId = message.getHead().getFromId();
ChannelContainer.getInstance().getActiveChannelByUserId(fromId).getChannel().writeAndFlush(message);
break;
}
case 2001: {
// 收到2001或3001消息,返回给客户端消息发送状态报告
String fromId = message.getHead().getFromId();
MessageProtobuf.Msg.Builder sentReportMsgBuilder = MessageProtobuf.Msg.newBuilder();
MessageProtobuf.Head.Builder sentReportHeadBuilder = MessageProtobuf.Head.newBuilder();
sentReportHeadBuilder.setMsgId(message.getHead().getMsgId());
sentReportHeadBuilder.setMsgType(1010);
sentReportHeadBuilder.setTimestamp(System.currentTimeMillis());
sentReportHeadBuilder.setStatusReport(1);
sentReportMsgBuilder.setHead(sentReportHeadBuilder.build());
ChannelContainer.getInstance().getActiveChannelByUserId(fromId).getChannel().writeAndFlush(sentReportMsgBuilder.build());
// 同时转发消息到接收方
String toId = message.getHead().getToId();
ChannelContainer.getInstance().getActiveChannelByUserId(toId).getChannel().writeAndFlush(message);
break;
}
case 3001: {
// todo 群聊,自己实现吧,toId可以是群id,根据群id查找所有在线用户的id,循环遍历channel发送即可。
break;
}
default:
break;
}
}
public static class ChannelContainer {
private ChannelContainer() {
}
private static final ChannelContainer INSTANCE = new ChannelContainer();
public static ChannelContainer getInstance() {
return INSTANCE;
}
private final Map<String, NettyChannel> CHANNELS = new ConcurrentHashMap<>();
public void saveChannel(NettyChannel channel) {
if (channel == null) {
return;
}
CHANNELS.put(channel.getChannelId(), channel);
}
public NettyChannel removeChannelIfConnectNoActive(Channel channel) {
if (channel == null) {
return null;
}
String channelId = channel.id().toString();
return removeChannelIfConnectNoActive(channelId);
}
public NettyChannel removeChannelIfConnectNoActive(String channelId) {
if (CHANNELS.containsKey(channelId) && !CHANNELS.get(channelId).isActive()) {
return CHANNELS.remove(channelId);
}
return null;
}
public String getUserIdByChannel(Channel channel) {
return getUserIdByChannel(channel.id().toString());
}
public String getUserIdByChannel(String channelId) {
if (CHANNELS.containsKey(channelId)) {
return CHANNELS.get(channelId).getUserId();
}
return null;
}
public NettyChannel getActiveChannelByUserId(String userId) {
for (Map.Entry<String, NettyChannel> entry : CHANNELS.entrySet()) {
if (entry.getValue().getUserId().equals(userId) && entry.getValue().isActive()) {
return entry.getValue();
}
}
return null;
}
}
public class NettyChannel {
private String userId;
private Channel channel;
public NettyChannel(String userId, Channel channel) {
this.userId = userId;
this.channel = channel;
}
public String getChannelId() {
return channel.id().toString();
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public Channel getChannel() {
return channel;
}
public void setChannel(Channel channel) {
this.channel = channel;
}
public boolean isActive() {
return channel.isActive();
}
}
}