【发布时间】:2017-01-26 19:24:22
【问题描述】:
信息:
我为企业创建了一个 C# 控制台服务器和 WinForms 客户端。
(如果我错了,请纠正我,我已经做了很多研究。所以我可能混淆了某些主题。)
在服务器上,我读到使用Socket.BegingReceive 和AsyncCallback 方法是最好的。由于它使用ThreadPool 类。对客户端进行多线程处理,同时重用线程等。(而不是在单个线程设置或每个客户端一个线程上执行 while 循环。(我已经读到这些具有我不感兴趣的特定缺点。))
基本上,我努力的是确保如果我正在制作的应用程序突然爆炸并且有 100,000(甚至可能 1,000,000?!)人们同时使用它,它将为每个客户端保持良好和稳定的连接.
情况:
服务器: Intel i5 4 Core:服务器在另一台服务器上处理 MYSQL 请求,并将其以他们期望的方式格式化返回给客户端。
客户端:无限的硬件资源:浏览表单时向服务器询问数据以填写表单。
所以我担心: 假设有 4 个客户端连接并请求服务器处理数据,这大约需要 5 秒来处理 MYSQL 数据库(我知道,处理速度就像一生。 (最坏的情况等))。如果第五个客户端连接怎么办。这意味着 ThreadPool 必须创建另一个线程来保存这个进程。现在使这些数字更大,我有 100 个同时客户端请求确切的长时间运行方法。在 4 核 CPU 上。因此,现在 ThreadPool 花费了十多秒或更长时间来创建所有这些新线程。
问题:
编辑:缩小问题范围:
我如何为 FindPrimeNumbers 调用实现异步编程。 (该方法是在数据库(不同服务器上的 MySQL,如果有帮助的话)搜索数字。)
我该怎么做才能防止这种情况发生?我如何使它更直观。如何防止大负载可能造成不良的用户体验等。
须藤代码
FindPrimerNumber 方法类似于数据库调用。
class Program
{
private static Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
private static List<Socket> clientSockets = new List<Socket>();
private static CancellationTokenSource CancelSource = new CancellationTokenSource();
private static CancellationToken CancelToken = CancelSource.Token;
private const int BUFFER_SIZE = 2048;
private const int PORT = 100;
private static readonly byte[] buffer = new byte[BUFFER_SIZE];
static void Main()
{
SetupServer();
Console.ReadLine();
CloseAllSockets();
}
private static void SetupServer()
{
serverSocket.Bind(new IPEndPoint(IPAddress.Any, PORT));
serverSocket.Listen(0);
serverSocket.BeginAccept(BeginAcceptCallback, null);
}
private static void CloseAllSockets()
{
foreach (Socket socket in clientSockets)
{
socket.Shutdown(SocketShutdown.Both);
socket.Close();
}
serverSocket.Close();
}
private static void BeginAcceptCallback(IAsyncResult AR)
{
Socket socket = serverSocket.EndAccept(AR);
clientSockets.Add(socket);
socket.BeginReceive(buffer, 0, BUFFER_SIZE, SocketFlags.None, BeginReceiveCallback, socket);
Console.WriteLine("Client connected!");
serverSocket.BeginAccept(BeginAcceptCallback, null);
}
private static void BeginReceiveCallback(IAsyncResult AR)
{
Socket current = (Socket)AR.AsyncState;
int receivedInt = current.EndReceive(AR);
byte[] received = new byte[receivedInt];
Array.Copy(buffer, received, receivedInt);
current.BeginReceive(buffer, 0, BUFFER_SIZE, SocketFlags.None, BeginReceiveCallback, current);
string text = Encoding.ASCII.GetString(received);
int number = 0;
if (text.ToLower() == "exit")
{
current.Shutdown(SocketShutdown.Both);
current.Close();
clientSockets.Remove(current);
Console.WriteLine("Client Disconnected");
return;
}
else if (int.TryParse(text, out number))
{
int num = FindPrimeNumber(number);
byte[] data = Encoding.ASCII.GetBytes(num.ToString());
current.Send(data);
Console.WriteLine("Prime Number: "+num+" :Sent");
}
else
{
current.Send(Encoding.ASCII.GetBytes("Not a Command"));
}
}
//VERY LONG CPU FUNCTION
public static int FindPrimeNumber(int n)
{
//Connect to database (MySQL Database on a different server, if that makes a difference)
//Send Query
//Read Return
return queryReturn()
}
}
【问题讨论】:
-
只要让你的 SQL 请求异步。此外,您误解了线程池中的线程如何管理代码执行。
-
并非所有东西都绑定到 CPU,几乎总是在通信中中继的主要原因,这不需要太多 CPU 时间,所以,只要所有东西都异步并且不等待同步响应 - 一切都很好,并且你只需要担心你的硬件。
-
是的,可扩展到大量用户的网络服务器的最大敌人是阻塞线程。任何阻塞代码都应替换为
async/await等效代码。 W.R.T.使用 APMBeginXXX、EndXXX对,您还应该考虑转向 TAP API(即支持async/await的基于任务的 API)。您的代码会更简洁,并且它们与您在帖子中提到的 APM API 具有或多或少相同的性能特征,并且具有显着减少认知负担的优势。 -
...所以任何时候你有阻塞的代码,例如同步 IO、
Thread.Sleep、等待信号量等,这些都应该替换为async await基于任务的等效项。跨度> -
如果您绝对热衷于避免阻塞代码,那么您的 ThreadPool 确实没有理由需要多个线程。在典型的 CRUD 应用程序上,CPU 几乎处于空闲状态,我已经为数千个并发客户端提供服务。它很少需要启动超过默认线程数。
标签: c# mysql multithreading sockets threadpool