C# TcpClient发送世界信息时异常关闭Server上的连接
我试图通过与服务器上的客户端连接的 NetworkStream 发送我的世界中的所有图块(称为粒子,不要将它们与客户端粒子效果混淆)(我的世界类中的 500x350 二维数组)和客户端。
代码如下:
public int ServerSendWorldData(NetworkStream networkStream, Point from, Point size, bool reportBytesSent, Networking.Client toClient)
{
int bytesOfAcknowledgementSent = 0;
int totalBytesSent = 0;
int tilesSentThisAcknowledgement = 0;
int tilesToSendForThisAcknowledgement = 20;
for (int x = from.X; x < size.X; x++)
{
for (int y = from.Y; y < size.Y; y++)
{
Thread.Sleep(0);
//Handle acknowledgement if needed.
if (tilesSentThisAcknowledgement >= tilesToSendForThisAcknowledgement)
{
//Wait for Client acknowledgement.
bytesOfAcknowledgementSent += Networking.NetworkingHelp.SendMessageFromByte((byte)Networking.MessageType.ServerSendWorldMapDataWaitAcknowledgement, networkStream);
//Handle here.
if (networkStream.ReadByte() == (byte)Networking.MessageType.ClientAcknowledgeWorldMapData)
tilesSentThisAcknowledgement = 0;
else
throw new Exception("Client did not acknowledge data!");
}
if (world.worldParticles[x, y] != null)
{
//Send Particle Data
totalBytesSent += Networking.NetworkingHelp.SendMessageFromByte((byte)Networking.MessageType.ServerSendWorldMapDataParticle, networkStream);
totalBytesSent += Networking.NetworkingHelp.SendMessageFromInt(x, networkStream);
totalBytesSent += Networking.NetworkingHelp.SendMessageFromInt(y, networkStream);
totalBytesSent += Networking.NetworkingHelp.SendMessageFromInt(world.worldParticles[x, y].ID, networkStream);
//totalBytesSent += Networking.NetworkingHelp.SendMessageFromInt(world.worldParticles[x, y].spriteIndex, networkStream);
totalBytesSent += Networking.NetworkingHelp.SendMessageFromBool(world.worldParticles[x, y].draw, networkStream);
totalBytesSent += Networking.NetworkingHelp.SendMessageFromBool(world.worldParticles[x, y].collisionWithEntities, networkStream);
tilesSentThisAcknowledgement++;
}
}
}
if (reportBytesSent)
{
Statistics.Console.WriteLine("Sent " + totalBytesSent + " bytes of World data to Client " + toClient.name + " and " + bytesOfAcknowledgementSent + " bytes of acknowledgement was exchanged!");
}
return totalBytesSent;
}
NetworkingHelp 类基本上只是 networkStream.Write,使用 BitConverter 将数据(如整数)转换为字节,或者使用 Encoding 将数据(如字符串)转换为 UTF8 中的字节。
该代码总共向客户端发送 0.76 MB 的地图数据。
使用 localhost / IPAddress.Loopback 连接到服务器时,不存在我遇到的问题,但通过互联网向客户端发送数据时出现问题 - 数据发送速度非常慢(可能是由于文件大小,我是虽然不确定)并且没有“处理确认”位代码……
//Handle acknowledgement if needed.
if (tilesSentThisAcknowledgement >= tilesToSendForThisAcknowledgement)
{
//Wait for Client acknowledgement.
bytesOfAcknowledgementSent += Networking.NetworkingHelp.SendMessageFromByte((byte)Networking.MessageType.ServerSendWorldMapDataWaitAcknowledgement, networkStream);
//Handle here.
if (networkStream.ReadByte() == (byte)Networking.MessageType.ClientAcknowledgeWorldMapData)
tilesSentThisAcknowledgement = 0;
else
throw new Exception("Client did not acknowledge data!");
}
客户端收到大约 20 个世界图块并抛出异常“无法将数据写入传输连接:现有连接被远程强制关闭” host”,这似乎发生是因为客户端收到一条 1 字节消息,其中字节 0 作为其内容。
看来“处理确认”代码创建的暂停解决了这个问题,但使世界数据传输速度变慢了很多。
非常感谢您的帮助,我知道我的代码有点混乱(欢迎有关如何改进它的建议,这是我第一次发送这么多数据)。这是用于读取客户端特定消息的代码块。
//WorldParticlesMapDataParticle
else if (recievedMessage[0] == (byte)Networking.MessageType.ServerSendWorldMapDataParticle)
{
//Read the position of the new data.
recievedMessageBytes = networkStream.Read(recievedMessage, 0, 4);
int newParticleX = BitConverter.ToInt32(recievedMessage, 0);
recievedMessageBytes = networkStream.Read(recievedMessage, 0, 4);
int newParticleY = BitConverter.ToInt32(recievedMessage, 0);
//Read the particle ParticleID.
recievedMessageBytes = networkStream.Read(recievedMessage, 0, 4);
int newParticleID = BitConverter.ToInt32(recievedMessage, 0);
//Read the particle SpriteID.
//recievedMessageBytes = networkStream.Read(recievedMessage, 0, 4);
//int newSpriteID = BitConverter.ToInt32(recievedMessage, 0);
//Read the particle draw.
recievedMessageBytes = networkStream.Read(recievedMessage, 0, 1);
bool newParticleDraw = BitConverter.ToBoolean(recievedMessage, 0);
//Read the particle collision.
recievedMessageBytes = networkStream.Read(recievedMessage, 0, 1);
bool newParticleCollision = BitConverter.ToBoolean(recievedMessage, 0);
//Set particle.
try
{
world.worldParticles[newParticleX, newParticleY] = World.Particle.ParticleManager.particleArray[newParticleID];
//world.worldParticles[newParticleX, newParticleY].spriteIndex = newSpriteID;
world.worldParticles[newParticleX, newParticleY].draw = newParticleDraw;
world.worldParticles[newParticleX, newParticleY].collisionWithEntities = newParticleCollision;
}
catch (Exception ex)
{
Statistics.Console.WriteLine("Server requested new Particle at " + newParticleX + "," + newParticleY + ", but Client failed to place it due to an exception : " + ex.Message);
try
{
world.worldParticles[newParticleX, newParticleY] = null;
}
catch
{ }
}
}
I am trying to send all the tiles (which are referred to as Particles, don't confuse them with client particle effects) in my world (500x350 2d array in my World class) through a NetworkStream hooked up with my Client both on the Server and Clientside.
The code is as follows :
public int ServerSendWorldData(NetworkStream networkStream, Point from, Point size, bool reportBytesSent, Networking.Client toClient)
{
int bytesOfAcknowledgementSent = 0;
int totalBytesSent = 0;
int tilesSentThisAcknowledgement = 0;
int tilesToSendForThisAcknowledgement = 20;
for (int x = from.X; x < size.X; x++)
{
for (int y = from.Y; y < size.Y; y++)
{
Thread.Sleep(0);
//Handle acknowledgement if needed.
if (tilesSentThisAcknowledgement >= tilesToSendForThisAcknowledgement)
{
//Wait for Client acknowledgement.
bytesOfAcknowledgementSent += Networking.NetworkingHelp.SendMessageFromByte((byte)Networking.MessageType.ServerSendWorldMapDataWaitAcknowledgement, networkStream);
//Handle here.
if (networkStream.ReadByte() == (byte)Networking.MessageType.ClientAcknowledgeWorldMapData)
tilesSentThisAcknowledgement = 0;
else
throw new Exception("Client did not acknowledge data!");
}
if (world.worldParticles[x, y] != null)
{
//Send Particle Data
totalBytesSent += Networking.NetworkingHelp.SendMessageFromByte((byte)Networking.MessageType.ServerSendWorldMapDataParticle, networkStream);
totalBytesSent += Networking.NetworkingHelp.SendMessageFromInt(x, networkStream);
totalBytesSent += Networking.NetworkingHelp.SendMessageFromInt(y, networkStream);
totalBytesSent += Networking.NetworkingHelp.SendMessageFromInt(world.worldParticles[x, y].ID, networkStream);
//totalBytesSent += Networking.NetworkingHelp.SendMessageFromInt(world.worldParticles[x, y].spriteIndex, networkStream);
totalBytesSent += Networking.NetworkingHelp.SendMessageFromBool(world.worldParticles[x, y].draw, networkStream);
totalBytesSent += Networking.NetworkingHelp.SendMessageFromBool(world.worldParticles[x, y].collisionWithEntities, networkStream);
tilesSentThisAcknowledgement++;
}
}
}
if (reportBytesSent)
{
Statistics.Console.WriteLine("Sent " + totalBytesSent + " bytes of World data to Client " + toClient.name + " and " + bytesOfAcknowledgementSent + " bytes of acknowledgement was exchanged!");
}
return totalBytesSent;
}
The NetworkingHelp class is basically just networkStream.Write with either a BitConverter converting data such as ints to bytes or an Encoding converting data such as strings to bytes in UTF8.
The code sends a total of 0.76 MB of map data to the Client.
The problem I am having is not present when connecting to the server using localhost / IPAddress.Loopback, but the problem appears when sending data to Clients through the internet - the data is sent extremely slowly (probably due to the file size, I'm unsure though) and without the "Handle Acknowledgement" bit of code ...
//Handle acknowledgement if needed.
if (tilesSentThisAcknowledgement >= tilesToSendForThisAcknowledgement)
{
//Wait for Client acknowledgement.
bytesOfAcknowledgementSent += Networking.NetworkingHelp.SendMessageFromByte((byte)Networking.MessageType.ServerSendWorldMapDataWaitAcknowledgement, networkStream);
//Handle here.
if (networkStream.ReadByte() == (byte)Networking.MessageType.ClientAcknowledgeWorldMapData)
tilesSentThisAcknowledgement = 0;
else
throw new Exception("Client did not acknowledge data!");
}
... the Client recieves about 20 world tiles and throws the exception "Unable to write data to the transport connection: An existing connection was forcibly closed by the remote host", this seems to happen because the Client gets a 1 byte message with byte 0 as its contents.
It seems that the pause created by the "Handle Acknowledgement" code solves this problem, but makes the world data transfer a lot slower.
Help would be much appreciated, and I'm aware my code is kind of messy (open to suggestions on how to improve it, it's my first time sending this much data). Here is the block of code for reading the particular message for the Client.
//WorldParticlesMapDataParticle
else if (recievedMessage[0] == (byte)Networking.MessageType.ServerSendWorldMapDataParticle)
{
//Read the position of the new data.
recievedMessageBytes = networkStream.Read(recievedMessage, 0, 4);
int newParticleX = BitConverter.ToInt32(recievedMessage, 0);
recievedMessageBytes = networkStream.Read(recievedMessage, 0, 4);
int newParticleY = BitConverter.ToInt32(recievedMessage, 0);
//Read the particle ParticleID.
recievedMessageBytes = networkStream.Read(recievedMessage, 0, 4);
int newParticleID = BitConverter.ToInt32(recievedMessage, 0);
//Read the particle SpriteID.
//recievedMessageBytes = networkStream.Read(recievedMessage, 0, 4);
//int newSpriteID = BitConverter.ToInt32(recievedMessage, 0);
//Read the particle draw.
recievedMessageBytes = networkStream.Read(recievedMessage, 0, 1);
bool newParticleDraw = BitConverter.ToBoolean(recievedMessage, 0);
//Read the particle collision.
recievedMessageBytes = networkStream.Read(recievedMessage, 0, 1);
bool newParticleCollision = BitConverter.ToBoolean(recievedMessage, 0);
//Set particle.
try
{
world.worldParticles[newParticleX, newParticleY] = World.Particle.ParticleManager.particleArray[newParticleID];
//world.worldParticles[newParticleX, newParticleY].spriteIndex = newSpriteID;
world.worldParticles[newParticleX, newParticleY].draw = newParticleDraw;
world.worldParticles[newParticleX, newParticleY].collisionWithEntities = newParticleCollision;
}
catch (Exception ex)
{
Statistics.Console.WriteLine("Server requested new Particle at " + newParticleX + "," + newParticleY + ", but Client failed to place it due to an exception : " + ex.Message);
try
{
world.worldParticles[newParticleX, newParticleY] = null;
}
catch
{ }
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
您忽略 NetworkStream.Read 的返回值。我认为您假设它总是会读取您要求它读取的字节数,除非出现错误。事实上,您指定的字节数只是它将读取的最大字节数,并且它不会读取比您调用它时当前可用的字节数更多的字节。
“此方法将数据读取到缓冲区参数中,并返回成功读取的字节数。如果没有数据可供读取,则 Read 方法返回 0。Read 操作会读取尽可能多的可用数据,最多可达数量由大小参数指定的字节数。如果远程主机关闭连接,并且已接收到所有可用数据,则 Read 方法立即完成并返回零字节。” - NetworkStream.Read
您已创建假设 TCP 会以某种方式将数据“粘合在一起”,只是因为您在一次调用中发送了数据,这是一个典型的错误。事实上TCP没有这样的机制。
You ignore the return value from
NetworkStream.Read
. I think you are assuming that it will always read the number of bytes you asked it to read unless there's an error. In fact, the number of bytes you specify is just the maximum it will read and it will not read more bytes than are currently available when you call it."This method reads data into the buffer parameter and returns the number of bytes successfully read. If no data is available for reading, the Read method returns 0. The Read operation reads as much data as is available, up to the number of bytes specified by the size parameter. If the remote host shuts down the connection, and all available data has been received, the Read method completes immediately and return zero bytes." - NetworkStream.Read
You have made the classic mistake of assuming TCP will somehow 'glue together' data just because you sent it in a single call. In fact, TCP has no such mechanism.
您一次发送一个粒子,尝试制作一大堆您想要发送的内容,并将其分成一个块发送
you are sending one particle at a time try making a big array of what you want to send and send it in one chunk
为了补充 David Schwartz 的答案,您最好阅读 Stephen Cleary 的 TCP /IP 常见问题解答,尤其是 消息框架部分
To complement David Schwartz's answer, you would be wise to read Stephen Cleary's TCP/IP FAQ, especially the message framing section