我正在尝试正常关闭TCPListener
-意味着,如果有任何客户端已连接,请等待该请求得到响应,然后正常断开连接。
namespace Server {
class Program{
static void Main(string[]args) {
Console.WriteLine("Starting Server");
CancellationTokenSource cts = new CancellationTokenSource();
Server cc = new Server();
// In production, this will not be a task, but its own thread
var t = cc.StartListener(cts.Token);
Console.WriteLine("Server Started - Press any key to exit to stop the listener");
Console.ReadKey(true);
Console.WriteLine("\r\nStopping the listener");
cts.Cancel();
// Wait for the task (that should be a thread to finish
Task[] ts = new Task[1]{ t };
Task.WaitAll(ts);
Console.WriteLine("\r\nListener stopped - exiting");
}
}
public class Server{
public async Task StartListener(CancellationToken cts) {
tcpListener = new TcpListener(IPAddress.Any, 4321);
tcpListener.Start();
Console.WriteLine();
// Keep accepting clients until the cancellation token
while (!cts.IsCancellationRequested) {
var tcpClient = await tcpListener.AcceptTcpClientAsync().ConfigureAwait(false);
// Increment the count of outstanding clients
Interlocked.Increment(ref c);
// When we are done, use a continuation to decrement the count
ProcessClient(tcpClient).ContinueWith((_t) => Interlocked.Decrement(ref c));
Console.Write("\b\b\b\b" + c);
}
Console.WriteLine($ "\r\nWaiting for {c} connections to finish");
// Stop the listener
tcpListener.Stop();
// Stick around until all clients are done
while (c > 0) {}
Console.WriteLine("Done");
}
int c = 0;
public TcpListener tcpListener;
static Random random = new Random();
private async Task ProcessClient(TcpClient tcpClient) {
var ns = tcpClient.GetStream();
try {
byte[]b = new byte[16];
await ns.ReadAsync(b, 0, 16);
// Introduce a random delay to simulate 'real world' conditions
await Task.Delay(random.Next(100, 500));
// Write back the payload we receive (should be a guid, i.e 16-bytes)
await ns.WriteAsync(b, 0, 16);
} catch (Exception ex) {
Console.WriteLine(ex.Message);
}
finally {
tcpClient.Close();
}
}
}
}
这是我的客户
namespace client{
class Program{
static void Main(string[]args) {
List<Task> ts = new List<Task>();
for (int i = 0; i < 5000; i++) {
var t = Connect(i);
ts.Add(t);
}
Task.WaitAll(ts.ToArray());
Console.WriteLine("done - exiting, but first \r\n");
// Group all the messages so they are only output once
foreach(var m in messages.GroupBy(x => x).Select(x => (x.Count() + " " + x.Key))) {
Console.WriteLine(m);
}
}
static object o = new Object();
static Random r = new Random();
static List <string> messages = new List <string> ();
static async Task Connect(int i) {
try {
// Delay below will simulate requests coming in over time
await Task.Delay(r.Next(0, 10000));
TcpClient tcpClient = new TcpClient();
await tcpClient.ConnectAsync("127.0.0.1", 4321);
using(var ns = tcpClient.GetStream()) {
var g = Guid.NewGuid();
// Send a guid
var bytes = g.ToByteArray();
await ns.WriteAsync(bytes, 0, 16);
// Read guid back out
var outputBytes = new byte[16];
await ns.ReadAsync(outputBytes, 0, 16);
// Did we get the right value back?
var og = new Guid(outputBytes);
}
} catch (Exception ex) {
lock(o) {
var message = ex.Message.Length <= 150 ? ex.Message + "..." : ex.Message.Substring(0, 149);
if (messages.IndexOf(message) == -1) {}
messages.Add(message);
}
}
}
}
}
[如果我停止服务器,但客户端继续运行,显然,我会收到一堆
无法建立连接,因为目标计算机主动拒绝了它。 [:: ffff:127.0.0.1]:4321 ...
这是预料之中的,我不理解的是,为什么客户端仍然报告一些连接(很少)被强制关闭。
无法从传输连接中读取数据:现有连接被远程主机强行关闭.....
这是预料之中的,我不理解的是,为什么客户端仍然报告一些连接(很少)被强制关闭。
有两件事阻碍了您的期望:您没有在连接上使用优美的关闭方式,(更重要的是)网络驱动程序代表您接受客户端连接并将其放入“积压”中。
我修改了您的代码示例,以显示关键点的当前系统时间:
我还在服务器中的每个报告之后添加了两秒钟的等待时间,因此侦听套接字的实际关闭和进程的关闭都比报告晚得多。我这样做是为了使将客户端进程的输出与服务器进程及其操作的输出关联起来更加容易。第一次延迟还使待办事项完全填满,因此更容易查看其影响(请参阅下文)。第二个延迟有助于确保进程本身的关闭不会影响连接的处理方式。
服务器的输出看起来像这样:
启动服务器服务器已启动-按任意键退出以停止侦听器163停止听众156等待156连接完成(当前时间为04:10:27.040)完成(04:10:29.048退出)侦听器已停止-正在退出
客户端输出看起来像这样:
完成-退出,但首先200:无法从传输连接中读取数据:远程主机强行关闭了现有连接...(最早时间:04:10:29.047)2514:无法建立连接,因为目标计算机主动拒绝了127.0.0.1:4321 ...(最早的时间:04:10:29.202)
注意:
看到读取的错误消息的数量恰好是200也是很有启发性的。所以,这是怎么回事?
读取错误是由于客户端代表服务器的网络驱动程序接受了其连接,而不是服务器程序本身接受了这些连接。即这些是侦听套接字的积压中的连接。据客户所知,他们的连接已被接受,但实际上服务器进程实际上并没有这样做。因此,网络驱动程序必须强制关闭这些连接。这也是为什么有趣的是,读取错误的数量恰好是200。过去,Windows上非服务器OS的默认(和最大)积压只有5,而服务器OS的积压是200。我正在运行Windows 10“ Pro”,因此我推测微软还增加了操作系统“ Pro”版本的默认积压。我猜想“ Home”版本仍然有旧的默认值5,但是我对此可能是错的。请注意,实际上,由于线程调度和计时问题,实际数量有时可能会比实际积压值稍高。但是它总是很接近。
Random
类不是线程安全的,并且不能保证读取操作将返回您期望的字节(如果要接收任何数据,则读操作可能只返回一个字节就返回)。我认为,就您提供的概念验证示例而言,其他两个问题可能无关紧要。如果不安全使用Random
,将不会返回正确的高斯分布,并且您的代码示例实际上并不关心是否已接收当前字节。我运行了您的代码的版本,并修复了这些问题,它似乎并没有影响整体行为,也不希望这样做。
但是到最后,当处理网络I / O时,尤其是在客户端和服务器之间的协调不紧密的情况下(例如,在确认没有更多客户端连接之前,服务器不会关闭。请求…在现实生活中,这几乎[ must