You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

替换readUTF/writeUTF后MMORPG服务器频繁断连,求长字符串传输方案

问题:MMORPG长字符串传输引发连接异常及优化方案咨询

我的MMORPG游戏服务器某功能发送的「字符串更新」长度已超过readUTF()和writeUTF()的65535限制,于是替换为以下实现:

客户端读取代码

//incommingReply = GameActivity.commandIn.readUTF();  //65535 length limit
int length = GameActivity.commandIn.readInt();
byte[] data = new byte[length];
GameActivity.commandIn.readFully(data);
incommingReply = new String(data,"UTF-8");

服务器写入代码

//commandOut.writeUTF(request);  //65535 length limit
byte[] b = request.getBytes("UTF-8");
commandOut.writeInt(b.length);
commandOut.write(b);
commandOut.flush();

更换实现后,玩家频繁因java.net.SocketException: Software caused connection abort断开连接,同时出现如下栈追踪的EOFException:

E  MCR Exception
E  Exception: java.io.EOFException
E  stack trace: 
   java.io.DataInputStream.readFully(DataInputStream.java:205)
   java.io.DataInputStream.readInt(DataInputStream.java:394)
   com.kisnardonline.kisnardonline.MyCommandReceiver.run(MyCommandReceiver.java:251)
   java.lang.Thread.run(Thread.java:1012)

尝试改用readObject()也未解决问题,求更优的长字符串传输方案?


解决方案及优化建议

先排查现有实现的问题

出现EOFException和连接中断,核心原因是客户端读取数据时未获取到预期的字节数,可能的诱因包括:

  • 流中存在残留数据:之前的命令未完全读取,导致本次readInt()读到的不是正确的长度值
  • 数据传输丢包:网络波动导致部分字节未送达,readFully()无法凑齐指定长度
  • 长度与实际字节不匹配:比如getBytes("UTF-8")在特殊环境下的编码差异(概率极低,但需排查)

方案一:增强现有长度前缀实现,添加数据校验

在现有逻辑基础上加入CRC校验,确保数据完整性,同时避免脏数据干扰:

服务器端代码

import java.util.zip.CRC32;
import java.nio.charset.StandardCharsets;

// ...

byte[] b = request.getBytes(StandardCharsets.UTF_8);
int length = b.length;
CRC32 crc = new CRC32();
crc.update(b);
long checksum = crc.getValue();

commandOut.writeInt(length);
commandOut.write(b);
commandOut.writeLong(checksum);
commandOut.flush();

客户端代码

import java.util.zip.CRC32;
import java.nio.charset.StandardCharsets;

// ...

int length = GameActivity.commandIn.readInt();
byte[] data = new byte[length];
GameActivity.commandIn.readFully(data);
long receivedChecksum = GameActivity.commandIn.readLong();

CRC32 crc = new CRC32();
crc.update(data);
long calculatedChecksum = crc.getValue();

if (calculatedChecksum != receivedChecksum) {
    // 数据校验失败,可触发重传或断开重连
    throw new IOException("Data integrity check failed");
}
incommingReply = new String(data, StandardCharsets.UTF_8);

方案二:分块传输超大型字符串

对于几MB级别的超大字符串,分块传输可降低单次传输的网络压力,避免因大数据包导致的丢包或连接中断:

服务器端代码

import java.nio.charset.StandardCharsets;

// ...

byte[] b = request.getBytes(StandardCharsets.UTF_8);
int totalLength = b.length;
commandOut.writeInt(totalLength);

int offset = 0;
final int BLOCK_SIZE = 4096; // 4KB块大小,可根据网络环境调整
while (offset < totalLength) {
    int chunkLength = Math.min(BLOCK_SIZE, totalLength - offset);
    commandOut.writeInt(chunkLength);
    commandOut.write(b, offset, chunkLength);
    offset += chunkLength;
}
commandOut.writeInt(0); // 发送0长度作为结束标记
commandOut.flush();

客户端代码

import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;

// ...

int totalLength = GameActivity.commandIn.readInt();
ByteArrayOutputStream baos = new ByteArrayOutputStream(totalLength);

while (true) {
    int chunkLength = GameActivity.commandIn.readInt();
    if (chunkLength == 0) {
        break;
    }
    byte[] chunk = new byte[chunkLength];
    GameActivity.commandIn.readFully(chunk);
    baos.write(chunk);
}

byte[] data = baos.toByteArray();
if (data.length != totalLength) {
    throw new IOException("Incomplete data received");
}
incommingReply = new String(data, StandardCharsets.UTF_8);

方案三:使用成熟序列化框架

手动处理流容易出错,推荐用Protobuf、Kryo等成熟框架,它们内置了:

  • 高效的序列化/反序列化
  • 自动粘包/拆包处理
  • 数据完整性校验
  • 支持大对象传输

以Protobuf为例,只需定义消息结构:

message StringUpdate {
    string content = 1;
}

服务器端序列化后发送,客户端直接反序列化即可,无需手动处理长度和字节数组。

额外异常处理建议

  • 捕获EOFException和SocketException时,立即关闭连接并清理资源,避免无效重试
  • 添加心跳机制,定期检测连接状态,及时回收死连接

内容的提问来源于stack exchange,提问作者KisnardOnline

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 16:07:52