通过上篇《Netty游戏服务器实战开发(3):自定义消息》我们都知道,客户端发送过来的消息我们服务器通过自定义编解码实现解析每条消息,并且做对应的处理,但是当批量消息到达的时候我们不能做出及时处理,需要将消息放到队列中,然后在进行处理,提高系统的性能。但是上篇介绍的重点是消息的编解码,接下来我们介绍消息的处理,并且利用线程池化技术实现消息队列处理。
首先我们来复习一下java提供的几种队列模型。
BlockingQueue接口
提供了3个添加元素方法。
add:添加元素到队列里,添加成功返回true,由于容量满了添加失败会抛出IllegalStateException异常
offer:添加元素到队列里,添加成功返回true,添加失败返回false
put:添加元素到队列里,如果容量满了会阻塞直到容量不满
3个删除方法。
poll:删除队列头部元素,如果队列为空,返回null。否则返回元素。
remove:基于对象找到对应的元素,并删除。删除成功返回true,否则返回false
take:删除队列头部元素,如果队列为空,一直阻塞到队列有元素并删除
常用的阻塞队列具体类有ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue、LinkedBlockingDeque等。
ArrayBlockingQueue
ArrayBlockingQueue的原理就是使用一个可重入锁和这个锁生成的两个条件对象进行并发控制(classic two-condition algorithm)。
ArrayBlockingQueue是一个带有长度的阻塞队列,初始化的时候必须要指定队列长度,且指定长度之后不允许进行修改。
LinkedBlockingQueue
LinkedBlockingQueue是一个使用链表完成队列操作的阻塞队列。链表是单向链表,而不是双向链表。
内部使用放锁和拿锁,这两个锁实现阻塞(“two lock queue” algorithm)。
当然关于这部分的知识还是有很多的,这儿就不详细的介绍了,我们来看利用java通过的安全队列如何使用到我们的系统中
首先我们来看消息处理部分。
我们知道,netty通过自定义编解码后消息传入到对应的handler中,对于Netty中,Channel,ChannelHandler,和ChannelPipeline之间的关系我们用一张图来描述,具体的细节问题还是要去看看Netty in Action中的对应章节。
下面就是我们实现的代码,我们抽象一个AbstractNettyNetMessageTcpServerHandler 类来处理一些功能的方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130 1/**
2 * @author EGLS0807 - [Created on 2018-07-24 21:07]
3 * @company http://www.g2us.com/
4 * @jdk java version "1.8.0_77"
5 */
6public abstract class AbstractNettyNetMessageTcpServerHandler extends ChannelInboundHandlerAdapter {
7 Logger logger = LoggerUtils.getLogger(AbstractNettyNetMessageTcpServerHandler.class);
8 private int lossContextNumber = 0;
9
10 /**
11 * channel 注册
12 *
13 * @param ctx
14 * @throws Exception
15 */
16 @Override
17 public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
18 ctx.fireChannelRegistered();
19 NettyTcpSession nettyTcpSession = (NettyTcpSession) SpringServiceManager.springLoadService.getNettyTcpSessionBuilder().buildSession(ctx.channel());
20 boolean can = SpringServiceManager.springLoadService.getNetTcpSessionLoopUpService().addNettySession(nettyTcpSession);
21 if (!can) {
22 AbstractNettyNetMessage errorMessage = SpringServiceManager.springLoadService.getNettyTcpMessageFactory().createCommonErrorResponseMessage(-1, 10500);
23 nettyTcpSession.write(errorMessage);
24 nettyTcpSession.close();
25 ctx.close();
26 return;
27 }
28 addUpdateSession(nettyTcpSession);
29 }
30
31 public abstract void addUpdateSession(NettyTcpSession nettyTcpSession);
32
33 /**
34 * have error in project
35 *
36 * @param ctx
37 * @param cause
38 * @throws Exception
39 */
40 @Override
41 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
42 logger.error(cause.getMessage(), cause);
43 if (cause instanceof java.io.IOException) {
44 logger.error(cause.getMessage());
45 //return;
46 }
47 // if(logger.isTraceEnabled()){
48 logger.error(cause.getMessage(), cause);
49 // }
50 //设置下线
51 disconnect(ctx.channel());
52 //销毁上下文
53 ctx.close();
54
55 }
56
57 /**
58 * 下线操作
59 *
60 * @param channel
61 */
62 public void disconnect(Channel channel) {
63 long sessionId = channel.attr(NettyTcpSessionBuilder.sessionId).get();
64 NettyTcpSession nettySession = (NettyTcpSession) SpringServiceManager.springLoadService.getNetTcpSessionLoopUpService().findNettySession(sessionId);
65 if (nettySession == null) {
66 logger.error("TCP NET SESSION NULL CHANNEL ID IS:" + channel.id().asLongText());
67 return;
68 }
69 nettySession.close();
70 }
71
72 /**
73 * disconnect; interrupt
74 *
75 * @param ctx
76 * @throws Exception
77 */
78 @Override
79 public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
80 ctx.flush();
81 }
82
83 /**
84 * heart loop check
85 * @param ctx
86 * @param evt
87 * @throws Exception
88 */
89 @Override
90 public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
91 long sessionId = ctx.channel().attr(NettyTcpSessionBuilder.sessionId).get();
92 if (evt instanceof IdleStateEvent) {
93 IdleStateEvent event = (IdleStateEvent) evt;
94 switch (event.state()) {
95 case ALL_IDLE:
96 logger.info("SESSION ID =" + sessionId + " IS ALL IDLE");
97 break;
98 case READER_IDLE:
99 logger.info("SESSION ID =" + sessionId + " IS READER IDLE");
100 lossContextNumber++;
101 logger.info(lossContextNumber);
102 break;
103 case WRITER_IDLE:
104 logger.info("SESSION ID =" + sessionId + " IS READER IDLE");
105 break;
106 default:
107 break;
108 }
109 } else {
110 super.userEventTriggered(ctx, evt);
111 }
112 if (lossContextNumber > GlobalConstants.NettyNet.SESSION_HEART_CHECK_NUMBER) {
113 NettyTcpSession nettyTcpSession = (NettyTcpSession) SpringServiceManager.springLoadService.getNetTcpSessionLoopUpService().findNettySession(sessionId);
114 disconnect(ctx.channel());
115 if (nettyTcpSession == null) {
116 ctx.fireChannelUnregistered();
117 return;
118 }
119 // remove session
120 SpringServiceManager.springLoadService.getNetTcpSessionLoopUpService().removeNettySession(nettyTcpSession.getSessionId());
121 ctx.fireChannelUnregistered();
122 }
123 }
124
125 @Override
126 public void channelInactive(ChannelHandlerContext ctx) throws Exception {
127 super.channelInactive(ctx);
128 }
129
130
上面的代码我们可以看到,客户端登录消息到来的时候,检查是否合法,然后将生成对应的自定义session和channel保存起来,以方便记录下一次消息到来的时候破案是否是合法消息,而AbstractNettyNetMessageTcpServerHandler是一个抽象类,主要是为了封装一些共同的方法,比如前面说的注册channel,保存session,报错下线等,心跳检测等公共操作。
对于他的实现类,NettyNetMessageTcpServerHandler,实现channelRead()方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 1/**
2 * @author EGLS0807 - [Created on 2018-07-26 21:11]
3 * @company http://www.g2us.com/
4 * @jdk java version "1.8.0_77"
5 */
6public class NettyNetMessageTcpServerHandler extends AbstractNettyNetMessageTcpServerHandler {
7
8 @Override
9 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
10 AbstractNettyNetMessage message = (AbstractNettyNetMessage) msg;
11 INettyServerPipeLine nettyTcpServerPipeLine = SpringServiceManager.springLoadService.getNettyTcpServerPipeLine();
12 nettyTcpServerPipeLine.dispatch(ctx.channel(), message);
13 }
14
15 @Override
16 public void addUpdateSession(NettyTcpSession nettyTcpSession) {
17 }
18}
19
20
21
上面代码中首先获得一条消息管道,在将消息注入到管道中
1
2
3 1 nettyTcpServerPipeLine.dispatch(ctx.channel(), message);
2
3
由INettyServerpilpline的实现类来实现管道中处理消息,
tcp管道实现类如下NettyTcpServerPipeLineImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 1/**
2 * @author EGLS0807 - [Created on 2018-08-02 20:41]
3 * @company http://www.g2us.com/
4 * @jdk java version "1.8.0_77"
5 */
6@Service
7public class NettyTcpServerPipeLineImpl implements INettyServerPipeLine {
8
9private Logger logger=LoggerUtils.getLogger(NettyTcpServerPipeLineImpl.class);
10 @Override
11 public void dispatch(Channel channel, AbstractNettyNetMessage message) {
12 int commId = message.getNettyNetMessageHead().getCmd();
13 MessageRegistryFactory messageRegistryFactory = SpringServiceManager.springLoadService.getMessageRegistryFactory();
14 MessageComm messageComm = messageRegistryFactory.getMessageComm(commId);
15 if (message instanceof AbstractNettyNetProtoBufTcpMessage) {
16 AbstractNettyNetProtoBufTcpMessage protoBufTcpMessage = (AbstractNettyNetProtoBufTcpMessage) message;
17 INettyChannleOperationService netTcpSessionLoopUpService = SpringServiceManager.springLoadService.getNetTcpSessionLoopUpService();
18 long sessionId = channel.attr(NettyTcpSessionBuilder.sessionId).get();
19 NettyTcpSession nettySession = (NettyTcpSession) netTcpSessionLoopUpService.findNettySession(sessionId);
20 if(nettySession==null){
21 logger.error("NETTY SESSION IS NULL");
22 }
23 message.setAttribute(MessageAttributeEnum.DISPATCH_SESSION,nettySession);
24 nettySession.addNettyNetMessage(message);
25 }
26
27 }
28
29
首先是根据消息的id找到具体消息的注解,通过工厂类获取消息对象,然后通过执行器找到对应的session对象,最后通过session把消息添加到session中,而session中的addNettyNetMessage方法则是将消息添加到消息队列中去。由此,我们就可以从netty的handler模块将消息放到消息队列模块了。下面是消息session的具体逻辑。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76 1/**
2 * @author twjitm - [Created on 2018-07-24 15:59]
3 * @jdk java version "1.8.0_77"
4 * tcp session
5 */
6public class NettyTcpSession extends NettySession implements IUpdatable {
7 /**
8 * session id
9 */
10 private long sessionId;
11 /**
12 * 消息处理器
13 */
14 private NettyTcpNetProtoMessageProcess netProtoMessageProcess;
15 /**
16 * 消息发送器
17 */
18 private NettyNetTcpMessageSender netTcpMessageSender;
19
20 boolean netMessageProcessSwitch = true;
21
22
23 public NettyTcpSession(Channel channel) {
24 super(channel);
25 sessionId = SpringServiceManager.springLoadService.getLongIdGenerator().generateId();
26 netProtoMessageProcess = new NettyTcpNetProtoMessageProcess(this);
27 netTcpMessageSender = new NettyNetTcpMessageSender(this);
28
29
30 }
31
32 /**
33 * @param switchFlag
34 */
35 public void processNetMessage(boolean switchFlag) {
36 if (netMessageProcessSwitch || switchFlag) {
37 netProtoMessageProcess.update();
38 }
39 }
40
41 /**
42 * @param abstractNetMessage
43 */
44 public void addNettyNetMessage(AbstractNettyNetMessage abstractNetMessage) {
45 this.netProtoMessageProcess.addNetMessage(abstractNetMessage);
46 this.processNetMessage(true);
47 }
48
49 public void close() {
50 this.netProtoMessageProcess.close();
51 this.netTcpMessageSender.close();
52 }
53
54 @Override
55 public boolean update() {
56 processNetMessage(false);
57 return false;
58 }
59
60 public long getSessionId() {
61 return sessionId;
62 }
63
64 public NettyTcpNetProtoMessageProcess getNetProtoMessageProcess() {
65 return netProtoMessageProcess;
66 }
67
68 public NettyNetTcpMessageSender getNetTcpMessageSender() {
69 return netTcpMessageSender;
70 }
71
72 public boolean isNetMessageProcessSwitch() {
73 return netMessageProcessSwitch;
74 }
75
76
最后将主要流程图用下图表示