配置Netty服务端
package com.chalk.netty.server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.net.InetSocketAddress;
/**
* Netty配置类
*/
@Component
@Order(100)
public class NettyServer {
private static final Logger logger = LoggerFactory.getLogger(NettyServer.class);
public void start(InetSocketAddress address) {
/* 配置服务端的NIO线程组 */
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap()
.group(bossGroup, workerGroup)//绑定线程池
.channel(NioServerSocketChannel.class)
.localAddress(address)
.childHandler(new NettyServerChannelInitializer())//编码解码
.option(ChannelOption.SO_BACKLOG, 128)//服务端接受连接的队列长度,如果队列已满,客户端连接将被拒绝
.childOption(ChannelOption.SO_KEEPALIVE, true);//保持长连接,2小时无数据激活心跳机制
// 绑定端口,开始接收进来的连接 / 绑定对应ip和端口,同步等待成功
ChannelFuture future = bootstrap.bind(address).sync();
logger.info("Server start listen at " + address.getPort());
//关闭channel和块,直到它被关闭 / 等待服务端监听端口关闭
future.channel().closeFuture().sync();
System.out.println("--------------------"+Thread.currentThread().getName()+"--------------------------");
} catch (Exception e) {
e.printStackTrace();
// bossGroup.shutdownGracefully();
// workerGroup.shutdownGracefully();
// logger.info("Server Closed:{}", e.getMessage());
}
finally {
//优雅退出,释放 NIO 线程组
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
配置Netty从客户端获取、发送数据的编码格式
package com.chalk.netty.server;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
/**
*
* 配置Netty从客户端获取、发送数据的编码格式
*
*/
public class NettyServerChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
//获取客户端数据的编码格式
channel.pipeline().addLast("decoder",new StringDecoder(CharsetUtil.UTF_8));
//发送给客户端数据的编码格式
channel.pipeline().addLast("encoder",new StringEncoder(CharsetUtil.UTF_8));
//添加Netty处理类
channel.pipeline().addLast(new NettyServerHandler());
}
}
添加Netty处理类
package com.chalk.netty.server;
import io.netty.channel.*;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.net.InetSocketAddress;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Netty处理类
*/
@Slf4j
@Component
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
/**
* 管理一个全局map,保存连接进服务端的通道数量
*/
private static final ConcurrentHashMap<ChannelId, ChannelHandlerContext> CHANNEL_MAP = new ConcurrentHashMap<>();
/**
* 所有的channel
*/
private static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
/**
* 存储设备号与channel的Map
*/
private static Map<String, Channel> channelMap = new ConcurrentHashMap<>();
/**
* 存储channel与设备号的Map
*/
private static Map<Channel, String> channelReverseMap = new ConcurrentHashMap<>();
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Channel incoming = ctx.channel();
channels.add(incoming);
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
Channel incoming = ctx.channel();
channels.remove(incoming);
}
/**
* 服务端监听到客户端活动 / 只要有客户端连接就进行这个方法
*
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Channel incoming = ctx.channel();
log.info("ChatClient:{},在线", incoming.remoteAddress());
/* ------ */
InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
String clientIp = insocket.getAddress().getHostAddress();
int clientPort = insocket.getPort();
//获取连接通道唯一标识
ChannelId channelId = ctx.channel().id();
//如果map中不包含此连接,就保存连接
if (CHANNEL_MAP.containsKey(channelId)) {
log.info("客户端【" + channelId + "】是连接状态,连接通道数量: " + CHANNEL_MAP.size());
} else {
//保存连接
CHANNEL_MAP.put(channelId, ctx);
log.info("客户端【" + channelId + "】连接netty服务器[IP:" + clientIp + "--->PORT:" + clientPort + "]");
log.info("连接通道数量: " + CHANNEL_MAP.size());
}
/* ------ */
}
/**
* 服务端监听到客户端不活动 / 客户端终止连接就进入
*
* @param ctx
* @throws Exception
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
/* ------ */
InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
String clientIp = insocket.getAddress().getHostAddress();
//获取终止连接的客户端ID
ChannelId channelId = ctx.channel().id();
//包含此客户端才去删除
if (CHANNEL_MAP.containsKey(channelId)) {
//删除连接
CHANNEL_MAP.remove(channelId);
System.out.println();
log.info("客户端【" + channelId + "】退出netty服务器[IP:" + clientIp + "--->PORT:" + insocket.getPort() + "]");
log.info("连接通道数量: " + CHANNEL_MAP.size());
}
/* ------ */
// Channel incoming = ctx.channel();
// channels.remove(incoming);
// String key = channelReverseMap.get(incoming);
// if (StringUtils.isNotBlank(key)) {
// channelMap.remove(key);
// }
// channelReverseMap.remove(incoming);
// log.info("ChatClient:{},掉线", incoming.remoteAddress());
}
/**
* @param ctx
* @DESCRIPTION: 有客户端发消息会触发此函数
* @return: void
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.info("加载客户端报文......");
log.info("【" + ctx.channel().id() + "】" + " :" + msg);
Channel incoming = ctx.channel();
log.info("From Client:{}, msg:{}", incoming.remoteAddress(), msg.toString());
//以下可以做获取客户端数据后返回
//响应客户端
this.channelWrite(ctx.channel().id(), msg);
// 消息处理
// messageManage(incoming, msg.toString());
}
/**
* @param msg 需要发送的消息内容
* @param channelId 连接通道唯一id
* @DESCRIPTION: 服务端给客户端发送消息
* @return: void
*/
public void channelWrite(ChannelId channelId, Object msg) throws Exception {
ChannelHandlerContext ctx = CHANNEL_MAP.get(channelId);
if (ctx == null) {
log.info("通道【" + channelId + "】不存在");
return;
}
if (msg == null && msg == "") {
log.info("服务端响应空的消息");
return;
}
log.info("服务端响应------------------------------------------");
//将客户端的信息直接返回写入ctx
ctx.write(msg);
//刷新缓存区
ctx.flush();
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
String socketString = ctx.channel().remoteAddress().toString();
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state() == IdleState.READER_IDLE) {
log.info("Client: " + socketString + " READER_IDLE 读超时");
ctx.disconnect();
} else if (event.state() == IdleState.WRITER_IDLE) {
log.info("Client: " + socketString + " WRITER_IDLE 写超时");
ctx.disconnect();
} else if (event.state() == IdleState.ALL_IDLE) {
log.info("Client: " + socketString + " ALL_IDLE 总超时");
ctx.disconnect();
}
}
}
/**
* 连接错误时调用
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
Channel incoming = ctx.channel();
log.info("ChatClient:{},异常", incoming.remoteAddress());
// 当出现异常就关闭连接
cause.printStackTrace();
ctx.close();
log.info(ctx.channel().id() + " 发生了错误,此连接被关闭" + "此时连通数量: " + CHANNEL_MAP.size());
}
}
配置Netty客户端
package com.chalk.netty.client;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import java.util.concurrent.TimeUnit;
/**
* @ClassName: NettyClinet
* @Author: gluoh
* @Date: 2021/02/24 15:29
* @Description:
*/
@Slf4j
public class NettyClient {
@Value("${printer.server.host}")
private String host;
@Value("${printer.server.port}")
private int port;
private static Channel channel;
public NettyClient(){
}
public NettyClient(String host, int port) {
this.host = host;
this.port = port;
}
public void start() {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.option(ChannelOption.SO_KEEPALIVE,true)
.channel(NioSocketChannel.class)
.handler(new NettyClientChannelInitializer(host,port));
ChannelFuture f = b.connect(host,port).sync();
//断线重连
f.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
if (!channelFuture.isSuccess()) {
final EventLoop loop = channelFuture.channel().eventLoop();
loop.schedule(new Runnable() {
@Override
public void run() {
log.error("服务端链接不上,开始重连操作...");
System.err.println("服务端链接不上,开始重连操作...");
start();
}
}, 1L, TimeUnit.SECONDS);
} else {
channel = channelFuture.channel();
log.info("服务端链接成功...");
System.err.println("服务端链接成功...");
}
}
});
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
new NettyClient("127.0.0.1",7005).start();
}
}
package com.chalk.netty.client;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import java.util.concurrent.TimeUnit;
/**
* @ClassName: ClientChannelInitializer
* @Author: gluoh
* @Date: 2021/02/24 15:29
* @Description:
*/
public class NettyClientChannelInitializer extends ChannelInitializer<SocketChannel> {
private String host;
private int port;
public NettyClientChannelInitializer(String host, int port) {
this.host = host;
this.port = port;
}
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast("decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers
.weakCachingConcurrentResolver(this.getClass().getClassLoader())));
pipeline.addLast("encoder", new ObjectEncoder());
//心跳检测
pipeline.addLast(new IdleStateHandler(0,5,0,TimeUnit.SECONDS));
//客户端的逻辑
pipeline.addLast("handler", new NettyClientHandler(host,port));
}
}
package com.chalk.netty.client;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.EventLoop;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
/**
* @ClassName: NettyClientHandler
* @Author: gluoh
* @Date: 2021/02/24 15:29
* @Description:
*/
@Slf4j
public class NettyClientHandler extends SimpleChannelInboundHandler {
private String host;
private int port;
private NettyClient nettyClient;
public NettyClientHandler(String host, int port) {
this.host = host;
this.port = port;
nettyClient = new NettyClient(host,port);
}
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o)
throws Exception {
String response = "{\"data_id\":\"123\",\"message\":\"已接收请求\",\"gatename\":\"入口\",\"gateid\":\"1\",\"service\":\"setopenstrobe\",\"result_code\":0}";
System.out.println(response);
if (o.toString().startsWith("<")){
log.info("response:{}", response);
channelHandlerContext.writeAndFlush(response);
}
System.out.println("Server say : " + o.toString());;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("通道已连接!!");
String login = "{\"service\":\"checkKey\",\"parkid\":\"301019120373\",\"parkkey\":\"C80FB9B8-73E8-4C03-B300-2037F14F42C6\"}";
log.info("login:{}", login);
ctx.channel().writeAndFlush(login);
super.channelActive(ctx);
}
/**
* @param ctx
* @DESCRIPTION: 有服务端端终止连接服务器会触发此函数
* @return: void
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("断线了。。。。。。");
//使用过程中断线重连
final EventLoop eventLoop = ctx.channel().eventLoop();
eventLoop.schedule(new Runnable() {
@Override
public void run() {
nettyClient.start();
}
}, 1, TimeUnit.SECONDS);
ctx.fireChannelInactive();
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt)
throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state().equals(IdleState.READER_IDLE)) {
System.out.println("READER_IDLE");
} else if (event.state().equals(IdleState.WRITER_IDLE)) {
/**发送心跳,保持长连接*/
String heart = new String("{\"service\":\"heartbeat\",\"parkid\":\"301019120373\",\"time\":\"2020-03-30 16:26:18\"}");
log.info("heart:{}", heart);
ctx.channel().writeAndFlush(heart);
log.debug("心跳发送成功!");
System.out.println("心跳发送成功!");
} else if (event.state().equals(IdleState.ALL_IDLE)) {
System.out.println("ALL_IDLE");
}
}
super.userEventTriggered(ctx, evt);
}
}
yml配置
netty:
port: 7005
# 公司测试私有IP
# url:
url: 127.0.0.1