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

TCP客户端无法完整读取服务器消息问题求助

问题描述

服务器TCP数据发送格式

服务器的数据发送结构分为Header和Data两部分:

------------|--------------------------------
---Header---|  ----------- Data ----------
------------|--------------------------------
  • Header固定40字节,前2字节表示Data的总长度,剩余38字节均为0。
  • 当Data总长度超过1500字节时,会分两次发送:
    • 第一次:发送完整Header + 1460字节的Data
    • 第二次:仅发送剩余的Data(无Header)
      示例:若Data总长度为1597字节
    first transaction -> 
     -------|--------------------
     |Header| data of length 1460
     -------|--------------------
    second transaction ->
     |--------------------
     data of length 147 (Note this transaction doesn't have header)
     |--------------------
    

客户端读取流程

  1. 先读取40字节的Header,解析出Data的总长度。
  2. 根据解析出的长度读取对应字节数的Data。

遇到的问题

  • 本地运行完全正常,但在其他电脑上,仅**调试模式(逐步执行)**下能正常读取,非调试模式下只能读取消息前半部分,丢弃后续内容,抛出如下错误:

System.Exception
HResult=0x80131500
Message=Exception occurred {0}
Source=SockTest
StackTrace:
at socketTest.sockTest.readData() in C:\usr\testing\SocketTransREcive\SockTest\sockTest.cs:line 125
at socketTest.main.testTrasRecv(sockTest sock, String command) in C:\usr\testing\SocketTransREcive\SockTest\Program.cs:line 13
at socketTest.main.testSend() in C:\usr\testing\SocketTransREcive\SockTest\Program.cs:line 51
at socketTest.main.Main(String[] args) in C:\usr\testing\SocketTransREcive\SockTest\Program.cs:line 63

This exception was originally thrown at this call stack:
[External Code]
socketTest.sockTest.readData() in sockTest.cs

Inner Exception 1:
ArgumentOutOfRangeException: Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection. (Parameter 'count')

  • 已用Wireshark确认服务器发送的数据完全正常,求解决思路。

客户端代码

namespace socketTest
{
    using System;
    using System.Net;
    using System.Net.Sockets;
    public class sockTest : IDisposable
    {
        // defaults
        private string ip = "192.168.1.100";
        private int port = 1234;
        //private int port = 16666;
        public int error = -1;
        public int noError = 0;
        bool disposed = false;
        private byte[] buffer;
        private byte[] header;
        // updated once instanciated 
        private IPAddress Ip;
        public TcpClient socketHandle;
        public NetworkStream tcpStream;
        // instance check 
        private static readonly Lazy<sockTest> lazy = new Lazy<sockTest>(() => new sockTest());

        private sockTest()
        {
            try
            {
                Console.WriteLine("Program started !");
                this.Ip = IPAddress.Parse(ip);

            }
            catch (Exception exception)
            {
                //throw new Exception("Could not initialize socket erro : {0}", exception);
                Console.WriteLine("Could not initialize socket error: {0}", exception);
            }
        }

        public static sockTest Instance
        {
            get
            {
                return lazy.Value;
            }
        }

        public int connect()
        {
            try
            {
                this.socketHandle = new TcpClient();
                this.buffer = new byte[4000];
                this.header = new byte[10];
                this.socketHandle.Connect(this.Ip, this.port);
                this.tcpStream = socketHandle.GetStream();
                return noError;
            }

            catch (Exception exception)
            {
                Console.WriteLine(exception.ToString());
                return error;
            }

        }

        public int prcessHeader(byte[] header)
        {
            int ind = 0;
            int flag = 0;
            for (int i = 0; i < 10; i = i + 2)
            {
                int value = (header[i + 0]) | (header[i + 1] << 8);
                if(flag == 0)
                {
                  
                    ind = value;
                    flag = 1;
                }
            }

            return ind;
        }

        public string readData()
        {
            try
            {
                tcpStream.Read(this.header, 0, 10);
                var inputString = System.Text.Encoding.ASCII.GetString(this.header);
                //int msg_len = Int32.Parse(inputString);
                int msg_len = prcessHeader(this.header);
                tcpStream.Read(this.buffer, 0, msg_len);
                return System.Text.Encoding.ASCII.GetString(this.buffer);
            }
            catch (Exception exception)
            {
                throw new Exception("Exception occurred {0}", exception);
                //return null;
            }
        }



        public int sendData(string data)
        {
            try
            {
                byte[] bData = System.Text.Encoding.ASCII.GetBytes(data);

                tcpStream.Write(bData, 0, bData.Length * sizeof(byte));
                return noError;
            }
            catch (Exception exception)
            {
                return error;
            }
        }

        public void closeConnection()
        {
            tcpStream.Close();
            this.socketHandle.Close();

        }

        public void Dispose()
        {
            // Dispose of unmanaged resources.
            this.CheckDispose(true);
            // Suppress finalization.
            GC.SuppressFinalize(this);
        }

        protected virtual void CheckDispose(bool disposing)
        {
            if (disposed)
                return;

            if (disposing)
            {
                this.closeConnection();
            }

            disposed = true;
        }


    }
}

解决方案

核心问题分析

  1. Header读取不完整:服务器Header是40字节,但代码中仅读取10字节,导致解析出的Data长度错误,后续读取时易触发越界异常。
  2. TCP流读取逻辑错误:NetworkStream.Read()不保证一次性读取指定长度的字节,调试模式下因执行慢数据已全部到达,能读到完整内容;非调试模式下数据分批到达,直接按指定长度读取会导致内容缺失或越界。
  3. 分块发送处理缺失:代码未适配服务器分两次发送数据的逻辑,无Header的后续数据会被误当作下一条消息的Header,引发混乱。

修复步骤

1. 修正Header读取逻辑

  • 将header数组大小改为40字节,确保能容纳完整Header:
    // 在connect方法中修改
    this.header = new byte[40];
    
  • 循环读取直到获取完整的40字节Header:
    // 替换readData中的Header读取代码
    int totalHeaderRead = 0;
    while (totalHeaderRead < 40)
    {
        int read = tcpStream.Read(this.header, totalHeaderRead, 40 - totalHeaderRead);
        if (read == 0)
        {
            throw new IOException("Connection closed while reading header");
        }
        totalHeaderRead += read;
    }
    

2. 简化Header解析逻辑

直接解析Header前2字节即可得到Data总长度:

public int prcessHeader(byte[] header)
{
    // 按小端序解析前2字节的Data长度
    return BitConverter.ToUInt16(header, 0);
}

3. 实现完整的Data读取逻辑

循环读取直到获取全部指定长度的Data,适配分块发送场景:

// 替换readData中的Data读取代码
int totalDataRead = 0;
// 确保buffer足够容纳数据
if (msg_len > this.buffer.Length)
{
    Array.Resize(ref this.buffer, msg_len);
}
while (totalDataRead < msg_len)
{
    int read = tcpStream.Read(this.buffer, totalDataRead, msg_len - totalDataRead);
    if (read == 0)
    {
        throw new IOException("Connection closed while reading data");
    }
    totalDataRead += read;
}

4. 修复异常抛出格式

原代码中异常消息格式化错误,修正为:

throw new Exception($"Exception occurred {exception.Message}", exception);

修复后的readData方法示例

public string readData()
{
    try
    {
        // 读取完整的40字节Header
        int totalHeaderRead = 0;
        while (totalHeaderRead < 40)
        {
            int read = tcpStream.Read(this.header, totalHeaderRead, 40 - totalHeaderRead);
            if (read == 0)
            {
                throw new IOException("Connection closed while reading header");
            }
            totalHeaderRead += read;
        }

        // 解析Data总长度
        int msg_len = prcessHeader(this.header);

        // 确保buffer足够容纳数据
        if (msg_len > this.buffer.Length)
        {
            Array.Resize(ref this.buffer, msg_len);
        }

        // 读取完整的Data
        int totalDataRead = 0;
        while (totalDataRead < msg_len)
        {
            int read = tcpStream.Read(this.buffer, totalDataRead, msg_len - totalDataRead);
            if (read == 0)
            {
                throw new IOException("Connection closed while reading data");
            }
            totalDataRead += read;
        }

        return System.Text.Encoding.ASCII.GetString(this.buffer, 0, msg_len);
    }
    catch (Exception exception)
    {
        throw new Exception($"Exception occurred {exception.Message}", exception);
    }
}

额外注意事项

  • 编码适配:若服务器使用非ASCII编码,需对应调整解码方式(如UTF-8)。
  • Buffer优化:可初始设置更大的buffer,减少动态Resize的开销。
  • 异常处理:可根据业务需求添加重试、重连等逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 02:35:21