TCP client\server - 客户端并不总是读 [英] TCP client\server - client doesn't always read

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

问题描述

客户端代码:



  TcpClient的客户端=新的TcpClient(); 
的NetworkStream纳秒;
私人无效Form1_Load的(对象发件人,EventArgs五)
{

{
client.Connect(127.0.0.1,560);
NS = client.GetStream();
字节[]缓冲=的readFully(NS,client.Available);

//使用缓冲...
}

{
//工作显示错误...
}
}

公共静态的byte []的readFully(的NetworkStream流,诠释initialLength)
{
//如果我们传递了一个无用的初始长度,只需
//使用32K。
如果(initialLength< 1)
{
initialLength = 32768;
}

字节[]缓冲区=新的字节[initialLength]
长读= 0;

INT块;
,而((块= stream.Read(缓冲区(INT)读取,buffer.Length - (INT)读))0)
{
读+ =块;

//如果我们已经达到了缓冲区的末尾,检查,看看是否有
//任何更多的信息
如果(阅读== buffer.Length)
{
INT nextByte = stream.ReadByte();

//尾流?如果是这样,我们就大功告成了
如果(nextByte == -1)
{
返回缓冲区;
}

//不。调整缓冲,摆在我们已经字节只是
//读取,并继续
字节[] = newBuffer新的字节[buffer.Length * 2];
Array.Copy(缓冲液,newBuffer,buffer.Length);
newBuffer [阅读] =(字节)nextByte;
缓冲= newBuffer;
阅读++;
}
}
//缓冲区现在是太大了。它的大小。
字节[] = RET新的字节[阅读]
Array.Copy(缓冲,RET,读取);
返回RET;
}

服务器代码:



 私有静态的TcpListener的TcpListener; 
私有静态主题listenThread;
私有静态诠释的客户;
静态无效的主要(字串[] args)
{
的TCPListener =新的TcpListener(IPAddress.Any,560);
listenThread =新主题(新的ThreadStart(ListenForClients));
listenThread.Start();
}

私有静态无效ListenForClients()
{
tcpListener.Start();
Console.WriteLine(服务器启动。);

,而(真)
{
//阻塞,直到客户端已连接到服务器
TcpClient的客户端= tcpListener.AcceptTcpClient();

//创建一个线程来处理通信
//与连接的客户端
螺纹clientThread =新主题(新ParameterizedThreadStart(HandleClientComm));
clientThread.Start(客户端);
}
}

私有静态无效HandleClientComm(对象客户端)
{
客户++;
的TcpClient的TcpClient =(TcpClient的)客户端;
的NetworkStream clientStream = tcpClient.GetStream();
ASCIIEncoding编码器=新ASCIIEncoding();
Console.WriteLine(客户端连接({0}连接),clients.ToString());

#地区sendingHandler
字节[]缓冲= encoder.GetBytes(AddressBookServer.Properties.Settings.Default.contacts);

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






正如你可以从代码中看到,我试图发送 AddressBookServer.Properties.Settings.Default.contacts (字符串,而不是空)来连接的客户端。



problam是,有时候(这是奇怪的一部分)在客户临危串,有时它保持被挡在了 ns.Read 排队等着收到的东西。



我trye​​d通过把一个断点上线后 ns.Read ,我看到,当它不工作,它从来没有得到该行,所以也没有收到这是由服务器发送的消息。



我的问题:我怎样才能解决这个问题。



我的假设:服务器客户端之前发送邮件能接受吗为此它永远不会被客户收到。


解决方案

正如马克Gravell指出的那样,这是一个框架的问题。下面是一个简单的客户端和服务器向您展示如何与消息的长度前缀包围着您的邮件。请记住,这只是让你开始一个样本。我不认为这是生产就绪代码:



客户端代码:



 使用系统; 
使用System.Collections.Generic;
使用System.Linq的;使用的System.Net.Sockets
;
使用System.Text;

命名空间SimpleClient的
{
内部类客户端
{
私有静态无效的主要(字串[] args)
{
尝试
{
TcpClient的客户端=新的TcpClient();
的NetworkStream纳秒;
client.Connect(127.0.0.1,560);
NS = client.GetStream();
字节[]缓冲= ReadNBytes(NS,4);
//读出我们所知道的是有长度字段,因为服务器总是发送。
INT msgLenth = BitConverter.ToInt32(缓冲液,0);
缓冲= ReadNBytes(NS,msgLenth);

//使用缓冲工作...
ASCIIEncoding编码器=新ASCIIEncoding();
弦乐味精= encoder.GetString(缓冲);
Console.WriteLine(MSG);
client.Close();
}

{
//显示错误...
}
}

公共静态的byte [] ReadNBytes (的NetworkStream流,INT N)
{
字节[]缓冲区=新的字节[N];
INT读取动作= 0;

INT块;
,而(读取动作< N)
{
块= stream.Read(缓冲区(INT)读取动作,buffer.Length - (INT)读取动作);
如果(块== 0)
{
//错误出
抛出新的异常(意外断开);
}
读取动作+ =块;
}
返回缓冲区;
}
}
}

服务器代码:

 使用系统;使用System.Net 
;使用的System.Net.Sockets
;
使用System.Text;
使用的System.Threading;

命名空间SimpleServer来
{
级服务器
{
私有静态的TcpListener的TcpListener;
私有静态诠释的客户;
静态无效的主要(字串[] args)
{
的TCPListener =新的TcpListener(IPAddress.Any,560);
tcpListener.Start();
Console.WriteLine(服务器启动。);

,而(真)
{
//阻塞,直到客户端已连接到服务器
TcpClient的客户端= tcpListener.AcceptTcpClient();

//创建一个线程来处理通信
//与连接的客户端
螺纹clientThread =新主题(新ParameterizedThreadStart(HandleClientComm));
clientThread.Start(客户端);
}
}

私有静态无效HandleClientComm(对象客户端)
{
INT clientCount = Interlocked.Increment(REF客户端);
的TcpClient的TcpClient =(TcpClient的)客户端;
的NetworkStream clientStream = tcpClient.GetStream();
ASCIIEncoding编码器=新ASCIIEncoding();
Console.WriteLine(客户端连接({0}连接),clientCount);

#地区sendingHandler
字节[]缓冲= encoder.GetBytes(一些联系人作为一个字符串!);
字节[] = lengthBuffer BitConverter.GetBytes(buffer.Length);
clientStream.Write(lengthBuffer,0,lengthBuffer.Length);
clientStream.Write(缓冲液,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



基本上你需要确保两个服务器和客户端上的架构具有相同字节顺序运行,或处理从一个字节顺序转换到另一个需要的。



修改2 (回答在评论的问题):



1)为什么client.available返回0



这是一个计时问题。客户端连接到服务器,然后立即询问哪些字节可用。根据什么其他进程正在运行,为可用的处理器等时间段,客户可能会问什么是可用的服务器已经有机会在所有发送任何东西之前。在这种情况下,它会返回0。



2)为什么我用互锁递增的客户呢?



当你最初写它是递增的客户在新创建的线程运行HandleClientComm(...)的代码。如果同时连接两个或两个以上的客户有可能的竞争条件为多个线程试图增加客户可能发生。最终的结果将是客户将低于它应该是。



3)为什么我改的readFully方法?



您的readFully,我改成ReadNBytes,版本接近是正确的,但有一些缺陷:




  • 设置initialLenth 32768如果原始initialLength为零或更低。永远不要猜你需要多少字节从套接字读取。正如马克Gravell提到需要用长度前缀或某种分隔符的包围着您的邮件。

  • NetworkStream.Read块,直到一些字节读取或返回一个0,如果关闭套接字走出它的下面。有没有需要调用stream.ReadByte,因为大块会已经是0,如果插座被断开。做出这样的转变简化了的方法,特别是因为我们知道我们到底需要多少字节根据我们的简单包头阅读。

  • 现在,我们知道我们有多少去阅读,我们可以正是我们需要前面分配。这消除了在返回时重新调整大小的缓冲区的必要性。我们可以只返回我们分配的。



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



我序列长度为32位。你可以看到,BitConverter.GetBytes(int值)的文件中。我们知道,一个字节为8位,所以干脆除以32 8给我们4。



5)为什么是这样而不是生产就绪/我怎样才能提高代码<? / p>


  • 基本上没有真正的错误处理。 NetworkStream.Read()可以抛出一些例外,其中没有我处理。添加适当的错误处理会很长的路要走,以使其生产做好准备。


  • 我还没有真正测试超出粗略运行代码。那就需要在各种条件下进​​行测试。


  • 有没有规定客户端重新连接或重试,但你可能不需要这个你目的。


  • 这是写一个简单的例子,而实际上可能并不符合您要满足要求。不知道这些要求我不能说这是准备好你的生产环境(无论是)。


  • 从概念ReadNBytes是好的,但如果有人向您发送恶意消息它声称该消息的长度为2千兆字节或东西,你要尝试和盲目拨出20亿。大部分字节级协议(什么到线路上的说明)指定的最大邮件大小。这将需要进行检查,如果消息其实是可以大,你需要以不同的方式处理它不仅仅是分配的缓冲区,可能写入文件或你在阅读其他输出流。同样,不知道你的全部要求我不能肯定需要什么就有什么。




希望这有助于。


Client Code:

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

Server Code:

    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
        }


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.

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.

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 question: How can I fix it?

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

解决方案

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:

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

Server Code:

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

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.

Edit:

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.

Edit 2 (to answer question in the comments):

1) Why can client.available return 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) Why did I use Interlocked to increment the clients?

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) Why did I change ReadFully method?

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

  • Setting initialLenth to 32768 if the original initialLength was zero or less. You should never guess how many bytes you need to read from a socket. As Mark Gravell mentioned you need to frame your messages with either a length prefix or some sort of delimiter.
  • NetworkStream.Read blocks until some bytes are read or returns a 0 if the socket is closed out from underneath it. There was no need to call stream.ReadByte, since chunk would already be 0 if the socket was disconnected. Making that change simplified the method, especially since we know exactly how many bytes we need to read based on our simple header.
  • Now that we know how much we are going to read, we can allocate exactly what we need up front. This removes the necessity of re-sizing the buffer upon return. We can just return what we allocated.

4) How do I know the length buffer size is 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) Why is this not production ready/How can I improve the code?

  • 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).

  • 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.

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

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