0

So my server and chat client are made from 2 different C# TCP tutorials.You may recognize 1 if not both of them and I have made my own modifications to them to fit my own style. When I tried both they worked perfectly fine with 0 loss, but my version has exactly a 50% loss rate. For instance: 1. A client connects: Data received 2. A client sends text: No Data 3. A client sends text: Data received 4. A client sends text: No Data The server code is as follows:

    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();
        }
    }
}

And here's my client code

    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);
        }
    }

}

This code is obviously a work in progress and sorry in advance for messiness. Any other suggestions to the code are also welcome.

4

1 回答 1

1

好的,这开始有点长了。

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

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

还有很多“风格”问题,虽然这不是代码审查,所以让我列出一些:使用古老的技术,服务器的同步套接字,随意混合多线程代码和 GUI。不过,这主要是关于可维护性和性能 - 而不是正确性。

现在,客户端有点简单:

  • 再次,不要Flush网络流。
  • 如果不需要,请不要使用后台线程。只需确保正确终止连接等。
  • Disconnect实际上应该断开连接。没那么难,关掉就好了TcpClient
  • 应该怎么readData = "" + returndata做?这很愚蠢。
  • 您忽略了Read. 这意味着您不知道您读取了多少字节的数据 - 这意味着您的returnData字符串实际上包含消息后跟几千个\0字符。您在输出中看不到它们的唯一原因是因为大多数 Windows 都将\0其用作字符串终止符(“当时它是有意义的”)。.NET 没有。
  • 再次,Read期望立即获得整个消息。与服务器不同,这不会使客户端崩溃,但您的代码会表现不同(例如,\r\n >>即使它不是单独的消息,也会有额外的行为。

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

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

于 2015-06-02T06:30:05.617 回答