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

如何判断C# Socket服务器已发送完整消息

解决TCP Socket接收响应时循环无法正常退出的问题

TCP是流式协议,本身没有消息边界,你不能依赖Receive返回0来判断消息结束(只有服务器主动关闭连接时才会返回0)。你当前的问题正是服务器发送完响应后未关闭连接,导致循环一直等待超时。

解决核心是在应用层定义消息边界规则,和服务器端约定好完整消息的标识方式。结合你发送消息时采用的「先发送长度前缀,再发送内容」的格式,最适配的方案是让服务器响应也遵循同样规则:先返回4字节大端序的长度值,再返回响应内容。

修改后的完整代码

public static async Task UseSocketClass(string ipString, int port, string message)
{
    string response = "";
    try
    {
        IPAddress ip = IPAddress.Parse(ipString);
        IPEndPoint ipEndPoint = new(ip, port);
        using Socket client = new Socket(ipEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
        await client.ConnectAsync(ipEndPoint);
        
        // 发送消息(保留原有逻辑)
        int messageLength = message.Length;
        var messageLengthBytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(messageLength));
        var messageBytes = Encoding.UTF8.GetBytes(message);
        byte[] finalMessageBytes = messageLengthBytes.Concat(messageBytes).ToArray();
        client.SendTimeout = 10000;
        client.ReceiveTimeout = 10000;
        await client.SendAsync(finalMessageBytes, SocketFlags.None);
        Console.WriteLine($"Socket client sent message: \"{message}\"");

        // --- 核心修改:按长度前缀接收完整响应 ---
        var lengthBuffer = new byte[4];
        int receivedLengthBytes = 0;
        // 先接收4字节的长度前缀,确保完整接收
        while (receivedLengthBytes < 4)
        {
            int bytesReceived = await client.ReceiveAsync(new ArraySegment<byte>(lengthBuffer, receivedLengthBytes, 4 - receivedLengthBytes), SocketFlags.None);
            if (bytesReceived == 0)
            {
                throw new IOException("服务器提前关闭连接");
            }
            receivedLengthBytes += bytesReceived;
        }
        // 将大端序长度转换为主机字节序
        int responseLength = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(lengthBuffer, 0));
        Console.WriteLine($"预期响应长度: {responseLength}");

        var responseBuffer = new byte[responseLength];
        int receivedResponseBytes = 0;
        // 循环接收直到凑够完整响应的字节数
        while (receivedResponseBytes < responseLength)
        {
            int bytesReceived = await client.ReceiveAsync(new ArraySegment<byte>(responseBuffer, receivedResponseBytes, responseLength - receivedResponseBytes), SocketFlags.None);
            if (bytesReceived == 0)
            {
                throw new IOException("服务器提前关闭连接,未发送完整响应");
            }
            receivedResponseBytes += bytesReceived;
            Console.WriteLine($"已接收字节数: {receivedResponseBytes}");
        }

        // 转换为字符串响应
        response = Encoding.UTF8.GetString(responseBuffer);
        if (!string.IsNullOrEmpty(response))
            Console.WriteLine($"Socket response => {response}");
        else
            Console.WriteLine("Socket response is empty.");

        client.Shutdown(SocketShutdown.Both);
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Response So Far => {response}");
        Console.WriteLine($"{ex.GetType()} => {ex.Message}");
    }
}

备选方案(如果服务器不支持长度前缀)

  • 约定结束符:比如服务器响应末尾固定添加\r\n或特定标识(如[RESPONSE_END])。接收时每次读取数据后检查是否包含结束符,找到后截断结束符并退出循环,注意处理结束符跨缓冲区的情况。
  • 服务器主动关闭连接:服务器发送完响应后立即关闭连接,此时Receive会返回0,你的原循环就能正常退出。但该方式仅适用于短连接场景,无法复用连接。

另外,原代码中混用了异步方法(ConnectAsync、SendAsync)和同步方法(Receive),建议统一使用异步的ReceiveAsync,避免阻塞线程。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 22:12:26