c# - TcpListener backlog concept misunderstanding
问题描述
I'm trying to understand the backlog parameter of TcpListener
class but I struggle on how to achieve maximum number of pending connections at same time so I can test it.
I have a sample async server and client code. MSDN says that the backlog is the maximum length of the pending connections queue. I made the server listen for connections all the time and the client is connecting 30 times. What I expect is after the 20th request to throw a SocketException
in the client because the backlog is set to 20. Why doesn't it block it?
My second misunderstanding is do I really need to put my logic of the accepted connection in a new thread assuming there is a slow operation which takes around 10 seconds e.g. sending a file over the TCP? Currently, I put my logic in a new Thread
, I know it's not the best solution and instead I should use a ThreadPool
but the question is principal. I tested it by changing the client side's loop to 1000 iterations and if my logic is not in a new thread, the connections were getting blocked after the 200th connection probably because Thread.Sleep slows the main thread each time by 10 seconds and the main thread is responsible for all the accept callbacks. So basically, I explain it myself as the following: if I want to use the same concept, I have to put my AcceptCallback logic in a new thread like I did or I have to do something like the accepted answer here: TcpListener is queuing connections faster than I can clear them. Am I right?
Server code:
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace Server
{
class Program
{
private static readonly ManualResetEvent _mre = new ManualResetEvent(false);
static void Main(string[] args)
{
TcpListener listener = new TcpListener(IPAddress.Any, 80);
try
{
listener.Start(20);
while (true)
{
_mre.Reset();
Console.WriteLine("Waiting for a connection...");
listener.BeginAcceptTcpClient(new AsyncCallback(AcceptCallback), listener);
_mre.WaitOne();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
private static void AcceptCallback(IAsyncResult ar)
{
_mre.Set();
TcpListener listener = (TcpListener)ar.AsyncState;
TcpClient client = listener.EndAcceptTcpClient(ar);
IPAddress ip = ((IPEndPoint)client.Client.RemoteEndPoint).Address;
Console.WriteLine($"{ip} has connected!");
// Actually I changed it to ThreadPool
//new Thread(() =>
//{
// Console.WriteLine("Sleeping 10 seconds...");
// Thread.Sleep(10000);
// Console.WriteLine("Done");
//}).Start();
ThreadPool.QueueUserWorkItem(new WaitCallback((obj) =>
{
Console.WriteLine("Sleeping 10 seconds...");
Thread.Sleep(10000);
Console.WriteLine("Done");
}));
// Close connection
client.Close();
}
}
}
Client code:
using System;
using System.Net.Sockets;
namespace Client
{
class Program
{
static void Main(string[] args)
{
for (int i = 0; i < 30; i++)
{
Console.WriteLine($"Connecting {i}");
using (TcpClient client = new TcpClient()) // because once we are done, we have to close the connection with close.Close() and in this way it will be executed automatically by the using statement
{
try
{
client.Connect("localhost", 80);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
Console.ReadKey();
}
}
}
Edit: Since my second question might be a little bit confusing, I will post my code which includes sent messages and the question is should I leave it like that or put the NetworkStream
in a new thread?
Server:
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace Server
{
class Program
{
private static readonly ManualResetEvent _mre = new ManualResetEvent(false);
static void Main(string[] args)
{
// MSDN example: https://docs.microsoft.com/en-us/dotnet/framework/network-programming/asynchronous-server-socket-example
// A better solution is posted here: https://stackoverflow.com/questions/2745401/tcplistener-is-queuing-connections-faster-than-i-can-clear-them
TcpListener listener = new TcpListener(IPAddress.Any, 80);
try
{
// Backlog limit is 200 for Windows 10 consumer edition
listener.Start(5);
while (true)
{
// Set event to nonsignaled state
_mre.Reset();
Console.WriteLine("Waiting for a connection...");
listener.BeginAcceptTcpClient(new AsyncCallback(AcceptCallback), listener);
// Wait before a connection is made before continuing
_mre.WaitOne();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
private static void AcceptCallback(IAsyncResult ar)
{
// Signal the main thread to continue
_mre.Set();
TcpListener listener = (TcpListener)ar.AsyncState;
TcpClient client = listener.EndAcceptTcpClient(ar);
IPAddress ip = ((IPEndPoint)client.Client.RemoteEndPoint).Address;
Console.WriteLine($"{ip} has connected!");
using (NetworkStream ns = client.GetStream())
{
byte[] bytes = Encoding.Unicode.GetBytes("test");
ns.Write(bytes, 0, bytes.Length);
}
// Use this only with backlog 20 in order to test
Thread.Sleep(5000);
// Close connection
client.Close();
Console.WriteLine("Connection closed.");
}
}
}
Client:
using System;
using System.Net.Sockets;
using System.Text;
namespace Client
{
class Program
{
static void Main(string[] args)
{
for (int i = 0; i < 33; i++)
{
Console.WriteLine($"Connecting {i}");
using (TcpClient client = new TcpClient()) // once we are done, the using statement will do client.Close()
{
try
{
client.Connect("localhost", 80);
using (NetworkStream ns = client.GetStream())
{
byte[] bytes = new byte[100];
int readBytes = ns.Read(bytes, 0, bytes.Length);
string result = Encoding.Unicode.GetString(bytes, 0, readBytes);
Console.WriteLine(result);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
Console.ReadKey();
}
}
}
解决方案
侦听积压在 RFC 6458 中定义,并告诉操作系统在accept queue
.
传入连接由 TCP/IP 堆栈放置在此队列中,并在服务器调用Accept
以处理新连接时删除。
在您的问题中,两个版本的服务器代码都Accept
从主线程循环调用,并在进行另一个接受调用之前等待AcceptCallback
开始。这导致队列很快耗尽。
为了演示侦听队列溢出,最简单的方法是减慢服务器的接受速度 - 例如将其减慢到零:
var serverEp = new IPEndPoint(IPAddress.Loopback, 34567);
var serverSocket = new TcpListener(serverEp);
serverSocket.Start(3);
for (int i = 1; i <= 10; i++)
{
var clientSocket = new TcpClient();
clientSocket.Connect(serverEp);
Console.WriteLine($"Connected socket {i}");
}
Accept
在您的示例中,您可以在主线程的循环末尾添加一个睡眠,并提高连接速率。
在现实世界中,最佳积压取决于:
- 客户端/互联网/操作系统可以填满队列的速率
- 操作系统/服务器可以处理队列的速率
我不建议Thread
直接使用,这是服务器使用Task
和Socket Task Extensions的外观:
static async Task Main(string[] args)
{
var server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
server.Bind(new IPEndPoint(IPAddress.Any, 80));
server.Listen(5);
while (true)
{
var client = await server.AcceptAsync();
var backTask = ProcessClient(client);
}
}
private static async Task ProcessClient(Socket socket)
{
using (socket)
{
var ip = ((IPEndPoint)(socket.RemoteEndPoint)).Address;
Console.WriteLine($"{ip} has connected!");
var buffer = Encoding.Unicode.GetBytes("test");
await socket.SendAsync(buffer, SocketFlags.None);
}
Console.WriteLine("Connection closed.");
}
推荐阅读
- c - 使用带有函数的二维字符数组
- javascript - 数组或迭代器中的每个孩子都应该有一个唯一的“key”道具。在反应js?
- vim - Vim 插入模式下的所有移动和编辑都是不好的做法吗?
- html - Ruby Table CSS - 背景无法正常工作
- angular - 将模板导入 Angular 6 的方法
- jquery - 使用简单 Jquery 的多个模态相同页面(在桌面和移动设备上响应)
- anylogic - 如何减少我使用的源块的数量?
- c# - 获取 Vuforia 目标图像旋转度数
- django - 如何根据当前用户是否是博客帖子的所有者来限制模板中显示的编辑链接?
- automation - 使用 JXA 向新联系人添加电子邮件