【发布时间】:2016-06-17 17:21:57
【问题描述】:
在我正在处理的应用程序中,我想断开尝试向我发送太大数据包的客户端。 就在断开它们之前,我想向他们发送一条消息,告知他们断开它们的原因。
我遇到的问题是客户端无法接收此服务器消息,如果服务器没有读取客户端首先发送给他的所有内容。我不明白为什么会这样。
我已设法将其范围缩小到一个非常小的测试设置来证明问题。
StreamUtil 类是一个简单的包装类,有助于解决 TCP 消息边界问题,基本上在发送方,它首先发送每条消息的大小,然后是消息本身,在接收方,它接收大小首先是消息,然后是消息。
客户端使用 ReadKey 命令来模拟发送和接收之间的一些时间,在我的实际应用程序中看到这两个操作也不是立即背靠背。
这是一个有效的测试用例:
- 如下图运行服务器
- 如下图运行客户端,会显示“Press key message”,WAIT not press key yet
- 关闭服务器,因为一切都已经在客户端接收缓冲区中(我使用数据包嗅探器验证了这一点)
- 在客户端按键 -> 客户端正确显示来自服务器的消息。
这是我所期待的,到目前为止还没有问题。
现在在服务器代码中,注释掉第二个接收呼叫并重复上述步骤。 步骤 1 和 2 成功完成,从客户端发送到服务器没有错误。 然而,在第 3 步中,客户端在从服务器读取数据时崩溃,即使服务器回复已到达客户端(再次通过数据包嗅探器验证)。
如果我在不关闭服务器上的套接字的情况下进行部分关闭(例如 socket.Shutdown (...send...)),一切正常。
1:我无法理解为什么不在服务器上处理来自客户端的文本行会导致客户端无法接收从服务器发回的文本。
2:如果我将内容从服务器发送到客户端但在实际关闭套接字之前停止服务器,则此内容永远不会到达,但字节已经传输到服务器端......(参见服务器中的 ReadKey 进行模拟,基本上我在那里阻塞然后退出服务器)
如果有人能阐明这两个问题,我将不胜感激。
客户:
class TcpClientDemo
{
public static void Main (string[] args)
{
Console.WriteLine ("Starting....");
TcpClient client = new TcpClient();
try
{
client.Connect("localhost", 56789);
NetworkStream stream = client.GetStream();
StreamUtil.SendString(stream, "Client teststring...");
Console.WriteLine("Press key to initiate receive...");
Console.ReadKey();
Console.WriteLine("server reply:" + StreamUtil.ReceiveString(stream));
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
finally
{
client.Close();
}
Console.WriteLine("Client ended");
Console.ReadKey(true);
}
}
服务器:
class TcpServerDemo
{
public static void Main (string[] args)
{
TcpListener listener = new TcpListener (IPAddress.Any, 56789);
listener.Start ();
Console.WriteLine ("Waiting for clients to serve...");
while (true)
{
TcpClient client = null;
NetworkStream stream = null;
try
{
client = listener.AcceptTcpClient();
stream = client.GetStream();
//question 1: Why does commenting this line prevent the client from receiving the server reply??
Console.WriteLine("client string:" + StreamUtil.ReceiveString(stream));
StreamUtil.SendString(stream, "...Server reply goes here...");
//question 2: If I close the server program without actually calling client.Close (while on this line), the client program crashes as well, why?
//Console.ReadKey();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
break;
}
finally
{
if (stream != null) stream.Close();
if (client != null) client.Close();
Console.WriteLine("Done serving this client, everything closed.");
}
}
listener.Stop();
Console.WriteLine("Server ended.");
Console.ReadKey(true);
}
}
StreamUtil:
public class StreamUtil
{
public static byte[] ReadBytes (NetworkStream pStream, int byteCount) {
byte[] bytes = new byte[byteCount];
int bytesRead = 0;
int totalBytesRead = 0;
try {
while (
totalBytesRead != byteCount &&
(bytesRead = pStream.Read (bytes, totalBytesRead, byteCount - totalBytesRead)) > 0
) {
totalBytesRead += bytesRead;
Console.WriteLine("Read/Total:" + bytesRead + "/" + totalBytesRead);
}
} catch (Exception e) {
Console.WriteLine(e.Message);
}
return (totalBytesRead == byteCount) ? bytes : null;
}
public static void SendString (NetworkStream pStream, string pMessage) {
byte[] sendPacket = Encoding.ASCII.GetBytes (pMessage);
pStream.Write (BitConverter.GetBytes (sendPacket.Length), 0, 4);
pStream.Write (sendPacket, 0, sendPacket.Length);
}
public static string ReceiveString (NetworkStream pStream) {
int byteCountToRead = BitConverter.ToInt32(ReadBytes (pStream, 4), 0);
Console.WriteLine("Byte count to read:"+byteCountToRead);
byte[] receivePacket = ReadBytes (pStream, byteCountToRead);
return Encoding.ASCII.GetString (receivePacket);
}
}
【问题讨论】: