1. 程式人生 > >Java遊戲伺服器開發之十五--使用StringMessage封裝訊息

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