替换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
相关产品推荐
相关产品推荐

