我有一个服务器应用程序,它只需要一半的时间就可以获取数据。 Why/how 会发生这种情况吗?我该如何解决?

I have a server application that gets data exactly half the time. Why/how does this happen and how do I fix it?

所以我的服务器和聊天客户端是由 2 个不同的 C# TCP tutorials.You 可能识别出 1 个,如果不是两个,我已经对它们进行了自己的修改以适应我自己的风格。当我尝试这两种方法时,它们都工作得很好,损失为 0,但我的版本恰好有 50% 的损失率。 例如: 1. 客户端连接:收到数据 2.客户端发送文本:无数据 3.客户端发送文本:收到数据 4.客户端发送文本:无数据 服务器代码如下:

    using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Net;

namespace WindowsFormsApplication2
{
    class Server
    {
        private TcpListener tcpListener;
        private Thread listenThread;
        public Hashtable clientsList = new Hashtable();
        private System.Windows.Forms.TextBox output;
        private delegate void ObjectDelegate(String text);
        private ObjectDelegate del;

        public Server(System.Windows.Forms.TextBox setOut)
        {
            this.tcpListener = new TcpListener(IPAddress.Any, 8888);
            this.listenThread = new Thread(new ThreadStart(ListenForClients));
            this.listenThread.IsBackground = true;
            this.listenThread.Start();
            output = setOut;
            del = new ObjectDelegate(outputTextToServer);
        }

        private void ListenForClients()
        {
            this.tcpListener.Start();
            while (true)
            {
                //blocks until a client has connected to the server
                TcpClient client = this.tcpListener.AcceptTcpClient();
                //create a thread to handle communication 
                //with connected client
                addClient(client);
                Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientComm));
                clientThread.IsBackground = true;
                clientThread.Start(client);
            }
        }

        private void HandleClientComm(object client)
        {
            TcpClient tcpClient = (TcpClient)client;
            NetworkStream clientStream = tcpClient.GetStream();

            byte[] message = new byte[4096];
            int bytesRead;
            while (true)
            {
                bytesRead = 0;
                try
                {
                    //blocks until a client sends a message
                    bytesRead = clientStream.Read(message, 0, 4096);
                }
                catch
                {
                    //a socket error has occured
                    break;
                }
                if (bytesRead == 0)
                {
                    //the client has disconnected from the server
                    break;
                }
                //message has successfully been received
                String text = getData(clientStream);
                del.Invoke(text); //Used for Cross Threading & sending text to server output
                //if filter(text)
                sendMessage(tcpClient);
                //System.Diagnostics.Debug.WriteLine(text); //Spit it out in the console
            }

            tcpClient.Close();
        }

        private void outputTextToServer(String text)
        {
            if (output.InvokeRequired)
            {
                // we then create the delegate again
                // if you've made it global then you won't need to do this
                ObjectDelegate method = new ObjectDelegate(outputTextToServer);
                // we then simply invoke it and return
                output.Invoke(method, text);
                return;
            }
            output.AppendText(Environment.NewLine + " >> " + text);
        }

        private String getData(NetworkStream stream)
        {
            int newData;
            byte[] message = new byte[4096];
            ASCIIEncoding encoder = new ASCIIEncoding();
            newData = stream.Read(message, 0, 4096);
            String text = encoder.GetString(message, 0, newData); //Translate it into text
            text = text.Substring(0, text.IndexOf("$")); //Here comes the money
            return text;
        }

        private void addClient(object client)
        {
            TcpClient tcpClient = (TcpClient)client;
            NetworkStream clientStream = tcpClient.GetStream();
            String dataFromClient = getData(clientStream);
            if (clientsList.Contains(dataFromClient))
            {
                Console.WriteLine(dataFromClient + " Tried to join chat room, but " + dataFromClient + " is already in use");
                //broadcast("A doppleganger of " + dataFromClient + " has attempted to join!", dataFromClient, false);
            }
            else
            {
                clientsList.Add(dataFromClient, tcpClient);
                //broadcast(dataFromClient + " Joined ", dataFromClient, false);
                del.Invoke(dataFromClient + " Joined chat room ");
                //handleClinet client = new handleClinet();
                //client.startClient(clientSocket, dataFromClient, clientsList);
            }
        }

        private Boolean connectionAlive(NetworkStream stream)
        {
            byte[] message = new byte[4096];
            int bytesRead = 0;
            try
            {
                //blocks until a client sends a message
                bytesRead = stream.Read(message, 0, 4096);
            }
            catch
            {
                //a socket error has occured
                return false;
            }
            if (bytesRead == 0)
            {
                //the client has disconnected from the server
                //clientsList.Remove
                return false;
            }
            return true;
        }

        private void sendMessage(TcpClient client)
        {
            NetworkStream clientStream = client.GetStream();
            ASCIIEncoding encoder = new ASCIIEncoding();
            byte[] buffer = encoder.GetBytes("Hello Client!");

            clientStream.Write(buffer, 0, buffer.Length);
            clientStream.Flush();
        }
    }
}

这是我的客户端代码

    using System;
using System.Windows.Forms;
using System.Text;
using System.Net.Sockets;
using System.Threading;



namespace WindowsFormsApplication2
{

    public partial class Form1 : Form
    {
        public delegate void newDelegate();
        public newDelegate myDelegate;
        System.Net.Sockets.TcpClient clientSocket = new System.Net.Sockets.TcpClient();
        NetworkStream serverStream = default(NetworkStream);
        string readData = null;

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            newMsg();
        }

        private void newMsg()
        {
            byte[] outStream = System.Text.Encoding.ASCII.GetBytes(textBox2.Text + "$");
            serverStream.Write(outStream, 0, outStream.Length);
            serverStream.Flush();
            textBox2.Text = "";
        }

        private void button2_Click(object sender, EventArgs e)
        {
            readData = "Connecting to Chat Server ...";
            msg();
            clientSocket.Connect(txtIP.Text, int.Parse(txtPort.Text));
            serverStream = clientSocket.GetStream();

            byte[] outStream = System.Text.Encoding.ASCII.GetBytes(txtName.Text + "$");
            serverStream.Write(outStream, 0, outStream.Length);
            serverStream.Flush();

            myDelegate = new newDelegate(disconnect);
            Thread ctThread = new Thread(getMessage);
            ctThread.IsBackground = true;
            ctThread.Start();
            button2.Enabled = false;
        }

        private void getMessage()
        {
            while (true)
            {
                serverStream = clientSocket.GetStream();
                int buffSize = 0;
                byte[] inStream = new byte[clientSocket.ReceiveBufferSize];
                buffSize = clientSocket.ReceiveBufferSize;
                try
                {
                    serverStream.Read(inStream, 0, buffSize);
                    string returndata = System.Text.Encoding.ASCII.GetString(inStream);
                    readData = "" + returndata;
                    msg();
                }
                catch
                {
                    Invoke(myDelegate);
                    return;
                }
            }
        }

        private void disconnect()
        {
            button2.Enabled = true;
        }

        private void msg()
        {
            if (this.InvokeRequired)
                this.Invoke(new MethodInvoker(msg));
            else
                textBox1.AppendText(Environment.NewLine + " >> " + readData);
            //textBox1.Text = textBox1.Text + Environment.NewLine + " >> " + readData;
        }

        private void textBox2_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)
            {
                newMsg();
            }
        }

        private void cmdHost_Click(object sender, EventArgs e)
        {
            Server serv = new Server(txtLog);
        }
    }

}

这段代码显然是一个正在进行的工作,对于混乱提前表示歉意。也欢迎对代码提出任何其他建议。

好的,开始有点长了。

您的代码中存在多个错误。从服务器代码开始:

  • 正如 Damien 指出的那样,您正在尝试阅读每个 "message" 两次 - 首先是 HandleClientComm,然后是 getData。流不再有原始数据,因此您只是完全丢弃其中一个读取(因此可疑的 50% "packet" 损失)
  • 稍后,在 getData 中,您丢弃了流中第一个 $ 之后的所有数据。虽然这显然是一种处理消息框架的尝试(因为 TCP 是一种基于流的协议,而不是基于消息的协议),但这是一种愚蠢的做法——您正在丢弃数据。这在您的测试中没有显示的原因是 1) Windows 对待本地 TCP 与远程 TCP 非常不同,2) 您实际上需要能够足够快地发送两条消息以使它们 "blend" 一起在流中。这意味着要么在大约 200 毫秒内发送两条消息(默认 TCP 缓冲),要么在读取时阻塞。
  • 您保持 Flush 网络流。这实际上并没有做任何事情,即使它做了,你也不想那样做。
  • connectionAlive 从共享套接字读取 - 这总是一个坏主意。永远不要超过一个 reader - 多个 reader 不适用于基于流的协议。您似乎没有在示例代码中使用它,但请注意不要尝试这样做。
  • 注释掉的clientList.Remove当然是共享字段的跨线程访问了。如果你想这样做,你必须确保并发访问是安全的 - 通过使用 ConcurrentDictionary 而不是 HashSet,或者通过 locking 围绕每个写 and 读取 clientList.
  • 您希望在一个 Read 中收到完整的消息。这对于一个简单的聊天客户端来说可能没问题,但无论如何它都是糟糕的 TCP - 你需要阅读直到找到你的消息终止符。如果我发送的消息足够大,您的代码将在 text.IndexOf("$").
  • 上死掉

还有很多 "style" 问题,尽管这不是代码审查,所以让我只列出一些:使用古老的技术、服务器的同步套接字、将多线程代码与 GUI 混合随意。这主要是关于可维护性和性能,而不是正确性。

现在,客户端有点简单了:

  • 同样,不要Flush网络流。
  • 如无必要,请不要使用后台线程。只需确保正确终止连接等即可。
  • 断开实际上应该断开。这并不难,只需关闭 TcpClient.
  • readData = "" + returndata 应该做什么?太傻了。
  • 您忽略了 Read 的 return 值。这意味着您不知道您读取了多少字节的数据 - 这意味着您的 returnData 字符串实际上包含消息,后跟几千个 [=28=] 字符。您在输出中看不到它们的唯一原因是因为大多数 Windows 使用 [=28=] 作为字符串终止符 ("it made sense at the time")。 .NET 没有。
  • 同样,Read 需要一次完整的消息。与服务器不同,这不会使客户端崩溃,但您的代码的行为会有所不同(例如,一个额外的 \r\n >>,即使它不是单独的消息。

服务器的样式问题也适用于此。

作为旁注,我最近制作了一个简化的网络示例,它使用更现代的技术处理简单的聊天客户端-服务器系统——使用基于 await 的异步 I/O 而不是例如,多线程。它不是可用于生产的代码,但它应该非常清楚地展示想法和意图(我还建议查看第一个示例,"HTTP-like TCP communication")。您可以在此处找到完整的源代码 - Networking Part 2.