下面是原始的,后来被我慢慢改造了下,最新可以访问
https://github.com/xiaoyunhe-coldcast/coldcast.git
直接上代码,不多逼逼。
(1)服务端
package com.coldcast.netty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.coldcast.util.logUtil;
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;
/**
* Title : Netty服务端
* @{author} Administrator
* @{date} 2020年3月31日
* @{description}
*/
@Service
public class NettyServer {
//boss事件轮询线程组 ,处理连接事件
private EventLoopGroup bossGroup = new NioEventLoopGroup();
//worker事件轮询线程组, 用于数据处理
private EventLoopGroup workerGroup = new NioEventLoopGroup();
@Autowired
private NettyServerInitializer nettyServerInitializer;
@Value("${netty.port}")
private Integer port;
/**
* 开启Netty服务
*
* @return
*/
public void start() {
try {
//启动类
ServerBootstrap serverBootstrap = new ServerBootstrap();
//设置参数,组配置
serverBootstrap.group(bossGroup, workerGroup)
//socket参数,当服务器请求处理程全满时,用于临时存放已完成三次握手的请求的队列的最大长度。
// 如果未设置或所设置的值小于1,Java将使用默认值50。
//
// 服务端可连接队列数,对应TCP/IP协议listen函数中backlog参数
.option(ChannelOption.SO_BACKLOG, 128)
// 设置TCP长连接,一般如果两个小时内没有数据的通信时,TCP会自动发送一个活动探测数据报文
// 建议长连接的时候打开,心跳检测
.childOption(ChannelOption.SO_KEEPALIVE, true)
// 构造channel通道工厂//bossGroup的通道,只是负责连接
.channel(NioServerSocketChannel.class)
// 设置通道处理者ChannelHandlerworkerGroup的处理器
.childHandler(nettyServerInitializer);
// 绑定端口,开始接收进来的连接
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
logUtil.info("netty服务启动: [port:" + port + "]");
// 等待服务器socket关闭
// 应用程序会一直等待,直到channel关闭
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
logUtil.error("netty服务启动异常-" + e.getMessage());
} finally {
// 优雅的关闭服务端
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
核心就是创建了两个group,一个boss,一个work.
(2)客户端代码
package com.coldcast.netty;
import java.time.LocalDate;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.springframework.stereotype.Component;
import com.coldcast.util.logUtil;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.NoArgsConstructor;
/**
* Title : 客户端
* @{author} Administrator
* @{date} 2020年3月31日
* @{description}
*/
@NoArgsConstructor
@Component
public class NettyClient {
private EventLoopGroup group = new NioEventLoopGroup();
// 定时线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
/**
*@{author} 连接
*@{date} 2020年4月1日
*@{tags} @param ip
*@{tags} @param port
*/
public Bootstrap connect(String ip,int port) {
Channel channel = null;
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.group(group).channel(NioSocketChannel.class);
// 将小的数据包包装成更大的帧进行传送,提高网络的负载,即TCP延迟传输
bootstrap.option(ChannelOption.TCP_NODELAY, true);
// 设置TCP的长连接,默认的 keepalive的心跳时间是两个小时
// bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
bootstrap.handler(new NettyClientInitializer());
channel = bootstrap.connect(ip, port).sync().channel();
// 处理
handler(channel);
// 应用程序会一直等待,直到channel关闭
channel.closeFuture().sync();
return bootstrap;
} catch (Exception e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
return bootstrap;
}
/**
*@{author} 业务逻辑
*@{date} 2020年4月1日
*@{tags} @param channel
*/
private void handler(Channel channel) {
//如果任务里面执行的时间大于 period 的时间,下一次的任务会推迟执行。
//本次任务执行完后下次的任务还需要延迟period时间后再执行
scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
System.out.println("====定时任务开始====");
// 发送json字符串
LocalDate now = LocalDate.now();
String msg = "{\"time\":\""+now.toString()+"\",\"name\":\"admin\",\"age\":27}\n";
channel.writeAndFlush(msg);
}
}, 2, 10, TimeUnit.SECONDS);
channel.writeAndFlush("cient开始处理业务");
System.out.println("开始处理业务");
}
/**
*@{author} 发送消息
*@{date} 2020年4月1日
*@{tags} @param channel
*@{tags} @param message
*@{tags} @return
*/
public String sendMessage(String ip, int port,String message){
try{
Bootstrap bootstrap = connect(ip, port);
Channel channel = bootstrap.connect(ip, port).sync().channel();
channel.writeAndFlush(message);
logUtil.info(message);
return "消息发送成功";
}catch (Exception e) {
e.printStackTrace();
logUtil.error("发送消息失败");
return "发送消息失败";
}
};
/**
* 主动关闭
*/
public void close() {
group.shutdownGracefully();
}
// /**
// * 测试入口
// *
// * @param args
// */
// public static void main(String[] args) {
// NettyClient nettyClient = new NettyClient();
// nettyClient.connect("127.0.0.1",8090);
// }
}
客户端创建一个group就行了,这里消息发送使用的channel,做的一个定时发送,一直发消息。
(3)心跳检测
package com.coldcast.netty;
import org.springframework.stereotype.Component;
import com.coldcast.util.logUtil;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
/**
* Title : 服务端心跳检查
* @{author} Administrator
* @{date} 2020年3月31日
* @{description}
*/
@Component
@ChannelHandler.Sharable
public class AcceptorIdleStateTrigger extends ChannelInboundHandlerAdapter {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
if (idleStateEvent.state() == IdleState.ALL_IDLE) {
logUtil.info("已经5秒未收到客户端的消息了!");
//向服务端送心跳包
String heartbeat = "{\"msg\":\"server heart beat\"}\n";
//发送心跳消息,并在发送失败时关闭该连接
ctx.writeAndFlush(heartbeat)
.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
}
} else {
super.userEventTriggered(ctx, evt);
}
}
/*
//备注 , 状态
switch (event.state()){
case READER_IDLE:
eventType = "读空闲";
break;
case WRITER_IDLE:
eventType = "写空闲";
break;
case ALL_IDLE:
eventType ="读写空闲";
break;
}*/
}
这里的心跳检测用的就是消息的另一种形式,使用ChannelHandlerContext,起初是只监控读被我改成所有的了。
(4)NettyServerHandler 服务端处理业务类,
package com.coldcast.netty;
import java.net.InetSocketAddress;
import java.util.Map;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import com.coldcast.util.logUtil;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/**
* Title : 服务端处理器
* @{author} Administrator
* @{date} 2020年3月31日
* @{description}
*/
@Component
// 这个注解适用于标注一个channel handler可以被多个channel安全地共享
// 也可以使用new NettyServerHandler()方式解决
@ChannelHandler.Sharable
public class NettyServerHandler extends SimpleChannelInboundHandler<String> {
/**
* String 也可以是Object类型
*
* @param ctx
* @param msg
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
logUtil.info("接收到客户端的消息:{}", msg);
StringBuilder sb = null;
Map<String, Object> result = null;
try {
// 报文解析处理
sb = new StringBuilder();
result = JSON.parseObject(msg);
sb.append(result);
sb.append("解析成功");
sb.append("\n");
ctx.writeAndFlush(sb);
} catch (Exception e) {
String errorCode = "-1\n";
ctx.writeAndFlush(errorCode);
logUtil.error("报文解析失败: " + e.getMessage());
}
}
/**
* 客户端去和服务端连接成功时触发
*
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
String clientIp = insocket.getAddress().getHostAddress();
logUtil.info("收到客户端[ip:" + clientIp + "]连接");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// 当出现异常就关闭连接
ctx.close();
//把客户端的通道关闭
ctx.channel().close();
}
}
(5)NettyClientHandler 客户端业务处理类
package com.coldcast.netty;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/**
* Title : 客户端处理
* @{author} Administrator
* @{date} 2020年3月31日
* @{description}
*/
public class NettyClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("收到服务端消息: " + msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
(6)NettyServerInitializer服务端初始化
package com.coldcast.netty;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.CharsetUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* Title : service初始化配置
* @{author} Administrator
* @{date} 2020年3月31日
* @{description}
*/
@Component
public class NettyServerInitializer extends ChannelInitializer<SocketChannel> {
@Autowired
private NettyServerHandler nettyServerHandler;
@Autowired
private AcceptorIdleStateTrigger idleStateTrigger;
/**
* 初始化channel
*/
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// 分隔符解码器,处理半包。
// maxFrameLength 表示一行最大的长度
// Delimiters.lineDelimiter(),以/n,/r/n作为分隔符
pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
// 自定义心跳检测
// 1)readerIdleTime:为读超时时间(即多长时间没有接受到客户端发送数据)
// 2)writerIdleTime:为写超时时间(即多长时间没有向客户端发送数据)
// 3)allIdleTime:所有类型的超时时间
pipeline.addLast(new IdleStateHandler(5,0,0, TimeUnit.SECONDS));
ch.pipeline().addLast(idleStateTrigger);
pipeline.addLast(nettyServerHandler);
}
}
用于解决编码问题。客户端同理
package com.coldcast.netty;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
/**
* Title : 客户端通道初始化
* @{author} Administrator
* @{date} 2020年3月31日
* @{description}
*/
public class NettyClientInitializer extends ChannelInitializer<SocketChannel> {
/**
* 初始化channel
*/
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//自定义分隔符处理粘包问题
pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast(new NettyClientHandler());
}
}