C# 创建每秒能够处理数千个请求的TCP套接字服务器
在第一次尝试中,我创建了一个基本的TCP服务器:C# 创建每秒能够处理数千个请求的TCP套接字服务器,c#,sockets,load-testing,tcpclient,tcpserver,C#,Sockets,Load Testing,Tcpclient,Tcpserver,在第一次尝试中,我创建了一个基本的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);
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;
}
}
}
}
公共类程序
{
private volatile静态字典连接=new Dictionary();
私有易失性静态int fail=0;
私有静态字符串消息=”;
公共静态void Main(字符串[]args)
{
ServicePointManager.DefaultConnectionLimit=1000000;
ServicePointManager.Expect100Continue=false;
对于(int i=0;i<512;i++)
{
信息+=“T”;
}
int taskCount=10;
int requestscont=1000;
var taskList=新列表();
整数秒=0;
WriteLine($”开始:{DateTime.Now.ToString(“mm:ss”)});
对于(int i=0;i
{
对于(int j=0;j!t.IsCompleted)){Thread.Sleep(5000);}
Console.WriteLine($“complete:{DateTime.Now.ToString(“mm:ss”)});
int-total=0;
foreach(连接中的KeyValuePair KeyValuePair)
{
WriteLine($“{keyValuePair.Key}:{keyValuePair.Value}”);
总计+=keyValuePair.Value;
秒++;
}
WriteLine($”成功:{total}\t失败:{fail}\t秒:{seconds}”);
WriteLine($“End:{DateTime.Now.ToString(“mm:ss”)});
Console.ReadKey();
}
私有静态void Send()
{
尝试
{
TcpClient tcpclnt=新的TcpClient();
ConnectAsync(“192.168.1.21”,5678).Wait();
字符串str=消息;
Stream stm=tcpclnt.GetStream();
ascienceoding asen=新的ascienceoding();
byte[]ba=asen.GetBytes(str);
stm.Write(ba,0,ba.Length);
字节[]bb=新字节[100];
intk=stm.Read(bb,0100);
tcpclnt.Close();
锁(连接)
{
int key=int.Parse(DateTime.Now.ToString(“hhmmss”);
如果(!connections.ContainsKey(键))
{
连接。添加(键,0);
}
连接[键]=连接[键]+1;
}
}
捕获(例外e)
{
锁(连接)
{
失败+=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 */
}
public async void StartListener()//非阻塞侦听器
{
listener.Start();
while(true)
{
TcpClient client=await listener.AcceptTcpClientAsync().ConfigureAwait(false);
HandleClient(客户);
}
}
专用异步void HandleClient(TcpClient客户端)
{
NetworkStream NetworkStream=client.GetStream();
byte[]bytesFrom=新字节[20];
int totalRead=0;
虽然(totalRead您可能有一个“非阻塞侦听器”,但当任何特定的客户端连接时,它只专注于该客户端,直到该客户端发送消息并向其发送响应为止。这不会很好地扩展
我通常不喜欢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 */
}
public async void StartListener()//非阻塞侦听器
{
listener.Start();
while(true)
{
TcpClient client=await listener.AcceptTcpClientAsync().ConfigureAwait(false);
HandleClient(客户);
}
}
专用异步void HandleClient(TcpClient客户端)
{
NetworkStream NetworkStream=client.GetStream();
byte[]bytesFrom=新字节[20];
int totalRead=0;
while(totalReadSide注意-人们常犯的一个错误是忽略Read
的返回值。它告诉您缓冲区中已放置了多少字节,而该值可能低至1
。TCP不提供消息传递,它提供无休止的字节流。如果您想要消息传递(一端的1个发送与另一端的1个接收相匹配),这取决于您来实现(或者转移到已经提供消息传递的更高级别协议)。为了您的理智起见,我强烈建议您使用SignalR而不是原始TCP/IP。旁注-人们常犯的一个错误是忽略Read
的返回值。它告诉您缓冲区中已放置了多少字节,并且该值可能低至1
。TCP不提供消息传递,它提供了unending字节流。如果您想要消息传递(一端的1个发送与另一端的1个接收相匹配),则由您来实现(或移动到已经提供消息传递的更高级别协议)。为了您的理智起见,我强烈建议您使用SignalR而不是原始TCP/IP。谢谢,您的代码是正确的(我有固定大小的消息)但我正在寻找可伸缩性技巧。你认为重写服务器代码并使用线程而不是非阻塞侦听器是一个好主意吗?@Jozaghi-不,每个客户端的线程也不能很好地伸缩。试着这样做,如我上面所示,保持ev