【问题标题】:C# Reading data on TCP not always reading all the dataC# 在 TCP 上读取数据并不总是读取所有数据
【发布时间】:2017-11-20 05:38:31
【问题描述】:

这是我的 TCP 监听器,它读取接收到的数据:

    private void Update()
    {
        //Console.WriteLine("Call");
        if (!serverStarted)
        {
            return;
        }

        foreach (ServerClient c in clients.ToList())
        {
            // Is the client still connected?
            if (!IsConnected(c.tcp))
            {
                c.tcp.Close();
                disconnectList.Add(c);
                Console.WriteLine(c.connectionId + " has disconnected.");
                CharacterLogout(c.connectionId);
                continue;
                //Console.WriteLine("Check for connection?\n");
            }
            else
            {
                // Check for message from Client.

                NetworkStream s = c.tcp.GetStream();
                if (s.DataAvailable)
                {
                    StreamReader reader = new StreamReader(s, true);
                    string data = reader.ReadLine();

                    if (data != null)
                    {
                        OnIncomingData(c, data);
                    }
                }
                //continue;
            }
        }

        for (int i = 0; i < disconnectList.Count - 1; i++)
        {
            clients.Remove(disconnectList[i]);
            disconnectList.RemoveAt(i);
        }


    }

    private bool IsConnected(TcpClient c)
    {
        try
        {
            if (c != null && c.Client != null && c.Client.Connected)
            {
                if (c.Client.Poll(0, SelectMode.SelectRead))
                {
                    return !(c.Client.Receive(new byte[1], SocketFlags.Peek) == 0);
                }

                return true;
            }
            else
            {
                return false;
            }
        }
        catch
        {
            return false;
        }
    }

    private void StartListening()
    {
        server.BeginAcceptTcpClient(OnConnection, server);
    }

通常这工作正常。 例如,当我每 2、3 秒或更长的时间间隔发送一个请求时,一切正常。

但是,当我每 1 秒发送 2、3 个请求或什至更少的时间时,一开始它工作正常,过了一会儿它只是切断了接收到的数据。

如果我经常发送请求,我就会收到问题。这是接收到的数据示例。

Character position Updated
{"header":"1x008","data":{"characterId":"1","position.x":"257.8289","position.z":"251.5683","position.y":"2.798551"},"connectionId":1}
Character position Updated
{"header":"1x008","data":{"characterId":"1","position.x":"257.8895","position.z":"251.6447","position.y":"2.796823"},"connectionId":1}
Character position Updated
{"header":"1x008","data":{"characterId":"1","position.x":"257.9507","position.z":"251.7218","position.y":"2.795079"},"connectionId":1}
Character position Updated
{"header":"1x008","data":{"characterId":"1","position.x":"258.0536","position.z":"251.8516","position.y":"2.792141"},"connectionId":1}
Character position Updated
{"header":"1x008","data":{"characterId":"1","position.x":"258.2184","position.z":"252.0594","position.y":"2.793153"},"connectionId":1}
Character position Updated
{"header":"1x008","data":{"characterId":"1","position.x":"258.2699","position.z":"252.1243","position.y":"2.795335"},"connectionId":1}
Character position Updated
{"header":"1x008","data":{"characterId":"1","position.x":"258.3738","position.z":"252.2554","position.y":"2.799742"},"connectionId":1}
Character position Updated
{"header":"1x008","data":{"characterId":"1","position.x":"258.5936","position.z":"252.5325","position.y":"2.80906"},"connectionId":1}
Character position Updated
{"header":"1x008","data":{"characterId":"1","position.x":"258.7353","position.z":"252.7112","position.y":"2.815068"},"connectionId":1}
Character position Updated
{"header":"1x008","data":{"characterId":"1","position.x":"258.7883","position.z":"252.778","position.y":"2.817239"},"connectionId":1}
Character position Updated
{"header":"1x008","data":{"characterId":"1","position.x":"258.9598","position.z":"252.9942","position.y":"2.809892"},"connectionId":1}
Character position Updated
{"header":"1x008","data":{"characterId":"1","position.x":"259.0203","position.z":"253.0705","position.y":"2.806783"},"connectionId":1}
Character position Updated
{"header":"1x008","data":{"characterId":"1","position.x":"259.0894","position.z":"253.1577","position.y":"2.803234"},"connectionId":1}
Character position Updated
{"header":"1x008","data":{"characterId":"1","position.x":"259.2439","position.z":"253.3524","position.y":"2.795195"},"connectionId":1}
Character position Updated
{"header":"1x008","data":{"characterId":"1","position.x":"259.4146","position.z":"253.5677","position.y":"2.784776"},"connectionId":1}
Character position Updated
{"header":"1x008","data":{"characterId":"1","position.x":"259.5829","position.z":"253.7799","position.y":"2.772557"},"connectionId":1}
Character position Updated
{"header":"1x008","data":{"characterId":"1","position.x":"259.7452","position.z":"253.9845","position.y":"2.762063"},"connectionId":1}
Character position Updated
{"header":"1x008","data":{"characterId":"1","position.x":"259.8754","position.z":"254.1487","position.y":"2.760789"},"connectionId":1}
Character position Updated
{"header":"1x008","data":{"characterId":"1","position.x":"260.0437","position.z":"254.3609","position.y":"2.759143"},"connectionId":1}
Character position Updated
{"header":"1x008","data":{"characterId":"1","position.x":"260.2104","position.z":"254.5711","position.y":"2.757511"},"connectionId":1}
Character position Updated
{"header":"1x008","data":{"characterId":"1","position.x":"260.5002","position.z":"254.8992","position.y":"2.756335"},"connectionId":1}
Character position Updated
{"header":"1x008","data":{"characterId":"1","position.x":"260.9822","position.z":"255.1375","position.y":"2.765062"},"connectionId":1}
Character position Updated
{"header":"1x008","data":{"characterId":"1","position.x":"261.5056","position.z":"255.2772","position.y":"2.787397"},"connectionId":1}
Character position Updated
{"header":"1x008","data":{"characterId":"1","position.x":"261.5968","position.z":"255.3015","position.y":"2.791161"},"connectionId":1}
Character position Updated
{"header":"1x008","data":{"characterId":"1","position.x":"261.7819","position.z":"255.3509","position.y":"2.797171"},"connectionId":1}
Character position Updated
{"header":"1x008","data":{"characterId":"1","position.x":"261.8941","position.z":"255.3809","position.y":"2.799694"},"connectionId":1}
Character position Updated
ion.z":"255.5631","position.y":"2.815049"},"connectionId":1}

正如您在最后一行中看到的,JSON 已损坏。数据无缘无故地减少了,它会破坏我的代码。

我可以确认客户端为在服务器/侦听器端出现的最后一行发送了有效的 JSON 字符串。

我认为这可能与缓冲区大小有关,但我对这种 TCP 通信非常陌生。

这是来自客户端的发送函数:

public void Send(string header, Dictionary<string, string> data)
{

    if (stream.CanRead)
    {
        socketReady = true;
    }

    if (!socketReady)
    {
        return;
    }
    JsonData SendData = new JsonData();
    SendData.header = "1x" + header;
    foreach (var item in data)
    {
        SendData.data.Add(item.Key.ToString(), item.Value.ToString());
    }
    SendData.connectionId = connectionId;

    string json = JsonConvert.SerializeObject(SendData);
    var howManyBytes = json.Length * sizeof(Char);
    writer.WriteLine(json);
    writer.Flush();

    Debug.Log("Client World:" + json);
}

我的错误在哪里,我该如何解决?

【问题讨论】:

  • 你的意思是在从客户端发送的每条消息上,客户端都应该以字节为单位发送消息的大小,然后当服务器收到该消息时,他会知道消息的大小,如果不是等于他会将这部分消息放入,直到消息完成到大小?
  • 是的,这是那些答案和链接文章中推荐的解决方案。
  • 我已将Send 函数添加到我的问题中。这是我如何获得发送字符串的大小吗?通过做var howManyBytes = json.Length * sizeof(Char);?你能给我举个例子,我的发送函数应该是什么样子,所以它设置了一个前缀,其大小以我的发送字符串的字节为单位?
  • 您是否在循环运行 Update 代码?含义:单个套接字会到达new StreamReader(s, true) 两次吗?如果是这样:问题几乎可以肯定是您丢失了 StreamReader 缓冲的数据中的一些数据,并且您丢弃了 - 即它读取一行的 allpart 下一个

标签: c# json sockets tcp


【解决方案1】:

StreamReader 包含两个内部缓冲区 - 一个已解码但尚未使用的字符,以及已接收但尚未解码的字节之一(可能是多字节编码的不完整字符)。当它需要数据时,它首先检查缓冲区,如果它没有它需要的东西,然后它从流中读取“一些”数据,直到它可以完成当前请求(ReadLine() 或其他)。在某些情况下,消息稀疏可能意味着它会完整读取一条消息,这可能会错误地表明您的代码正在运行。然而,当多条消息靠近在一起时,它可以从套接字过度读取 - 接收一条消息的全部和另一条消息的一部分。这意味着非常保留每个套接字的StreamReader 非常重要 - 否则当您将其丢到地板上时,您会丢失从套接字消耗的所有数据,但不会丢失阅读之前的ReadLine()。这将完全您看到的结果。所以:作为一个简单的解决方法:不是存储套接字,而是存储一个套接字元组和它们的StreamReader - 并且每个套接字只创建一个读取器。

或者,您可以编写更好的“框架”代码并手动缓冲数据(某处),直到您的框架完成(在您的情况下为行尾)。然后处理后台缓冲区,注意保留帧后剩余的任何额外字节。如果您不熟悉网络代码,第一个选项可能更简单。不过,第二个选项更好 - 它可以避免在接收到部分帧时发生阻塞。目前,单个客户端可以通过发送一些没有换行的文本并仅保持套接字打开(可能每 30 秒运入一个额外的字符)来杀死您的服务器。这将导致您的服务器永远停止在ReadLine() - 或者直到您的服务器OOM,因为如果传入文本。哎呀,发送一大块没有换行符的文本!


可能就像(使用 C# vCurrent)一样简单

List<(Socket socket, StreamReader reader)> sockets = ...

...


// (adding)
var newSocket = ...
var reader = new StreamReader(...); // on newSocket
sockets.Add((newSocket, reader));
// ...

foreach(var pair in sockets) {
    // ...
    var socket = pair.socket;
    var line = pair.reader.ReadLine();
    if(line != null) {...}
    // ...
}

【讨论】:

  • 非常感谢您的详细回答!您能告诉我如何使用 StreamReader 撕开一组套接字吗?我的意思是你能把它添加到你的答案中吗?
  • 我会将您的答案标记为答案,但我会收到消息框架。再一次感谢你。这是一个显示良好消息框架的好指南:youtu.be/J9ae1abjbKM?list=PL-sMjTtTQ_8d-7weAFuK2Yf_jfv9iH3AQ 吗?
  • @TonyStark 其他需要注意的地方:您当前正在检查套接字上的 .DataAvailable - 它告诉您在套接字上本地缓冲了多少 - 但您可以已经在阅读器中缓冲了多行 - 所以 not 处理只是因为 .DataAvailable == 0 可能导致丢失消息(尤其是丢失 final 消息)。 AFAIK,没有简单的方法可以查看StreamReader 中缓冲的内容。这使得简单地根本不使用StreamReader并手动处理帧变得更加引人注目
  • 是的,我想我会使用消息框架。您能否查看我在链接 aboce 中提供的指南并说出您对此有何看法?采用这种方法好吗?
  • @TonyStark 坦率地说,我不会看半小时的视频 - 我是一个以文字为中心的人,但据我所知:它正在用 @987654333 做一些疯狂的奇怪事情@ 作为后台缓冲区,效率很低——而且看起来它也在使用BinaryFormatter,这让我非常紧张。不过,这些东西并不难 - 最终是ReadSomeDataAndAppendToBackBuffer(); while(BackBufferHasEntireFrame()) { ProcessFrameFromBackBuffer(); } - 棘手的一点是保持字节为单位,直到你有一个完整的帧
猜你喜欢
  • 2014-09-15
  • 2011-12-14
  • 2016-11-23
  • 1970-01-01
  • 2023-04-02
  • 2011-08-09
  • 2022-06-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多