c# .net

csharp .net WebSocket Client

kimbs0301 2025. 2. 23. 14:46

 

.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

 

Model.cs

using System;

namespace WebSocketClient;

public class EchoMessage
{
	public int Sequence { get; set; }
	public string Content { get; set; } = string.Empty;
	public Int64 CurrentTime { get; set; } = 0;
}

 

PacketCountTimer.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WebSocketClient;

public class PacketCountTimer
{
	public Timer? CountTimer = null;
	public int SendCount = 0;
	public int RecvCount = 0;

	public void Start()
	{
		int interval = 1 * 1000; // in milliseconds
		CountTimer = new Timer(PrintCount, new object(), 1 * 1000, interval);
	}

	private void PrintCount(object? state)
	{
		if (state == null) return;

		if (Monitor.TryEnter(state))
		{
			try
			{
				Console.WriteLine($"SendCount: {SendCount} RecvCount: {RecvCount}");
			}
			catch (Exception ex)
			{
				Console.WriteLine($"Error: {ex.Message}");
			}
			finally
			{
				Monitor.Exit(state);
			}
		}
	}
}

 

Program.cs

using System;
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;

namespace WebSocketClient;

public class Program
{
	public static void Main(string[] args)
	{
		var packetCountTimer = new PacketCountTimer();

		packetCountTimer.Start();

		for (int i = 1; i <= 1000; i++)
		{
			var connectTimeout = new CancellationTokenSource();
			connectTimeout.CancelAfter(2000);

			var url = new Uri($"ws://localhost:2121/connect?username={i}");

			var clientSocket = new ClientWebSocket();
			clientSocket.Options.DangerousDeflateOptions = new WebSocketDeflateOptions
			{
				ClientContextTakeover = true,
				ServerContextTakeover = true
			};
			clientSocket.Options.KeepAliveInterval = TimeSpan.FromSeconds(10); // 10초마다 서버로 Pong 전송
			//clientSocket.Options.KeepAliveInterval = TimeSpan.FromSeconds(0);

			clientSocket.ConnectAsync(url, null, connectTimeout.Token).Wait();

			if (clientSocket.State != WebSocketState.Open)
			{
				Console.WriteLine($"Failed to connect: {url}");
				clientSocket.Dispose();

				continue; // next
			}

			Console.WriteLine($"connect {i}");

			Task.Run(async () =>
			{
				ClientWebSocket socket = clientSocket;

				var recvByteArray = new byte[1024 * 4];

				while (socket.State == WebSocketState.Open)
				{
					var recvBuffer = new ArraySegment<byte>(recvByteArray);
					WebSocketReceiveResult result;
					try
					{
						result = await socket.ReceiveAsync(recvBuffer, CancellationToken.None);
					}
					catch (Exception ex)
					{
                    	if (socket.State == WebSocketState.Open)
						{
							Console.WriteLine($"Error: {ex.Message}");

							try
							{
								await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
								socket.Dispose();
							}
							catch (Exception) { }
                        }

						break;
					}
					
					if (result.MessageType == WebSocketMessageType.Binary)
					{
						if (result.Count < 1) continue; // next
                        
						TryDeserializeClientMessage(Encoding.UTF8.GetString(recvByteArray, 0, result.Count));

						//Console.WriteLine($"{result.MessageType} {result.EndOfMessage} {Encoding.UTF8.GetString(recvByteArray, 0, result.Count)}");

						Interlocked.Increment(ref packetCountTimer.RecvCount);
					}

					else if (result.MessageType == WebSocketMessageType.Text)
					{
						await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
						socket.Dispose();

						break;
					}

					else if (result.MessageType == WebSocketMessageType.Close)
						break;
				}
			});

			Task.Run(async () =>
			{
				ClientWebSocket socket = clientSocket;
				WebSocketMessageFlags socketMessageFlags = CreateMessageFlags(endOfMessage: true, compressMessage: false);

				for (int i = 1; i <= 100000; i++)
				{
					var now = DateTime.UtcNow.Ticks;
					var echoMessage = new EchoMessage()
					{
						Sequence = i,
						Content = DateTime.UtcNow.Ticks.ToString(),
						CurrentTime = now
					};

					//Console.WriteLine(Encoding.UTF8.GetString(JsonSerializer.SerializeToUtf8Bytes(echoMessage)));
					var sendBuffer = new ArraySegment<byte>(JsonSerializer.SerializeToUtf8Bytes(echoMessage));

					Interlocked.Increment(ref packetCountTimer.SendCount);

					try
					{
						await socket.SendAsync(sendBuffer, WebSocketMessageType.Binary, socketMessageFlags, CancellationToken.None);
					}
					catch (Exception ex)
					{
						Console.WriteLine($"Error: {ex.Message}");

						break;
					}

					await Task.Delay(50);
				}

				while (socket.State == WebSocketState.Open)
				{
					await Task.Delay(100);

					if (packetCountTimer.RecvCount >= 10_000_000)
					{
						await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, null, cancellationToken: CancellationToken.None);
						socket.Dispose();

						break;
					}
				}
			});
		}

		Console.ReadLine();
	}

	static EchoMessage? TryDeserializeClientMessage(string message)
	{
		try
		{
			return JsonSerializer.Deserialize<EchoMessage>(message);
		}
		catch (Exception ex)
		{
			Console.WriteLine($"Error: invalid message format {ex.Message}");
			return null;
		}
	}

	private static WebSocketMessageFlags CreateMessageFlags(bool endOfMessage, bool compressMessage)
	{
		WebSocketMessageFlags flags = WebSocketMessageFlags.None;

		if (endOfMessage == true)
			flags |= WebSocketMessageFlags.EndOfMessage;

		if (compressMessage == false)
			flags |= WebSocketMessageFlags.DisableCompression;

		return flags;
	}
}