netty-调整调用链路,TCP粘包方案替换
This commit is contained in:
@@ -24,10 +24,6 @@ public class Constant {
|
||||
*/
|
||||
public static AttributeKey<ChannelParam> CHANNEL_PARAM = AttributeKey.newInstance("CHANNEL_PARAM");
|
||||
|
||||
/**
|
||||
* 每局结束后的保护时间
|
||||
*/
|
||||
public static final Integer GAME_OVER_PROTECTION_SECONDS = 5;
|
||||
|
||||
public final static String DELIMITER_WORD = "$_$";
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.sv.netty.utils.JsonMapper;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.timeout.IdleState;
|
||||
import io.netty.handler.timeout.IdleStateEvent;
|
||||
import org.slf4j.Logger;
|
||||
@@ -14,15 +15,10 @@ import org.slf4j.LoggerFactory;
|
||||
*
|
||||
* @author ranfi
|
||||
*/
|
||||
public class ClientHandler extends ChannelInboundHandlerAdapter {
|
||||
public class ClientHandler extends SimpleChannelInboundHandler<String> {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(ClientHandler.class);
|
||||
|
||||
private static final String MESSAGE = "Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients.";
|
||||
|
||||
public ClientHandler() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当通道就绪就会触发
|
||||
* @param ctx
|
||||
@@ -50,25 +46,30 @@ public class ClientHandler extends ChannelInboundHandlerAdapter {
|
||||
* @throws Exception
|
||||
*/
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
public void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
|
||||
super.channelRead(ctx, msg);
|
||||
logger.info("接收服务器响应msg:[" + msg + "]");
|
||||
// 安卓写,非netty 后台实现
|
||||
// TODO 安卓获取心跳内容(有二维码的唯一识别)显示请求小程序的venueId的二维码,无需拼接url
|
||||
// TODO 安卓获取通知加载页面
|
||||
// TODO 安卓获取通知开门失败消息 (进入一个页面,然后显示倒计时,回到主页(二维码页面))
|
||||
// TODO 安卓获取通知开门的消息 (无需校验,直接操作开门)
|
||||
// Message message = JsonMapper.fromJson(msg, Message.class);
|
||||
// MessageService.getInstance().execute(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt)
|
||||
throws Exception {
|
||||
super.userEventTriggered(ctx, evt);
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
|
||||
if (IdleStateEvent.class.isAssignableFrom(evt.getClass())) {
|
||||
IdleStateEvent event = (IdleStateEvent) evt;
|
||||
// 如果IoSession闲置,则关闭连接
|
||||
if (event.state() == IdleState.READER_IDLE) {
|
||||
String json = JsonMapper.nonDefaultMapper().toJson(null);
|
||||
ctx.writeAndFlush(json);
|
||||
if (event.state() == IdleState.ALL_IDLE) {
|
||||
ctx.writeAndFlush("getHbMessage()");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
|
||||
public void channelReadComplete(ChannelHandlerContext ctx) {
|
||||
ctx.flush();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
package com.sv.netty.netty;
|
||||
|
||||
import com.sv.netty.utils.EncodeUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* TCP消息解码器
|
||||
*
|
||||
* @Author peakren
|
||||
* @Date 08/05/2017 10:34 PM
|
||||
*/
|
||||
public class MessageDecoder extends ByteToMessageDecoder {
|
||||
|
||||
private final static int MESSAGE_LENGTH = 4;
|
||||
private final static int MESSAGE_SEQNO = 4;
|
||||
private final static int MESSAGE_HEAD = 8;
|
||||
private final static int MESSAGE_MAX_LENGTH = 12;
|
||||
private final static int MAGIC_WORD = 0x9DDD; //码头
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(MessageDecoder.class);
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
if (in.capacity() >= MESSAGE_MAX_LENGTH) {
|
||||
int magicWord = in.readInt();
|
||||
if (magicWord == MAGIC_WORD) {
|
||||
int length = in.readInt();
|
||||
byte[] msg = new byte[length];
|
||||
in.readBytes(msg);
|
||||
String message = new String(msg, "utf-8");
|
||||
out.add(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.sv.netty.netty;
|
||||
|
||||
import com.sv.netty.config.Constant;
|
||||
import com.sv.netty.netty.message.BaseDto;
|
||||
import com.sv.netty.utils.JsonUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
@@ -17,24 +18,18 @@ import java.nio.charset.Charset;
|
||||
* @Author peakren
|
||||
* @Date 07/05/2017 10:43 PM
|
||||
*/
|
||||
public class MessageEncoder extends MessageToByteEncoder<BaseDto> {
|
||||
|
||||
/**
|
||||
* TODO 客户端没用就删了
|
||||
*/
|
||||
// private final static String mSeqno = "doll";
|
||||
private final static String DELIMITER_WORD = "$_$";
|
||||
public class MessageEncoder extends MessageToByteEncoder<String> {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(MessageEncoder.class);
|
||||
|
||||
Charset charset = Charset.forName("UTF-8");
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, BaseDto msg, ByteBuf out) throws Exception {
|
||||
String message = JsonUtils.encode(msg);
|
||||
logger.info("send message content:" + message);
|
||||
message = message + DELIMITER_WORD;
|
||||
byte[] content = message.getBytes(charset.name());
|
||||
protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) throws Exception {
|
||||
// String message = JsonUtils.encode(msg);
|
||||
logger.info("send message content:" + msg);
|
||||
msg = msg + Constant.DELIMITER_WORD;
|
||||
byte[] content = msg.getBytes(charset.name());
|
||||
out.writeBytes(content); //发送消息内容
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,9 @@ package com.sv.netty.netty;
|
||||
|
||||
import com.sv.netty.config.Constant;
|
||||
import com.sv.netty.config.SpringContextHolder;
|
||||
import com.sv.netty.netty.message.BaseDto;
|
||||
import com.sv.netty.netty.message.ChannelParam;
|
||||
import com.sv.netty.service.MessageService;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.handler.timeout.IdleState;
|
||||
import io.netty.handler.timeout.IdleStateEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -20,7 +17,7 @@ import java.net.InetSocketAddress;
|
||||
*/
|
||||
|
||||
@ChannelHandler.Sharable
|
||||
public class ServerHandler extends SimpleChannelInboundHandler<BaseDto> {
|
||||
public class ServerHandler extends SimpleChannelInboundHandler<String> {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(ServerHandler.class);
|
||||
|
||||
@@ -41,11 +38,11 @@ public class ServerHandler extends SimpleChannelInboundHandler<BaseDto> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, BaseDto msg) throws Exception {
|
||||
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
|
||||
super.channelRead(ctx, msg);
|
||||
String clientIp = ctx.channel().attr(Constant.CHANNEL_PARAM).get().getClientIp();
|
||||
try {
|
||||
messageService.receive(ctx.channel(), msg.toString());
|
||||
messageService.receive(ctx.channel(), msg);
|
||||
} catch (Exception e) {
|
||||
logger.error("[" + clientIp + "] host unknown error");
|
||||
}
|
||||
@@ -101,24 +98,20 @@ public class ServerHandler extends SimpleChannelInboundHandler<BaseDto> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link ChannelHandlerContext#fireUserEventTriggered(Object)} to forward
|
||||
* to the next {@link ChannelHandler} in the {@link ChannelPipeline}.
|
||||
* <p/>
|
||||
* Sub-classes may override this method to change behavior.
|
||||
*
|
||||
* @param ctx
|
||||
* @param evt
|
||||
* IdleStateHandler 如果几秒之后没有读操作,那么就会触发这个方法
|
||||
*/
|
||||
@Override
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||
IdleStateEvent event = (IdleStateEvent) evt;
|
||||
// 如果Channel读取数据闲置,则关闭连接
|
||||
if (event.state() == IdleState.READER_IDLE) {
|
||||
String clientIP = ((InetSocketAddress) ctx.channel().remoteAddress()).getAddress().getHostAddress();
|
||||
logger.info("Client [" + clientIP + "] has idle");
|
||||
messageService.destory(ctx.channel());
|
||||
ctx.channel().close();
|
||||
}
|
||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
|
||||
// Do nothing 客户端这个方法用来发心跳,
|
||||
// 可以考虑再ctx 获取客户端的上下文信息,将该客户移除我们的操作 TODO 值得尝试主要是要区分客户端
|
||||
// IdleStateEvent event = (IdleStateEvent) evt;
|
||||
// // 如果Channel读取数据闲置,则关闭连接
|
||||
// if (event.state() == IdleState.READER_IDLE) {
|
||||
// String clientIP = ((InetSocketAddress) ctx.channel().remoteAddress()).getAddress().getHostAddress();
|
||||
// logger.info("Client [" + clientIP + "] has idle");
|
||||
// messageService.destory(ctx.channel());
|
||||
// ctx.channel().close();
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package com.sv.netty.netty;
|
||||
|
||||
import com.sv.netty.config.Constant;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
|
||||
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
|
||||
import io.netty.handler.codec.string.StringDecoder;
|
||||
import io.netty.handler.timeout.IdleStateHandler;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
@@ -19,20 +20,20 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
public class ServerProtocolInitializer extends ChannelInitializer<SocketChannel> {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(ServerProtocolInitializer.class);
|
||||
private final int IDLE_TIME = 30; //连接检测空闲时间
|
||||
private final int READ_TIME = 30; //读超时时间
|
||||
private final int WRITE_TIME = 30; //写超时时间
|
||||
private final int READ_TIMEOUT = 600; //读超时时间
|
||||
private final int READ_TIMEOUT = 60; //读超时时间
|
||||
|
||||
@Override
|
||||
protected void initChannel(SocketChannel ch){
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
logger.info("ServerProtocolInitializer");
|
||||
// 通过指定的长度来标识整包的信息,这样就可以自动的处理粘包和半包的问题
|
||||
pipeline.addFirst(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 4, 4, 0, 0));
|
||||
// 超时设置
|
||||
pipeline.addLast(new ReadTimeoutHandler(READ_TIMEOUT));
|
||||
pipeline.addLast(new MessageDecoder());
|
||||
// 通过指定的长度来标识整包的信息,这样就可以自动的处理粘包和半包的问题
|
||||
pipeline.addLast(new DelimiterBasedFrameDecoder(2048,
|
||||
Unpooled.wrappedBuffer(Constant.DELIMITER_WORD.getBytes())));
|
||||
pipeline.addLast(new StringDecoder());
|
||||
pipeline.addLast(new MessageEncoder());
|
||||
// 心跳检测机制,通过调用触发下一个handler userEventTriggered 方法
|
||||
pipeline.addLast(new IdleStateHandler(READ_TIME, WRITE_TIME,IDLE_TIME));
|
||||
|
||||
Reference in New Issue
Block a user