版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/cn_yaojin/article/details/84621942
一、服务端
package yaojin;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import yaojin.coder.CustomerDecoder;
import yaojin.coder.CustomerEncoder;
import yaojin.msgtype.CustomMsg;
import java.net.SocketAddress;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class TestServer {
public static void main(String[] args) {
int port = 2020;
ServerBootstrap bootstrap = new ServerBootstrap();
EventLoopGroup boss = new NioEventLoopGroup(1, new ThreadFactory() {
//计数器
private AtomicInteger threadIndex = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
return new Thread(r,String.format("Boss_%d", this.threadIndex.incrementAndGet()));
}
});
//工作线程池
EventLoopGroup worker = new NioEventLoopGroup(4, new ThreadFactory() {
//计数器
private AtomicInteger threadIndex = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
return new Thread(r,String.format("NettyServerEPOLLSelector_%d_%d", 4, this.threadIndex.incrementAndGet()));
}
});
final DefaultEventExecutorGroup defaultEventExecutorGroup = new DefaultEventExecutorGroup(8, new ThreadFactory() {
//计数器
private AtomicInteger threadIndex = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "ServerCodecThread_" + this.threadIndex.incrementAndGet());
}
});
bootstrap.group(boss, worker);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.option(ChannelOption.SO_BACKLOG, 1024)
.option(ChannelOption.SO_REUSEADDR, true)
.option(ChannelOption.SO_KEEPALIVE, true)
.childOption(ChannelOption.TCP_NODELAY, true)
.childOption(ChannelOption.SO_SNDBUF, 1024*1024)// 发送缓冲大小
.childOption(ChannelOption.SO_RCVBUF, 1024*1024);// 接收缓冲大小
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//如果消息是以特殊符号结束的,比如 $,那么用以下代码
// ch.pipeline().addLast(new StringEncoder());
// // 设置特殊分隔符
// ByteBuf buf = Unpooled.copiedBuffer("$".getBytes());
// ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, buf));
// // 设置字符串形式的解码
// ch.pipeline().addLast(new StringDecoder());
// ch.pipeline().addLast(new StringHandler());
//如果是自定义消息,那么用以下代码
ch.pipeline().addLast(defaultEventExecutorGroup,
new CustomerEncoder(),//编码器
new CustomerDecoder(),//解码器
new IdleStateHandler(0,0,6, TimeUnit.SECONDS),//心跳检测,不管读写,只要超过6秒钟,就主动释放客户端占用的资源
new NettyConnectManageHandler(),//心跳处理
new CustomerHandler());//业务处理
}
});
try {
//绑定端口,开始同步
ChannelFuture channel = bootstrap.bind(port).sync();
System.out.println("监听端口:" + 2020);
//等待服务端监听端口的关闭,
channel.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 心跳超时处理
*/
class NettyConnectManageHandler extends ChannelDuplexHandler {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if(evt instanceof IdleStateEvent){
IdleState state = ((IdleStateEvent) evt).state();
if (state.equals(IdleState.ALL_IDLE)) {
System.out.println("客户端连接通道超时了,通道已经被关闭了");
//关闭通道
// ctx.channel().close();
ctx.channel().close().addListener(new ChannelFutureListener(){
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
System.out.println("通道已经成功被关闭了");
}
});
}
}else {
super.userEventTriggered(ctx,evt);
}
}
}
/**
* 自定义消息处理
*/
class CustomerHandler extends SimpleChannelInboundHandler<Object>{
@Override
protected void channelRead0(ChannelHandlerContext ct, Object msg) throws Exception {
if(msg instanceof CustomMsg){
CustomMsg customMsg = (CustomMsg) msg;
//根据type可以区分,如果是心跳包,那么不用理会
System.out.println("服务器接收到客户端的信息:"+customMsg.getBody()+"___"+customMsg.getFlag());
}
SocketAddress remote = ct.channel().remoteAddress();
String addr = remote != null ? remote.toString() : "";
System.out.println("服务端地址:"+addr);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
ctx.close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
/**
* 特殊分隔符 消息处理器
*/
class StringHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String request = (String) msg;
System.out.println("Server :" + msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
二、客户端
package yaojin;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import yaojin.coder.CustomerDecoder;
import yaojin.coder.CustomerEncoder;
import yaojin.msgtype.CustomMsg;
import java.util.concurrent.TimeUnit;
public class TestClient {
private static Channel channel;
public static void main(String[] args) {
start();
send();
}
/**
* 测试发送消息
*/
public static void send() {
// try {
// String msg = "---->hi,my name is cn_yaojin ,that is so happy to meet you ,and you could give me happiness. $_";
// for (int i = 0; 1 < 2; i++) {
// System.out.println(i);
// channel.writeAndFlush(i+msg);
// Thread.sleep(1000);
// }
//
//
//
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
CustomMsg customMsg = new CustomMsg();
customMsg.setType((byte)0xAB);//这里可以区分是业务消息,开始心跳包
customMsg.setFlag((byte)0xCD);
customMsg.setLength("Hello,cn_yaojin".length());
customMsg.setBody("Hello,cn_yaojin");
channel.writeAndFlush(customMsg);
}
public static void start() {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup);
bootstrap.channel(NioSocketChannel.class);
bootstrap.option(ChannelOption.TCP_NODELAY, true);
bootstrap.handler(new MyChannelHandler());
try {
// 异步链接服务器 同步等待链接成功
ChannelFuture f = bootstrap.connect("127.0.0.1", 2020).sync();
channel = f.channel();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class MyChannelHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//如果消息是以特殊符号 $ 结尾的,用以下处理器
// ch.pipeline().addLast(new StringEncoder());
// // 设置特殊分隔符
// ByteBuf buf = Unpooled.copiedBuffer("$".getBytes());
// ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, buf));
// ch.pipeline().addLast(new StringDecoder());
// // 客户端的处理器
// ch.pipeline().addLast(new HelloClientHandler());
//如果消息是自定义类型,那么用以下处理器
ch.pipeline().addLast(new CustomerEncoder());//编码器
ch.pipeline().addLast(new CustomerDecoder());//解码器
ch.pipeline().addLast(new IdleStateHandler(0,0,4, TimeUnit.SECONDS));//心跳设置
ch.pipeline().addLast(new ClientConnectManageHandler());//心跳处理器
ch.pipeline().addLast(new ClientHandler());//业务处理器
}
}
/**
* 心跳超时处理
*/
class ClientConnectManageHandler extends ChannelDuplexHandler {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if(evt instanceof IdleStateEvent){
IdleState state = ((IdleStateEvent) evt).state();
if (state.equals(IdleState.ALL_IDLE)) {
CustomMsg customMsg = new CustomMsg();
customMsg.setType((byte)0xAB);//这里需要区分是 心跳包类型
customMsg.setFlag((byte)0xCD);
customMsg.setLength("心跳包".length());
customMsg.setBody("心跳包");
System.out.println("发送-->心跳包");
ctx.writeAndFlush(customMsg);
}
}else {
super.userEventTriggered(ctx,evt);
}
}
}
/**
* 自定义消息处理器
*/
class ClientHandler extends ChannelInboundHandlerAdapter{
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("开始连接了");
//这里也可以发送消息
}
}
/**
* 分隔符消息处理器
*/
class HelloClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("Server say : " + msg);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("Client close ");
super.channelInactive(ctx);
}
}
三、编码器
package yaojin.coder;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import yaojin.msgtype.CustomMsg;
/**
* 消息编码器
*/
public class CustomerEncoder extends MessageToByteEncoder<CustomMsg> {
@Override
protected void encode(ChannelHandlerContext ct, CustomMsg msg, ByteBuf out) throws Exception {
if(null == msg){
throw new RuntimeException("内容不能为空");
}
String body = msg.getBody();
byte[] data=body.getBytes("utf-8");
out.writeByte(msg.getType());
out.writeByte(msg.getFlag());
out.writeInt(data.length);
out.writeBytes(data);
}
}
四、解码器
package yaojin.coder;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import yaojin.msgtype.CustomMsg;
/**
* 消息解码器
*/
public class CustomerDecoder extends LengthFieldBasedFrameDecoder {
//判断传送客户端传送过来的数据是否按照协议传输,头部信息的大小应该是 byte+byte+int = 1+1+4 = 6
private static final int HEADER_SIZE = 6;
private static int maxFrameLength = 1024 * 4;//数据大小, 4K
private static int lengthFieldLength = 4;//数据长度
private static int lengthFieldOffset = 2;//length的起始位置
private static int lengthAdjustment = 0;
private static int initialBytesToStrip = 0;//
public CustomerDecoder() {
super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip);
}
/**
* @param maxFrameLength 解码时,处理每个帧数据的最大长度
* @param lengthFieldOffset 该帧数据中,存放该帧数据的长度的数据的起始位置
* @param lengthFieldLength 记录该帧数据长度的字段本身的长度
* @param lengthAdjustment 修改帧数据长度字段中定义的值,可以为负数
* @param initialBytesToStrip 解析的时候需要跳过的字节数
* @param failFast 为true,当frame长度超过maxFrameLength时立即报TooLongFrameException异常,为false,读取完整个帧再报异常
*/
public CustomerDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip, boolean failFast) {
super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip, failFast);
}
@Override
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
System.out.println("服务器开始解码了");
if(in==null){
return null;
}
if(in.readableBytes()<HEADER_SIZE){
//说明数据不全
throw new RuntimeException("数据长度异常");
}
byte type = in.readByte();
byte flag = in.readByte();
int length = in.readInt();
//数据长度是否充足
if(in.readableBytes()<length){
throw new RuntimeException("数据长度不够");
}
//获取数据
ByteBuf buf = in.readBytes(length);
//数据内容
byte[] data = new byte[buf.readableBytes()];
buf.readBytes(data);
System.out.println("接收到的数据:"+new String(data,"utf-8"));
CustomMsg msg = new CustomMsg();
msg.setType(type);
msg.setFlag(flag);
msg.setLength(length);
msg.setBody(new String(data,"utf-8"));
return msg;
}
}
五、自定义消息实体
package yaojin.msgtype;
/**
* 测试消息实体
*/
public class CustomMsg {
public static void main(String[] args){
Integer a = 171;
String hex = "0X"+Integer.toHexString(a);
System.out.println(hex);
}
//类型 系统编号 0xAB 表示A系统,0xBC 表示B系统
private byte type;
//信息标志 0xAB 表示心跳包 0xBC 表示超时包 0xCD 业务信息包
private byte flag;
//主题信息的长度
private int length;
//主题信息
private String body;
public CustomMsg() {
}
public CustomMsg(byte type, byte flag, int length, String body) {
this.type = type;
this.flag = flag;
this.length = length;
this.body = body;
}
public byte getType() {
return type;
}
public void setType(byte type) {
this.type = type;
}
public byte getFlag() {
return flag;
}
public void setFlag(byte flag) {
this.flag = flag;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
}