2

クライアントコード:

TcpClient client = new TcpClient();
NetworkStream ns;
private void Form1_Load(object sender, EventArgs e)
{
    try
    {
        client.Connect("127.0.0.1", 560);
        ns = client.GetStream();
        byte[] buffer = ReadFully(ns, client.Available);

        //working with the buffer...
    }
    catch
    {
        //displaying error...
    }
}

public static byte[] ReadFully(NetworkStream stream , int initialLength)
{
    // If we've been passed an unhelpful initial length, just
    // use 32K.
    if (initialLength < 1)
    {
        initialLength = 32768;
    }

    byte[] buffer = new byte[initialLength];
    long read = 0;

    int chunk;
    while ((chunk = stream.Read(buffer, (int)read, buffer.Length - (int)read)) > 0)
    {
        read += chunk;

        // If we've reached the end of our buffer, check to see if there's
        // any more information
        if (read == buffer.Length)
        {
            int nextByte = stream.ReadByte();

            // End of stream? If so, we're done
            if (nextByte == -1)
            {
                return buffer;
            }

            // Nope. Resize the buffer, put in the byte we've just
            // read, and continue
            byte[] newBuffer = new byte[buffer.Length * 2];
            Array.Copy(buffer, newBuffer, buffer.Length);
            newBuffer[read] = (byte)nextByte;
            buffer = newBuffer;
            read++;
        }
    }
    // Buffer is now too big. Shrink it.
    byte[] ret = new byte[read];
    Array.Copy(buffer, ret, read);
    return ret;
}

サーバーコード:

    private static TcpListener tcpListener;
        private static Thread listenThread;
        private static int clients;
        static void Main(string[] args)
        {
            tcpListener = new TcpListener(IPAddress.Any, 560);
            listenThread = new Thread(new ThreadStart(ListenForClients));
            listenThread.Start();
        }

        private static void ListenForClients()
        {
            tcpListener.Start();
            Console.WriteLine("Server started.");

            while (true)
            {
                //blocks until a client has connected to the server
                TcpClient client = tcpListener.AcceptTcpClient();

                //create a thread to handle communication
                //with connected client
                Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientComm));
                clientThread.Start(client);
            }
        }

        private static void HandleClientComm(object client)
        {
            clients++;
            TcpClient tcpClient = (TcpClient)client;
            NetworkStream clientStream = tcpClient.GetStream();
            ASCIIEncoding encoder = new ASCIIEncoding();
            Console.WriteLine("Client connected. ({0} connected)", clients.ToString());

            #region sendingHandler
            byte[] buffer = encoder.GetBytes(AddressBookServer.Properties.Settings.Default.contacts);

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

コードからわかるようAddressBookServer.Properties.Settings.Default.contactsに、接続されたクライアントに (空ではない文字列) を送信しようとしています。

問題は、クライアントが文字列を受信する場合があり(これは奇妙な部分です)、何かを受信するのをns.Read待っている回線でブロックされ続ける場合があります。

後で行にブレークポイントを配置してデバッグを試みましたがns.Read、機能しない場合はその行に到達しないため、サーバーから送信されたメッセージを受信しません。

私の質問:どうすれば修正できますか?

私の仮定:クライアントがメッセージを受信する前にサーバーがメッセージを送信しているため、クライアントが受信することはありません。

4

1 に答える 1

8

Mark Gravellが指摘したように、これはフレーミングの問題です。これは、メッセージに長さのプレフィックスを付けてメッセージをフレーム化する方法を示す簡単なクライアントとサーバーです。これは、開始するための単なるサンプルであることに注意してください。私はそれを本番用のコードとは見なしません:

クライアントコード:

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

namespace SimpleClient
{
    internal class Client
    {
        private static void Main(string[] args)
        {
            try
            {
                TcpClient client = new TcpClient();
                NetworkStream ns;
                client.Connect("127.0.0.1", 560);
                ns = client.GetStream();
                byte[] buffer = ReadNBytes(ns, 4);
                    // read out the length field we know is there, because the server always sends it.
                int msgLenth = BitConverter.ToInt32(buffer, 0);
                buffer = ReadNBytes(ns, msgLenth);

                //working with the buffer...
                ASCIIEncoding encoder = new ASCIIEncoding();
                string msg = encoder.GetString(buffer);
                Console.WriteLine(msg);
                client.Close();
            }
            catch
            {
                //displaying error...
            }
        }

        public static byte[] ReadNBytes(NetworkStream stream, int n)
        {
            byte[] buffer = new byte[n];
            int bytesRead = 0;

            int chunk;
            while (bytesRead < n)
            {
                chunk = stream.Read(buffer, (int) bytesRead, buffer.Length - (int) bytesRead);
                if (chunk == 0)
                {
                    // error out
                    throw new Exception("Unexpected disconnect");
                }
                bytesRead += chunk;
            }
            return buffer;
        }
    }
}

サーバーコード:

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

namespace SimpleServer
{
    class Server
    {
        private static TcpListener tcpListener;
        private static int clients;
        static void Main(string[] args)
        {
            tcpListener = new TcpListener(IPAddress.Any, 560);
            tcpListener.Start();
            Console.WriteLine("Server started.");

            while (true)
            {
                //blocks until a client has connected to the server
                TcpClient client = tcpListener.AcceptTcpClient();

                //create a thread to handle communication
                //with connected client
                Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientComm));
                clientThread.Start(client);
            }
        }

        private static void HandleClientComm(object client)
        {
            int clientCount = Interlocked.Increment(ref clients);
            TcpClient tcpClient = (TcpClient)client;
            NetworkStream clientStream = tcpClient.GetStream();
            ASCIIEncoding encoder = new ASCIIEncoding();
            Console.WriteLine("Client connected. ({0} connected)", clientCount);

            #region sendingHandler
            byte[] buffer = encoder.GetBytes("Some Contacts as a string!");
            byte[] lengthBuffer = BitConverter.GetBytes(buffer.Length);
            clientStream.Write(lengthBuffer, 0, lengthBuffer.Length);
            clientStream.Write(buffer, 0, buffer.Length);
            clientStream.Flush();
            tcpClient.Close();
            #endregion
        }
    }
}

コードが機能することも失敗することもある理由は、client.Availableが0を返す可能性があるためです。それが行われたとき、読み取りバイトを32kに設定していたため、読み取り呼び出しはそれらのバイトが着信するのを待っていました。サーバーがソケットを閉じたことはないため、読み取りでもエラーは発生しません。

これがあなたを正しい方向に動かしてくれることを願っています。

編集:

元の投稿でエンディアンについて言及するのを忘れました。エンディアンとBitConverterの使用に関するドキュメントはこちらでご覧いただけます:http://msdn.microsoft.com/en-us/library/system.bitconverter(v = vs.100).aspx

基本的に、サーバーとクライアントの両方が同じエンディアンのアーキテクチャで実行されていることを確認するか、必要に応じて1つのエンディアンから別のエンディアンへの変換を処理する必要があります。

編集2(コメントの質問に答えるため):

1)client.availableが0を返すことができるのはなぜですか?

これはタイミングの問題です。クライアントはサーバーに接続していて、すぐにどのバイトが使用可能かを尋ねます。実行中の他のプロセス、使用可能なプロセッサのタイムスライスなどによっては、サーバーが何かを送信する前に、クライアントが何が使用可能かを尋ねている場合があります。この場合、0を返します。

2)クライアントをインクリメントするためにInterlockedを使用したのはなぜですか?

最初に記述したコードは、HandleClientComm(...)を実行する新しく作成されたスレッドでクライアントをインクリメントしていました。2つ以上のクライアントが同時に接続されている場合、複数のスレッドがクライアントをインクリメントしようとしたため、競合状態が発生する可能性があります。最終的には、クライアントは本来よりも少なくなります。

3)ReadFullyメソッドを変更したのはなぜですか?

私がReadNBytesに変更したReadFullyのバージョンはほぼ正しいものでしたが、いくつかの欠陥がありました。

  • 元のinitialLengthがゼロ以下の場合は、initialLenthを32768に設定します。ソケットから読み取る必要のあるバイト数を推測することは絶対にしないでください。Mark Gravellが述べたように、メッセージを長さのプレフィックスまたはある種の区切り文字でフレーム化する必要があります。
  • NetworkStream.Readは、いくつかのバイトが読み取られるまでブロックするか、ソケットがその下から閉じられている場合は0を返します。ソケットが切断された場合、チャンクはすでに0であるため、stream.ReadByteを呼び出す必要はありませんでした。この変更を行うと、メソッドが簡略化されました。特に、単純なヘッダーに基づいて読み取る必要のあるバイト数が正確にわかっているためです。
  • どれだけ読むかがわかったので、必要なものを前もって正確に割り当てることができます。これにより、返却時にバッファのサイズを変更する必要がなくなります。割り当てたものを返すことができます。

4)長さのバッファサイズが4であることをどのように知ることができますか?

シリアル化した長さは32ビットでした。これは、BitConverter.GetBytes(int value)のドキュメントで確認できます。1バイトは8ビットであることがわかっているので、32を8で割ると4になります。

5)これが本番環境に対応していないのはなぜですか/コードを改善するにはどうすればよいですか?

  • 基本的に、実際のエラー処理はありません。NetworkStream.Read()はいくつかの例外をスローする可能性がありますが、どれも私が処理していません。適切なエラー処理を追加すると、本番環境に対応できるようになります。

  • 私は大雑把な実行を超えてコードを実際にテストしていません。さまざまな条件下でテストする必要があります。

  • クライアントが再接続または再試行するためのプロビジョニングはありませんが、目的のためにこれを必要としない場合があります。

  • これは簡単な例として書かれたものであり、実際に満たそうとしている要件を満たしていない可能性があります。これらの要件がわからないので、これが本番環境に対応しているとは言えません(それが何であれ)。

  • Conceptually ReadNBytes is fine, but if someone sends you malicious message which claim the length of the message is 2 gigabytes or something, you are going to try and allocate 2 gigabytes blindly. Most byte level protocols (the description of what goes over the wire) specify the maximum size of messages. That would need to be checked, and if the messages can actually be large you would need to handle it differently than just allocating a buffer, maybe writing to a file or other output stream as you read it in. Again, not knowing your full requirements, I cannot be sure what is needed there.

Hope this helps.

于 2012-10-02T22:24:09.180 に答える