我有简单的服务器从客户端获取字符串并在屏幕上打印。 我也有简单的客户端,发送数据和关闭:
static void Main()
{
var client = new TcpClient("localhost", 26140);
var stream = client.GetStream();
Byte[] data = System.Text.Encoding.UTF8.GetBytes("CALC qwer");
stream.Write(data, 0, data.Length);
stream.Close();
client.Close();
//Thread.Sleep(100);
}
使用未注释的字符串'Thread.Sleep(100)'它可以正常工作。 但是在评论时,有时候(5-10次运行中的1次)客户端不会发送字符串。 看着wireshark和netstat我注意到客户端发送SYN,ACK包,建立连接并退出而不发送任何内容而不关闭套接字。
有人可以解释这个行为吗?睡眠有帮助吗?我做错了什么?
UPD:
使用此示例代码在关闭之前添加flush()确实有效,感谢Fox32。
但在此之后我又回到了我的初始代码:
var client = new TcpClient("localhost", 26140);
client.NoDelay = true;
var stream = client.GetStream();
var writer = new StreamWriter(stream);
writer.WriteLine("CALC qwer");
writer.Flush();
stream.Flush();
stream.Close();
client.Close();
即使使用NoDelay也无法正常工作。这很糟糕 - 在网络流上使用StreamWriter?
UPD:
这是服务器代码:
static void Main(string[] args)
{
(new Server(26140)).Run();
}
在服务器类中:
public void Run()
{
var listener = new TcpListener(IPAddress.Any, port);
listener.Start();
while (true)
{
try
{
var client = listener.AcceptTcpClient();
Console.WriteLine("Client accepted: " + client.Client.RemoteEndPoint);
var stream = client.GetStream();
stream.ReadTimeout = 2000;
byte[] buffer = new byte[1000];
stream.Read(buffer, 0, 1000);
var s = Encoding.UTF8.GetString(buffer);
Console.WriteLine(s);
}
catch (Exception ex)
{
Console.WriteLine("ERROR! " + ex.Message);
}
}
}
UPD:
添加偶数睡眠(1)会导致同时运行的30-50个客户端中的1个发生崩溃。 添加睡眠(10)似乎完全解决了,我无法抓住任何崩溃。 不明白,为什么socket需要这几毫秒来正确关闭。
答案 0 :(得分:5)
TcpClient
正在使用Nagle's algorithm并在通过网络发送之前等待更多数据。如果快速关闭套接字,则不会传输任何数据。
您有多种方法可以解决此问题:
NetworkStream
有一个Flush
方法用于刷新流内容(我不确定此方法是否可以对MSDN上的注释执行任何操作)
禁用Nagle算法:将TcpCLient
的{{3}}设置为true。
最后一个选项是设置TcpClient
的{{3}}。 NoDelay
方法文档指出,在调用LingerState
时使用了Close
答案 1 :(得分:4)
在几乎所有情况下,您应该在Shutdown
或Socket
之前致电TcpClient
,然后再处理它。粗暴地处置会破坏连接。
您的代码基本上包含TCP堆栈的竞争条件。
设置NoDelay
也是对此的修复,但会影响性能。拨打Flush
恕我直言仍会导致无序关机。不要这样做,因为它们只是通过隐藏症状来掩盖问题的黑客。致电Shutdown
。
我想强调Shutdown
上调用的Socket
是我所知道的唯一有效解决方案。甚至Flush
只会强制数据进入网络。由于网络连接,它仍然可能丢失。调用Close
后将不会重新传输,因为Close
是套接字上的粗鲁终止。
不幸的是TcpClient has a bug迫使你去底层的Socket关闭它:
tcpClient.Client.Shutdown();
tcpClient.Close();
根据Reflector,如果您曾访问过GetStream
,则会出现此问题,而Close不会关闭底层套接字。据我估计,这个错误产生的原因是开发人员并不真正了解Shutdown
的重要性。很少有人知道,很多应用程序因此而错。 A related question.
答案 2 :(得分:1)
在您的服务器端代码中,您只调用Read()
一次,但是当您调用read时,您无法假设数据可用。您必须继续循环读取,直到没有更多数据可用。请参阅下面的完整示例。
我尝试使用最少量的代码重现您的问题但无法做到。服务器每次都打印出客户端消息。没有特殊设置,例如NoDelay
,也没有明确的Close()
或Flush()
,只有Using
语句可确保所有资源得到妥善处理。
class Program
{
static int port = 123;
static string ip = "1.1.1.1";
static AutoResetEvent waitHandle = new AutoResetEvent(false);
static void Main(string[] args)
{
StartServer();
waitHandle.WaitOne();
for (int x=0; x<1000; x++)
{
StartClient(x);
}
Console.WriteLine("Done starting clients");
Console.ReadLine();
}
static void StartClient(int count)
{
Task.Factory.StartNew((paramCount) =>
{
int myCount = (int)paramCount;
using (TcpClient client = new TcpClient(ip, port))
{
using (NetworkStream networkStream = client.GetStream())
{
using (StreamWriter writer = new StreamWriter(networkStream))
{
writer.WriteLine("hello, tcp world #" + myCount);
}
}
}
}, count);
}
static void StartServer()
{
Task.Factory.StartNew(() =>
{
try
{
TcpListener listener = new TcpListener(port);
listener.Start();
Console.WriteLine("Listening...");
waitHandle.Set();
while (true)
{
TcpClient theClient = listener.AcceptTcpClient();
Task.Factory.StartNew((paramClient) => {
TcpClient client = (TcpClient)paramClient;
byte[] buffer = new byte[32768];
MemoryStream memory = new MemoryStream();
using (NetworkStream networkStream = client.GetStream())
{
do
{
int read = networkStream.Read(buffer, 0, buffer.Length);
memory.Write(buffer, 0, read);
}
while (networkStream.DataAvailable);
}
string text = Encoding.UTF8.GetString(memory.ToArray());
Console.WriteLine("from client: " + text);
}, theClient);
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
}, TaskCreationOptions.LongRunning);
}
}
答案 3 :(得分:0)
UPD:
我在几台计算机上测试了这个bug,没有任何崩溃。好像它是我计算机上的本地错误。
ENDOFUPD
所以,我发现了关于再现这个bug的内容。 @Despertar - 您的代码运行良好。但它不是重现这个bug的条件。在客户端上,您需要发送数据并在其后退出。在您的代码中,许多客户端正在发送数据,并且在所有应用程序关闭之后。
这就是我在计算机上测试它的方法: 我有服务器(只接受连接和打印传入数据),客户端(只在结束退出时发送数据)和运行实用程序(运行客户端exe多次)。
因此,我启动服务器,将运行实用程序复制到clients文件夹并运行它。 运行ulility启动150个客户端连接到服务器,其中5-10个死亡(我在服务器控制台中看到错误)。并且在客户端上取消注释Thread.Sleep()效果很好,没有错误。
有人可以尝试重现此版本的代码吗?
客户代码:
private static void Main(string[] args)
{
try
{
using (TcpClient client = new TcpClient(ip, port))
{
using (NetworkStream networkStream = client.GetStream())
{
using (StreamWriter writer = new StreamWriter(networkStream))
{
writer.WriteLine("# hello, tcp world #");
writer.Flush();
}
networkStream.Flush();
networkStream.Close();
}
client.Close();
//Thread.Sleep(10);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
代码,多次运行客户端(在exe文件中编译并放在客户端的exe附近 - 这段代码将逐个运行许多客户端):
static void Main(string[] args)
{
string path = "YOU_CLIENT_PROJECT_NAME.exe";
for (int i = 0; i < 150; i++ )
{
Console.WriteLine(i);
Process.Start(path);
Thread.Sleep(50);
}
Console.WriteLine("Done");
Console.ReadLine();
}
(不要忘记更改exent exe文件名的路径)
服务器代码:
class Program
{
static int port = 26140;
static AutoResetEvent waitHandle = new AutoResetEvent(false);
static void Main(string[] args)
{
StartServer();
waitHandle.WaitOne();
Console.ReadLine();
}
static void StartServer()
{
Task.Factory.StartNew(() =>
{
try
{
TcpListener listener = new TcpListener(port);
listener.Start();
Console.WriteLine("Listening...");
waitHandle.Set();
while (true)
{
TcpClient theClient = listener.AcceptTcpClient();
Task.Factory.StartNew(paramClient =>
{
try
{
TcpClient client = (TcpClient) paramClient;
byte[] buffer = new byte[32768];
MemoryStream memory = new MemoryStream();
using (NetworkStream networkStream = client.GetStream())
{
networkStream.ReadTimeout = 2000;
do
{
int read = networkStream.Read(buffer, 0, buffer.Length);
memory.Write(buffer, 0, read);
} while (networkStream.DataAvailable);
string text = Encoding.UTF8.GetString(memory.ToArray());
}
}
catch (Exception e)
{
Console.WriteLine("ERROR: " + e.Message);
}
}, theClient);
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
}, TaskCreationOptions.LongRunning);
}
}
答案 4 :(得分:0)
我尝试过代码,在几台计算机上重现这个错误。没有人崩溃。好像这是我当地的计算机错误。
感谢大家帮助我。
无论如何,这太奇怪了。如果我发现我的计算机上存在这个错误的原因,我会写一下。