TCP 客户端服务器 - 客户端并不总是读取 [英] TCP clientserver - client doesn't always read

查看:29
本文介绍了TCP 客户端服务器 - 客户端并不总是读取的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

客户代码:

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(一个字符串,非空)发送到连接的客户端.


As you can see from the code, i'm trying to send AddressBookServer.Properties.Settings.Default.contacts (a string, not empty) to the connected client.

问题是有时(这是奇怪的部分)客户端收到字符串,有时它在 ns.Read 行上一直被阻塞等待接收某些东西.

The problam is that sometimes(that's the wierd part) the client recieves the string and sometimes its keep being blocked on the ns.Read line waiting to recieve something.

我尝试通过在 ns.Read 之后的行上放置一个断点来进行调试,我看到当它不起作用时,它永远不会到达该行,因此它不会收到消息由服务器发送.

I tryed debuging by putting a breakpoint on the line after ns.Read and i saw that when it doesn't work it never gets to that line, so it doesn't recieve the message that was sent by the server.

我的问题:我该如何解决?

我的假设:服务器在客户端接收消息之前发送消息,因此它永远不会被客户端接收到.

My assumption: The server is sending the message before the client can recieve it therefor it's never get recieved by the client.

推荐答案

正如 Mark Gravell 所指出的,这是一个框架问题.这是一个简单的客户端和服务器,向您展示如何使用消息的长度前缀来构建消息.请记住,这只是帮助您入门的示例.我不会认为它是生产就绪代码:

As Mark Gravell pointed out, this is a framing problem. Here is a simple client and server to show you how to frame your messages with a length prefix on the message. Keep in mind that this is just a sample to get you started. I would not consider it production ready code:

客户代码:

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,因此读取调用正在等待这些字节进来.他们从不确实如此,而且由于服务器从未关闭套接字,因此 read 也不会出错.

The reason your code sometimes worked, and sometimes failed is that client.Available can return 0. When it did you were setting the bytes to read to 32k, so the read call was waiting for those bytes to come in. They never did, and since the server never closed the socket, read would not error out either.

希望这能让你朝着正确的方向前进.

Hope this gets you moving in the right direction.

我忘记在我原来的帖子中提到字节序了.您可以在此处查看有关字节顺序和使用 BitConverter 的文档:http://msdn.microsoft.com/en-us/library/system.bitconverter(v=vs.100).aspx

I forgot to mention endianess in my original post. You can see the documentation here about endianess and using BitConverter: http://msdn.microsoft.com/en-us/library/system.bitconverter(v=vs.100).aspx

基本上,您需要确保服务器和客户端都运行在具有相同字节序的架构上,或者根据需要处理从一种字节序到另一种字节序的转换.

Basically you need to make sure both server and client are running on architectures with the same endianess, or handle the conversion from one endianess to another as needed.

编辑 2(回答评论中的问题):

1) 为什么client.available可以返回0?

1) Why can client.available return 0?

这是一个时间问题.客户端连接到服务器,然后立即询问哪些字节可用.根据正在运行的其他进程、可用处理器的时间片等,客户端可能会在服务器有机会发送任何内容之前询问可用的内容.在这种情况下,它将返回 0.

This is a timing issue. The client is connecting to the server, then immediately asking which bytes are available. Depending on what other processes are running, time slices for the available processor etc, the client may be asking what is available before the server has had a chance to send anything at all. In this case it will return 0.

2) 为什么我使用 Interlocked 来增加客户端?

2) Why did I use Interlocked to increment the clients?

您最初编写的代码是在新创建的运行 HandleClientComm(...) 的线程中增加客户端.如果两个或更多客户端同时连接,则可能会出现竞争条件,因为多个线程试图增加客户端.最终的结果是客户会比应有的少.

The code as you had originally written it was incrementing the clients in the newly created thread running HandleClientComm(...). If two or more clients connected simultaneously it is possible a race condition could occur as multiple threads were trying to increment clients. The end result would be clients would be less than it should be.

3) 为什么我要更改 ReadFully 方法?

3) Why did I change ReadFully method?

你的 ReadFully 版本,我改为 ReadNBytes,几乎是正确的,但有一些缺陷:

You version of ReadFully, which I changed to ReadNBytes, was close to be correct, but had a few flaws:

  • 如果原始 initialLength 为零或更小,则将 initialLenth 设置为 32768.您永远不应该猜测需要从套接字读取多少字节.正如 Mark Gravell 提到的,您需要使用长度前缀或某种分隔符来构建您的消息.
  • NetworkStream.Read 阻塞直到读取一些字节或如果套接字从其下方关闭则返回 0.没有必要调用 stream.ReadByte,因为如果套接字断开连接,块就已经是 0.进行这一更改简化了方法,特别是因为我们根据简单的标头确切知道需要读取多少字节.
  • 既然我们知道要阅读多少内容,我们就可以预先准确分配我们需要的内容.这消除了在返回时重新调整缓冲区大小的必要性.我们可以只返回我们分配的内容.

4) 我怎么知道长度缓冲区大小是 4?

4) How do I know the length buffer size is 4?

我序列化的长度是 32 位.您可以在 BitConverter.GetBytes(int value) 的文档中看到这一点.我们知道一个字节是 8 位,所以简单地将 32 除以 8 得到 4.

The length I serialized was 32 bits. You can see that in the documentation for BitConverter.GetBytes(int value). We know a byte is 8 bits, so simply divide 32 by 8 giving us 4.

5) 为什么这还没有做好生产准备/我该如何改进代码?

5) Why is this not production ready/How can I improve the code?

  • 基本上没有真正的错误处理.NetworkStream.Read() 可以抛出几个异常,我没有处理这些异常.添加适当的错误处理对于使其做好生产准备大有帮助.

  • Basically there is no real error handling. NetworkStream.Read() can throw several exceptions, none of which I am handling. Adding the proper error handling would go a long way to making it production ready.

除了粗略运行之外,我还没有真正测试过代码.它需要在各种条件下进行测试.

I have not really tested the code beyond a cursory run. It would need to be tested under a variety of conditions.

客户端没有重新连接或重试的规定,尽管您可能不需要这样做.

There is no provision for the client to reconnect or retry, though you may not need this for your purposes.

这是作为一个简单示例编写的,可能实际上不符合您尝试满足的要求.不知道这些要求,我不能声称它已为您的生产环境(无论是什么)做好准备.

This was written as a simple example, and may not actually meet the requirements you are trying to fulfill. Not knowing those requirements I cannot claim this is ready for your production environment (whatever that is).

概念上 ReadNBytes 没问题,但是如果有人向您发送声称消息长度为 2 GB 之类的恶意消息,您将尝试盲目分配 2 GB.大多数字节级协议(对网络传输内容的描述)都指定了消息的最大大小.这需要检查,如果消息实际上可能很大,您需要以不同的方式处理它,而不仅仅是分配缓冲区,可能在您读入时写入文件或其他输出流.同样,不知道您的完整要求,我不确定那里需要什么.

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.

希望这会有所帮助.

这篇关于TCP 客户端服务器 - 客户端并不总是读取的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆