目录
引入依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.17.Final</version>
</dependency>
<!-- 解码and编码器 -->
<dependency>
<groupId>org.msgpack</groupId>
<artifactId>msgpack</artifactId>
<version>0.6.12</version>
</dependency>
解码器
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
import org.msgpack.MessagePack;
import java.util.List;
/**
* @author tom
* @version V1.0
* @date 2020/11/22 17:20
*/
public class MessageDecode extends MessageToMessageDecoder<ByteBuf> {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf msg,
List<Object> out) throws Exception {
final byte[] array;
//获取可读取的字节数
final int length = msg.readableBytes();
//存放数据的字节数组
array = new byte[length];
//读取数据到array数组中
msg.getBytes(msg.readerIndex(), array, 0, length);
MessagePack pack = new MessagePack();
//转化为java对象
out.add(pack.read(array, NettyMessage.class));
}
}
编码器
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import org.msgpack.MessagePack;
/**
* @author tom
* @version V1.0
* @date 2020/11/22 17:24
*/
public class MessageEncode extends MessageToByteEncoder<NettyMessage> {
@Override
protected void encode(ChannelHandlerContext ctx, NettyMessage msg, ByteBuf buf) throws Exception {
MessagePack pack = new MessagePack();
//把java对象转化为字节
byte[] write = pack.write(msg);
buf.writeBytes(write);
}
}
业务逻辑处理器公共类ChannelHandler
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleStateEvent;
/**
* @author tom
* @version V1.0
* @date 2020/11/22 17:30
*/
public abstract class MessageHandler extends ChannelInboundHandlerAdapter {
protected String name;
//记录次数
private int heartbeatCount = 0;
//获取server and client 传入的值
public MessageHandler(String name) {
this.name = name;
}
/**
* 输入数据业务逻辑处理
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
NettyMessage m = (NettyMessage) msg;
int type = m.getType();
switch (type) {
case 1:
//收到心跳消息,进行响应
sendPongMsg(ctx);
break;
case 2:
//发送心跳消息后收到响应
System.out.println(name + " 收到心跳响应消息 " + ctx.channel().remoteAddress());
break;
case 3:
handlerData(ctx,msg);
break;
default:
break;
}
}
protected abstract void handlerData(ChannelHandlerContext ctx,Object msg);
protected void sendPingMsg(ChannelHandlerContext ctx){
NettyMessage message = new NettyMessage();
message.setType(MessageType.PING);
ctx.channel().writeAndFlush(message);
heartbeatCount++;
System.out.println(name + " 发送心跳消息 " + ctx.channel().remoteAddress() + "count :" + heartbeatCount);
}
private void sendPongMsg(ChannelHandlerContext ctx) {
NettyMessage message = new NettyMessage();
message.setType(MessageType.PONG);
ctx.channel().writeAndFlush(message);
heartbeatCount++;
System.out.println(name +" 收到心跳发送响应消息 "+ctx.channel().remoteAddress() +" , count :" + heartbeatCount);
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt)
throws Exception {
IdleStateEvent stateEvent = (IdleStateEvent) evt;
switch (stateEvent.state()) {
case READER_IDLE:
handlerReaderIdle(ctx);
break;
case WRITER_IDLE:
handlerWriterIdle(ctx);
break;
case ALL_IDLE:
handlerAllIdle(ctx);
break;
default:
break;
}
}
/**
* 客户端与服务端在指定时间内没有任何读写请求,就会认为连接是idle的
*
* @param ctx
*/
protected void handlerAllIdle(ChannelHandlerContext ctx) {
System.err.println("---ALL_IDLE---");
}
protected void handlerWriterIdle(ChannelHandlerContext ctx) {
System.err.println("---WRITER_IDLE---");
}
protected void handlerReaderIdle(ChannelHandlerContext ctx) {
System.err.println("---READER_IDLE---");
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.err.println(" ---"+ctx.channel().remoteAddress() +"----- is active" );
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.err.println(" ---"+ctx.channel().remoteAddress() +"----- is no active");
}
}
定义消息类型
/**
* @author tom
* @version V1.0
* @date 2020/11/22 17:17
*/
public interface MessageType {
//心跳消息
byte PING = 1;
//响应消息
byte PONG = 2;
//业务消息
byte CUSTOMER = 3;
}
定义消息对象
import lombok.Data;
import org.msgpack.annotation.Message;
import java.io.Serializable;
/**
* @author tom
* @version V1.0
* @date 2020/11/22 17:16
*/
@Data
@Message
public class NettyMessage implements Serializable {
private static final long serialVersionUID = 1L;
// 消息类型
private int type;
// 消息内容
private String body;
}
Server端消息处理器
import com.example.springbootnetty.longconection.NettyMessage;
import com.example.springbootnetty.longconection.MessageType;
import com.example.springbootnetty.longconection.MessageHandler;
import io.netty.channel.ChannelHandlerContext;
/**
* @author tom
* @version V1.0
* @date 2020/11/22 17:34
*/
public class ServerHandler extends MessageHandler {
public ServerHandler() {
super("server");
}
@Override
protected void handlerData(ChannelHandlerContext ctx, Object msg) {
NettyMessage message = (NettyMessage) msg;
System.out.println("server 接收业务请求并进行业务逻辑处理 : " + message.toString());
message.setType(MessageType.CUSTOMER);
message.setBody("server 业务逻辑处理结果");
ctx.channel().writeAndFlush(message);
System.out.println("server 发送业务逻辑处理结果: " + message.toString());
}
@Override
protected void handlerReaderIdle(ChannelHandlerContext ctx) {
super.handlerReaderIdle(ctx);
System.err.println(" ---- client "+ ctx.channel().remoteAddress().toString() + " reader timeOut, --- close it");
ctx.close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
System.err.println( name +" exception" + cause.toString());
}
}
Server服务器
import com.example.springbootnetty.longconection.MessageDecode;
import com.example.springbootnetty.longconection.MessageEncode;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.timeout.IdleStateHandler;
/**
* @author tom
* @version V1.0
* @date 2020/11/22 17:38
*/
public class Server {
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup(4);
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup)
.channel(NioServerSocketChannel.class)
.localAddress(8081)
.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new IdleStateHandler(10,0,0));
pipeline.addLast(new MessageDecode());
pipeline.addLast(new MessageEncode());
pipeline.addLast(new ServerHandler());
}
});
System.out.println("start server 8081 --");
ChannelFuture sync = serverBootstrap.bind().sync();
sync.channel().closeFuture().sync();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
//优雅的关闭资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
client端消息处理器
import com.example.springbootnetty.longconection.MessageHandler;
import com.example.springbootnetty.longconection.NettyMessage;
import io.netty.channel.ChannelHandlerContext;
/**
* @author tom
* @version V1.0
* @date 2020/11/22 17:33
*/
public class ClientHandler extends MessageHandler {
private Client client;
public ClientHandler(Client client) {
super("client");
this.client = client;
}
@Override
protected void handlerData(ChannelHandlerContext ctx, Object msg) {
NettyMessage message = (NettyMessage) msg;
System.out.println("client 收到 server 业务逻辑处理结果: " + message.toString());
}
@Override
protected void handlerAllIdle(ChannelHandlerContext ctx) {
super.handlerAllIdle(ctx);
sendPingMsg(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
client.doConnect();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
System.out.println(name + "exception :"+ cause.toString());
}
}
client客户端
import com.example.springbootnetty.longconection.MessageDecode;
import com.example.springbootnetty.longconection.MessageEncode;
import com.example.springbootnetty.longconection.MessageType;
import com.example.springbootnetty.longconection.NettyMessage;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.IdleStateHandler;
import java.util.concurrent.TimeUnit;
/**
* @author tom
* @version V1.0
* @date 2020/11/22 17:28
*/
public class Client {
private NioEventLoopGroup worker = new NioEventLoopGroup();
private Channel channel;
private Bootstrap bootstrap;
public static void main(String[] args) throws InterruptedException {
Client client = new Client();
client.start();
client.sendData("Hello netty 这是一条业务处理请求");
}
private void start() {
bootstrap = new Bootstrap();
bootstrap.group(worker)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new IdleStateHandler(0, 0, 5));
pipeline.addLast(new MessageDecode());
pipeline.addLast(new MessageEncode());
pipeline.addLast(new ClientHandler(Client.this));
}
});
doConnect();
}
/**
* 连接服务端 and 重连
*/
protected void doConnect() {
if (channel != null && channel.isActive()) {
return;
}
ChannelFuture connect = bootstrap.connect("127.0.0.1", 8081);
// 实现监听通道连接的方法
connect.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
if (channelFuture.isSuccess()) {
System.out.println("连接状态正常---------" + channel.isActive());
} else {
System.out.println("每隔2s重连....");
channelFuture.channel().eventLoop().schedule(new Runnable() {
@Override
public void run() {
doConnect();
}
}, 2, TimeUnit.SECONDS);
}
}
});
channel = connect.channel(); // 作用域问题,分包处理会拿不到 channel,赋值一下下面的 sendData 才可以拿到正确的channel
}
/**
* 向服务端发送消息
*/
private void sendData(String msg) {
try {
for (int i = 0; i < 10; i++) {
Thread.sleep(5000);
if (channel != null && channel.isActive()) {
//获取一个键盘扫描器
NettyMessage message = new NettyMessage();
message.setType(MessageType.CUSTOMER);
message.setBody(msg);
System.out.println("第" + (i + 1) + "次向server发送业务处理请求");
channel.writeAndFlush(message);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("线程被中断");
}
}
}
先启动Server再启动client,client发送业务处理请求到Server端,Server端处理后返回业务逻辑处理结果给client,当最大空闲时间内客户端与服务端在指定时间内没有任何读写请求,会进行心跳检测