C# Socket接收数据触发Encoding.GetString索引越界异常问题
问题背景
- Socket接收数据解析时触发运行时异常
- 异常信息:
System.ArgumentOutOfRangeException: 'Index and count must refer to a location in the buffer. - 待明确两点:
- 该异常的触发原因
Encoding.ASCII.GetString(byte[] bytes, int index, int count)中index参数的实际作用
- 异常触发位置为数据包解析逻辑,对应问题代码如下:
public string Username { get; set; } public string HashPassword { get; set; } public string HWID { get; set; } public Command(string username, string password, string hwid) { Username = username; HashPassword = password; HWID = hwid; } public Command(byte[] data) { int usernameLenght = BitConverter.ToInt32(data, 0); Username = Encoding.ASCII.GetString(data, 1, usernameLenght); int passwordLenght = BitConverter.ToInt32(data, 2); HashPassword = Encoding.ASCII.GetString(data, 3, passwordLenght); // 异常触发行 int hwid = BitConverter.ToInt32(data, 4); HWID = Encoding.ASCII.GetString(data, 5, hwid); } public byte[] ToByteArray() { List<byte> byteList = new List<byte>(); byteList.AddRange(BitConverter.GetBytes(Username.Length)); byteList.AddRange(Encoding.ASCII.GetBytes(Username)); byteList.AddRange(BitConverter.GetBytes(HashPassword.Length)); byteList.AddRange(Encoding.ASCII.GetBytes(HashPassword)); byteList.AddRange(BitConverter.GetBytes(HWID.Length)); byteList.AddRange(Encoding.ASCII.GetBytes(HWID)); return byteList.ToArray(); }
具体解答
index参数作用
Encoding.ASCII.GetString的三个参数定义:
bytes:待解码的原始字节数组index:字节数组中启动解码操作的起始偏移位置,从0开始计数count:从起始偏移位置开始,连续读取的字节总数
方法执行时会强制校验index、count均非负,且index + count不超过字节数组总长度,不满足条件就会抛出你遇到的参数越界异常。
异常触发的根本原因
你的反序列化偏移计算逻辑完全错误,和自身实现的ToByteArray序列化规则不匹配:
- 序列化时调用
BitConverter.GetBytes(字符串长度)写入的长度值是int类型,固定占用4个字节,不是1个字节 - 反序列化时你错误假设长度字段仅占1字节,每次读取长度后仅将偏移加1,导致后续所有长度字段的读取位置完全错位,读出来的长度值是字节数组中随机的脏数据,大概率是一个远大于缓冲区剩余长度的值,最终触发越界。
举个实际的偏移例子:序列化时用户名长度写入在字节数组0-3的位置,从索引4开始才是用户名的ASCII内容。但你反序列化时从0位置读完用户名长度,直接从索引1开始读用户名内容,把长度字段剩余的3个字节当成了用户名内容的一部分,后续读密码长度时从索引2取值,拿到的根本不是你写入的密码长度,而是用户名内容中的字节值,自然会出现计算的读取范围超出缓冲区边界的问题。
修复方案
用动态偏移量逐段读取,每读完一段就把偏移往后移动对应长度,修复后的反序列化代码如下:
public Command(byte[] data) { int offset = 0; // 读取用户名 int usernameLength = BitConverter.ToInt32(data, offset); offset += 4; // int类型长度占4字节,读完偏移后移4位 Username = Encoding.ASCII.GetString(data, offset, usernameLength); offset += usernameLength; // 读完内容,偏移后移内容对应长度 // 读取密码 int passwordLength = BitConverter.ToInt32(data, offset); offset += 4; HashPassword = Encoding.ASCII.GetString(data, offset, passwordLength); offset += passwordLength; // 读取HWID int hwidLength = BitConverter.ToInt32(data, offset); offset += 4; HWID = Encoding.ASCII.GetString(data, offset, hwidLength); }
额外注意:TCP是面向流的协议,不存在天然的数据包边界,不能假设单次Socket接收操作就能拿到完整的序列化字节数组,接收端必须实现粘包/半包处理逻辑,攒够完整数据包长度后再传入构造函数解析,否则仍可能出现解析异常。
内容的提问来源于stack exchange,提问作者IdkWhatShouldIUse
相关产品推荐
相关产品推荐

