[英]create a TCP socket server which is able to handle thousands of requests per second
首先,我創建了一個基本的TCP服務器:
public class Tcp
{
private TcpListener listener { get; set; }
private bool accept { get; set; } = false;
public void StartServer(string ip, int port)
{
IPAddress address = IPAddress.Parse(ip);
listener = new TcpListener(address, port);
listener.Start();
accept = true;
StartListener();
Console.WriteLine($"Server started. Listening to TCP clients at {ip}:{port}");
}
public async void StartListener() //non blocking listener
{
listener.Start();
while (true)
{
try
{
TcpClient client = await listener.AcceptTcpClientAsync().ConfigureAwait(false);
HandleClient(client);
}
finally { }
}
}
private void HandleClient(TcpClient client)
{
try
{
NetworkStream networkStream = client.GetStream();
byte[] bytesFrom = new byte[20];
networkStream.Read(bytesFrom, 0, 20);
string dataFromClient = System.Text.Encoding.ASCII.GetString(bytesFrom);
string serverResponse = "Received!";
Byte[] sendBytes = Encoding.ASCII.GetBytes(serverResponse);
networkStream.Write(sendBytes, 0, sendBytes.Length);
networkStream.Flush();
}
catch(Exception ex)
{
}
}
}
我編寫了一個客戶端測試代碼,該代碼正在發送和記錄每秒的請求數
public class Program
{
private volatile static Dictionary<int, int> connections = new Dictionary<int, int>();
private volatile static int fail = 0;
private static string message = "";
public static void Main(string[] args)
{
ServicePointManager.DefaultConnectionLimit = 1000000;
ServicePointManager.Expect100Continue = false;
for (int i = 0; i < 512; i++)
{
message += "T";
}
int taskCount = 10;
int requestsCount = 1000;
var taskList = new List<Task>();
int seconds = 0;
Console.WriteLine($"start : {DateTime.Now.ToString("mm:ss")} ");
for (int i = 0; i < taskCount; i++)
{
taskList.Add(Task.Factory.StartNew(() =>
{
for (int j = 0; j < requestsCount; j++)
{
Send();
}
}));
}
Console.WriteLine($"threads stablished : {DateTime.Now.ToString("mm: ss")}");
while (taskList.Any(t => !t.IsCompleted)) { Thread.Sleep(5000); }
Console.WriteLine($"Compelete : {DateTime.Now.ToString("mm: ss")}");
int total = 0;
foreach (KeyValuePair<int, int> keyValuePair in connections)
{
Console.WriteLine($"{keyValuePair.Key}:{keyValuePair.Value}");
total += keyValuePair.Value;
seconds++;
}
Console.WriteLine($"succeded:{total}\tfail:{fail}\tseconds:{seconds}");
Console.WriteLine($"End : {DateTime.Now.ToString("mm: ss")}");
Console.ReadKey();
}
private static void Send()
{
try
{
TcpClient tcpclnt = new TcpClient();
tcpclnt.ConnectAsync("192.168.1.21", 5678).Wait();
String str = message;
Stream stm = tcpclnt.GetStream();
ASCIIEncoding asen = new ASCIIEncoding();
byte[] ba = asen.GetBytes(str);
stm.Write(ba, 0, ba.Length);
byte[] bb = new byte[100];
int k = stm.Read(bb, 0, 100);
tcpclnt.Close();
lock (connections)
{
int key = int.Parse(DateTime.Now.ToString("hhmmss"));
if (!connections.ContainsKey(key))
{
connections.Add(key, 0);
}
connections[key] = connections[key] + 1;
}
}
catch (Exception e)
{
lock (connections)
{
fail += 1;
}
}
}
}
當我在本地計算機上測試它時,我得到的最大每秒請求數為4000,而當我將其上載到本地Lan時,它每秒下降為200個請求。
問題是:如何提高服務器性能? 對套接字服務器進行負載測試的正確方法是什么?
您可能有一個“非阻塞偵聽器”,但是當任何特定的客戶端連接時,它將僅致力於該客戶端,直到該客戶端發送了一條消息並且已將響應發送回該客戶端。 這將無法很好地擴展。
我通常不喜歡async void
,但與您當前的代碼保持一致:
public async void StartListener() //non blocking listener
{
listener.Start();
while (true)
{
TcpClient client = await listener.AcceptTcpClientAsync().ConfigureAwait(false);
HandleClient(client);
}
}
private async void HandleClient(TcpClient client)
{
NetworkStream networkStream = client.GetStream();
byte[] bytesFrom = new byte[20];
int totalRead = 0;
while(totalRead<20)
{
totalRead += await networkStream.ReadAsync(bytesFrom, totalRead, 20-totalRead).ConfigureAwait(false);
}
string dataFromClient = System.Text.Encoding.ASCII.GetString(bytesFrom);
string serverResponse = "Received!";
Byte[] sendBytes = Encoding.ASCII.GetBytes(serverResponse);
await networkStream.WriteAsync(sendBytes, 0, sendBytes.Length).ConfigureAwait(false);
networkStream.Flush(); /* Not sure necessary */
}
我還修復了我在注釋中提到的有關忽略Read
的返回值的錯誤,並刪除了“隱藏我的錯誤,使錯誤無法在野外發現”錯誤處理。
如果不能保證客戶端將始終向該代碼發送20字節的消息,則需要執行其他操作,以便服務器知道要讀取多少數據。 通常可以通過在消息前面加上長度或使用某種形式的哨兵值來表示結束來完成此操作。 請注意,即使使用長度前綴,也不能保證一次性讀取整個長度 ,因此,您還需要使用如上所述的讀取循環來首先發現長度。
如果將一切切換到async
都不能滿足您的需要,那么您需要放棄使用NetworkStream
並開始在Socket
級別上工作,特別是使用旨在與SocketAsyncEventArgs
一起使用的async方法:
SocketAsyncEventArgs類是對System.Net.Sockets.Socket類的一組增強的一部分,該類提供了可供替代的異步模式,供專用的高性能套接字應用程序使用...應用程序可以單獨使用增強的異步模式,也可以使用增強的異步模式。僅在目標熱點地區(例如,在接收大量數據時)。
這些增強功能的主要特點是避免了在大批量異步套接字I / O期間對象的重復分配和同步。
在新的System.Net.Sockets.Socket類增強中,異步套接字操作由應用程序分配和維護的可重用SocketAsyncEventArgs對象描述。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.