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  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之后ns.Read上放置断点来进行调试,并且我看到当它不起作用时它永远不会到达该行,因此它不接收服务器发送的消息。

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

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

正如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时,所以读取调用正在等待那些字节进入。它们从未这样做过,并且由于服务器从未关闭套接字,因此读取也不会出错。

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

编辑:

我忘了在原帖中提到endianess。 你可以在这里看到关于endianess和使用BitConverter的文档: http : //msdn.microsoft.com/en-us/library/system.bitconverter( v=vs.100) .aspx

基本上,您需要确保服务器和客户端都在具有相同endianess的体系结构上运行,或者根据需要处理从一个endianess到另一个endianess的转换。

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

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

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

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

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

3)为什么我改变了ReadFully方法?

您改为ReadNBytes的ReadFully版本接近正确,但有一些缺陷:

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

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

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

5)为什么这不是生产准备/我如何改进代码?

  • 基本上没有真正的error handling。 NetworkStream.Read()可以抛出几个exception,我正在处理它们。 添加正确的error handling将使生产准备就绪。

  • 我还没有在粗略的运行之外测试代码。 它需要在各种条件下进行测试。

  • 客户端没有重新连接或重试的条款,但您​​可能不需要这些用于您的目的。

  • 这是作为一个简单的例子编写的,可能实际上并不符合您要实现的要求。 不知道这些要求我不能声称这已经为您的生产环境做好了准备(不管是什么)。

  • 从概念上讲,ReadNBytes很好,但如果有人向您发送声称消息长度为2千兆字节的恶意消息,您将尝试盲目地分配2千兆字节。 大多数字节级协议(通过线路的描述)指定了消息的最大大小。 这需要进行检查,如果消息实际上很大,则需要处理它而不仅仅是分配缓冲区,可能在读取时写入文件或其他输出流。再次,不知道您的全部要求,我无法确定那里需要什么。

希望这可以帮助。