0

したがって、私のサーバーとチャット クライアントは 2 つの異なる C# TCP チュートリアルから作成されています。両方ではないにしても 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);
        }
    }

}

このコードは明らかに進行中の作業であり、ごちゃごちゃして申し訳ありません。コードに対するその他の提案も歓迎します。

4

1 に答える 1

1

さて、これは少し長くなり始めています。

コードに複数のエラーがあります。サーバー コードから始めます。

  • ダミアンが指摘したように、各「メッセージ」を 2 回読み込もうとしています。最初は でHandleClientComm、次に でもう一度読みますgetData。ストリームには元のデータがなくなっているため、読み取りの 1 つを完全に破棄しているだけです (したがって、疑わしい 50% の「パケット」損失)。
  • 後の でgetData、最初の の後のストリーム内のすべてのデータを破棄します$。これは明らかにメッセージ フレーミングを処理するための試みですが (TCP はメッセージ ベースのプロトコルではなくストリーム ベースのプロトコルであるため)、これはばかげています。データを捨てているのです。これがテストで表示されなかった理由は、1) Windows がローカル TCP をリモート TCP とは非常に異なる方法で処理するため、2) 2 つのメッセージをストリーム内で「ブレンド」するのに十分な速さで実際に送信できる必要があるためです。 . つまり、約 200 ミリ秒 (デフォルトの TCP バッファリング) で 2 つのメッセージを送信するか、読み取りをブロックします。
  • Flushネットワークストリームを維持します。これは実際には何もしません。
  • connectionAlive共有ソケットから読み取る - これは常に悪い考えです。複数のリーダーを持つことはありません。複数のリーダーは、ストリームベースのプロトコルでは機能しません。サンプルコードで使用しているようには見えませんが、使用しないように注意してください。
  • コメントアウトclientList.Removeはもちろん、共有フィールドへのクロススレッド アクセスです。このようにしたい場合は、ConcurrentDictionaryの代わりに を使用するHashSetか、の書き込み読み取りのlockたびに ingを使用して、同時アクセスが安全であることを確認する必要があります。clientList
  • メッセージ全体を 1 つで取得することを期待していますRead。単純なチャット クライアントではこれで問題ないかもしれませんが、とにかく TCP が悪いので、メッセージ ターミネータが見つかるまで読む必要があります。十分な大きさのメッセージを送信すると、コードはtext.IndexOf("$").

「スタイル」の問題もたくさんありますが、これはコード レビューではありません。そのため、一部を挙げてみましょう: 古いテクノロジの使用、サーバー用の同期ソケット、マルチスレッド コードと GUI の任意の組み合わせ。ただし、これは主に保守性とパフォーマンスに関するものであり、正確さではありません。

これで、クライアントは少し単純になりました。

  • Flush繰り返しますが、ネットワーク ストリームは行わないでください。
  • 必要がない場合は、バックグラウンド スレッドを使用しないでください。接続などを適切に終了するようにしてください。
  • Disconnectは実際に 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 に答える