Java遊戲伺服器開發之十五--使用StringMessage封裝訊息
之前我們的訊息都是直接通過使用String,然後通過netty的預設編解碼器StringDecoder、StringEncoder來進行傳輸,而我們需要的不僅僅是文字內容,還需要擴充套件一些其他的東西(訊息號、狀態碼之類的)。
訊息傳輸可以看看這篇遊戲中tcp訊息使用byte位元組陣列傳輸
所以就想到用一個message進行封裝,同時自定義自己的編解碼器。
備註:這篇內容修改的內容比較多,或許有些凌亂,同時在後面這篇修改的內容或許會更改,大家可以檢視版本記錄進行詳細對比。
新增
IMessage.java 訊息的公共介面,這裡暫時沒用到
MessageDecoder.java 訊息編碼器,傳送訊息時使用
MessageEncoder.java 訊息編碼器,接收訊息時
StringMessage.java String型別的訊息
修改
INetworkConsumer.java 修改consume(String message, Channel channel)為consume(StringMessage message, Channel channel)
NetworkConsumer.java 修改messageId從message中獲取
SessionManager.java 新增一個sendMessage(Session session, StringMessage stringMessage) 方法
TcpMessageStringHandler.java 將其中的泛型類改成StringMessage
TcpServerStringInitializer.java
TcpStringClientHandlerTest.java 將其中的泛型類改成StringMessage
TcpStringClientTest.java
TestFirstHandler.java 修改AbstractHandler的泛型類為StringMessage
UserService.java 修改接收類為StringMessage
UserServiceImpl.java 修改接收類為StringMessage
1.StringMessage
首先我們的訊息是StringMessage,然後裡面的內容可以使用json、xml,這邊是使用json格式
看下里面的欄位 int messageId; int statusCode; int length; String body;
裡面就是訊息一些比較重要的內容,
messageId是根據不同的值,進行路由區分的
statusC內容的長ode主要是返回內容是告訴客戶端訊息的狀態,成功為1,其他不同的錯誤使用不同的錯誤碼
length是度,內容的長度是不可控制的,所以使用一個長度進行定義
body是具體的內容
2.MessageEncoder
訊息編碼器,我們傳送資訊的時候是直接使用StringMessage,但是netty不知道我們傳送的資料內容,所以要通過這個類進行轉換,轉化成系統知道的型別ByteBuf
讓這個類繼承MessageToByteEncoder,重寫encode方法,依次寫入 messageId; statusCode; length; body;
3.MessageDecoder
訊息解碼器,同上,在訊息接受的時候,通過這個類的轉化,我們可以得到StringMessage
讓這個類繼承LengthFieldBasedFrameDecoder,重寫decode方法,然後讀出messageId; statusCode; length; body;即可
具體的程式碼就是:
StringMessage
/* * Copyright (C), 2015-2018 * FileName: StringMessage * Author: zhao * Date: 2018/7/12 10:55 * Description: 傳送過來的請求 * History: * <author> <time> <version> <desc> * 作者姓名 修改時間 版本號 描述 */ package com.lizhaoblog.base.message; import com.lizhaoblog.base.util.GsonUtil; /** * 〈一句話功能簡述〉<br> * 〈String型別的請求〉 * * @author zhao * @date 2018/7/12 10:55 * @since 1.0.1 */ public class StringMessage { /** * 訊息號 */ private int messageId; /** * 狀態碼 */ private int statusCode; /** * 內容長度 */ private int length; /** * 具體內容 */ private String body; public StringMessage() { } public static StringMessage create(int messageId) { StringMessage stringMessage = new StringMessage(); stringMessage.setMessageId(messageId); return stringMessage; } public static StringMessage create(String origin) { StringMessage stringMessage = GsonUtil.fromJson(origin, StringMessage.class); return stringMessage; } public static StringMessage create(int length, int messageId, int statusCode, String content) { return new StringMessage(length, messageId, statusCode, content); } private StringMessage(int length, int messageId, int statusCode, String body) { this.length = length; this.messageId = messageId; this.statusCode = statusCode; this.body = body; } public int getLength() { return length; } public void setLength(int length) { this.length = length; } public int getMessageId() { return messageId; } public void setMessageId(int messageId) { this.messageId = messageId; } public int getStatusCode() { return statusCode; } public void setStatusCode(int statusCode) { this.statusCode = statusCode; } public String getBody() { return body; } public void setBody(String body) { this.body = body; } @Override public String toString() { return "StringMessage{" + "messageId=" + messageId + ", statusCode=" + statusCode + ", length=" + length + ", body='" + body + '\'' + '}'; } }
MessageEncoder
/*
* Copyright (C), 2015-2018
* FileName: MessageEncoder
* Author: zhao
* Date: 2018/7/16 14:58
* Description: 訊息編碼器
* History:
* <author> <time> <version> <desc>
* 作者姓名 修改時間 版本號 描述
*/
package com.lizhaoblog.base.message.codec;
import com.lizhaoblog.base.constant.ConstantValue;
import com.lizhaoblog.base.message.StringMessage;
import java.nio.charset.Charset;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
/**
* 〈一句話功能簡述〉<br>
* 〈訊息編碼器〉
*
* @author zhao
* @date 2018/7/16 14:58
* @since 1.0.1
*/
public class MessageEncoder extends MessageToByteEncoder<StringMessage> {
@Override
protected void encode(ChannelHandlerContext ctx, StringMessage msg, ByteBuf out) throws Exception {
if (null == msg) {
throw new Exception("msg is null");
}
String body = msg.getBody();
byte[] bodyBytes = body.getBytes(Charset.forName(ConstantValue.PROJECT_CHARSET));
out.writeInt(msg.getMessageId());
out.writeInt(msg.getStatusCode());
out.writeInt(bodyBytes.length);
out.writeBytes(bodyBytes);
}
}
MessageDecoder
/*
* Copyright (C), 2015-2018
* FileName: MessageDecoder
* Author: zhao
* Date: 2018/7/16 15:05
* Description: 訊息解碼器
* History:
* <author> <time> <version> <desc>
* 作者姓名 修改時間 版本號 描述
*/
package com.lizhaoblog.base.message.codec;
import com.lizhaoblog.base.constant.ConstantValue;
import com.lizhaoblog.base.message.StringMessage;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
/**
* 〈一句話功能簡述〉<br>
* 〈訊息解碼器〉
*
* @author zhao
* @date 2018/7/16 15:05
* @since 1.0.1
*/
public class MessageDecoder extends LengthFieldBasedFrameDecoder {
//判斷傳送客戶端傳送過來的資料是否按照協議傳輸,頭部資訊的大小應該是 int+int+int = 4+4+4 = 12
private static final int HEADER_SIZE = 12;
private int messageId;
private int statusCode;
private int length;
private String body;
/**
* @param maxFrameLength 解碼時,處理每個幀資料的最大長度
* @param lengthFieldOffset 該幀資料中,存放該幀資料的長度的資料的起始位置
* @param lengthFieldLength 記錄該幀資料長度的欄位本身的長度
* @param lengthAdjustment 修改幀資料長度欄位中定義的值,可以為負數
* @param initialBytesToStrip 解析的時候需要跳過的位元組數
* @param failFast 為true,當frame長度超過maxFrameLength時立即報TooLongFrameException異常,為false,讀取完整個幀再報異常
*/
public MessageDecoder(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 {
if (in == null) {
return null;
}
if (in.readableBytes() < HEADER_SIZE) {
throw new Exception("可讀資訊段比頭部資訊都小");
}
//注意在讀的過程中,readIndex的指標也在移動
messageId = in.readInt();
statusCode = in.readInt();
length = in.readInt();
if (in.readableBytes() < length) {
throw new Exception("body獲取長度" + length + ",實際長度沒有達到");
}
ByteBuf buf = in.readBytes(length);
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
body = new String(req, ConstantValue.PROJECT_CHARSET);
// CustomMsg customMsg = new CustomMsg(type, flag, length, body);
StringMessage stringMessage = StringMessage.create(length, messageId, statusCode, body);
return stringMessage;
}
}
上面的程式碼在碼雲上 https://gitee.com/lizhaoandroid/JgServer
可以加qq群一起探討Java遊戲伺服器開發的相關知識 676231564