gpt4 book ai didi

c# - TCP客户端\服务器 - 客户端并不总是读取

转载 作者:可可西里 更新时间:2023-11-01 02:35:08 27 4
gpt4 key购买 nike

客户代码:

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(字符串,而不是空的)发送到连接的客户机。
问题是,有时(这是wierd部分),客户机接收字符串,有时它在 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
}
}
}

您的代码有时工作,有时失败的原因是客户端。Available可以返回0。当它启动时,您将字节设置为读取32K,因此读取调用正在等待这些字节进入。他们从未这样做过,而且由于服务器从未关闭套接字,所以read也不会出错。
希望这能让你朝着正确的方向前进。
编辑:
我在原来的帖子里忘了提到endianes。您可以在此处查看有关endianes和使用bitconverter的文档: http://msdn.microsoft.com/en-us/library/system.bitconverter(v=vs.100).aspx
基本上,您需要确保服务器和客户机都在具有相同端点的体系结构上运行,或者根据需要处理从一个端点到另一个端点的转换。
编辑2(在评论中回答问题):
1)为什么client.available会返回0?
这是一个时间问题。客户端正在连接到服务器,然后立即询问哪些字节可用。根据正在运行的其他进程、可用处理器的时间片等,客户机可能会在服务器有机会发送任何内容之前询问可用的内容。在这种情况下,它将返回0。
2)为什么我使用联锁来增加客户机?
最初编写的代码是在新创建的运行handleclientcom(…)的线程中递增客户端。如果两个或多个客户端同时连接,则可能会出现争用情况,因为多个线程正在尝试增加客户端。最终的结果是,客户的数量会比实际情况少。
3)我为什么要更改readfully方法?
你的readfully版本,我改为readnbytes,很接近正确,但有一些缺陷:
如果原始初始长度小于等于零,则将initiallenth设置为32768。您不应该猜测需要从一个套接字读取多少字节。正如MarkGravell所提到的,您需要用一个长度前缀或某种分隔符来为消息设置框架。
networkstream.read阻塞直到读取某些字节,或者如果套接字从下面关闭,则返回0。不需要调用stream.readbyte,因为如果套接字断开连接,chunk已经是0。这样做简化了方法,特别是因为我们确切地知道需要根据简单的头读取多少字节。
既然我们知道要读多少,我们就可以预先准确地分配我们需要的内容。这消除了返回时重新调整缓冲区大小的必要性。我们可以把分配的钱还给你。
4)我如何知道长度缓冲区大小是4?
我序列化的长度是32位。您可以在bitconverter.getbytes(int值)文档中看到这一点。我们知道一个字节是8位,所以简单地把32除以8,得到4。
5)为什么不准备好生产/如何改进代码?
基本上没有真正的错误处理。networkstream.read()可以抛出几个异常,而我没有处理这些异常。添加正确的错误处理将大大有助于使生产准备就绪。
我没有真正测试过代码,只是粗略地运行了一下。它需要在各种条件下进行测试。
客户机没有重新连接或重试的规定,尽管您可能不需要这样做。
这只是一个简单的例子,可能并不能真正满足您想要满足的需求。不知道这些需求,我不能声称这已经为您的生产环境(无论是什么)做好了准备。
从概念上讲,readnbytes是可以的,但是如果有人向您发送恶意消息,声称消息的长度是2GB或其他,那么您将尝试盲目地分配2GB。大多数字节级协议(对通过线路的内容的描述)指定消息的最大大小。这需要检查,如果消息实际上可以很大,那么您需要以不同的方式处理它,而不是仅仅分配缓冲区,可能在读取时写入文件或其他输出流。再说一遍,我不知道你的全部要求,我不能确定那里需要什么。
希望这有帮助。

关于c# - TCP客户端\服务器 - 客户端并不总是读取,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12652791/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com