Coding Memo
에코 서버 (TcpClient, async/await 이용) 본문
TcpClient 및 TcpListener는 Socket을 직접 컨트롤 하지 않고 간단하고 빠르게 서버를 오픈하고 클라이언트로 서버에 연결할 수 있도록 구성되어 있는 기본 클래스이다.
https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.tcpclient?view=net-7.0
특징으로는 동기 및 비동기 메서드를 포함하고 있다는 것과 Send와 Receive시에 NetworkStream으로 데이터를 읽고 쓸 수 있다는 점이다. 추가적으로 Client 프로퍼티를 통해 연결되어 있는 Socket을 직접 컨트롤 할 수도 있다.
단순히 좀 더 높은 레벨에서 간단하게 서버와 클라이언트의 TCP 연결을 구현할 수 있구나~ 정도로 알아도 될 것 같다. async/await를 이용한 비동기 서버와 클라이언트에서 유용하게 사용될 수 있을 것 같다. (SocketAsyncEventArgs 사용 X)
해당 코드는 서버와 클라이언트 1:1 연결을 고려해서 간단하게 작성되었다.
Server
class Program
{
async static Task Main()
{
string host = Dns.GetHostName(); // local host name of my pc
IPHostEntry ipHost = Dns.GetHostEntry(host);
IPAddress ipAddr = ipHost.AddressList[0];
IPEndPoint endPoint = new(ipAddr, port: 7777);
TcpListener listener = new TcpListener(endPoint);
listener.Start();
var client = await listener.AcceptTcpClientAsync();
Console.WriteLine($"Connected: {client.Client.RemoteEndPoint}");
client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
HandleClient(client);
Console.ReadLine();
client.Close();
}
static async void HandleClient(TcpClient client)
{
try
{
using (NetworkStream stream = client.GetStream())
{
byte[] buffer = new byte[1024];
while (true)
{
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
string receivedMessage = Encoding.Unicode.GetString(buffer, 0, bytesRead);
Console.WriteLine($"recv: {receivedMessage}");
await stream.WriteAsync(buffer.AsMemory(0, bytesRead));
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
finally
{
client.Close();
}
}
}
Client
class Program
{
async static Task Main()
{
await Task.Delay(1000); // Thread.Sleep(1000);
string host = Dns.GetHostName(); // local host name of my pc
IPHostEntry ipHost = Dns.GetHostEntry(host);
IPAddress ipAddr = ipHost.AddressList[0];
IPEndPoint endPoint = new(ipAddr, port: 7777);
TcpClient client = new TcpClient();
client.Connect(endPoint); // 동기 호출!
Console.WriteLine($"Connected to {client.Client.RemoteEndPoint}");
int i = 0;
using (NetworkStream stream = client.GetStream())
{
byte[] receiveBuffer = new byte[1024];
while (true)
{
string msg = $"Hello: {i}";
byte[] buffer = Encoding.Unicode.GetBytes(msg);
await stream.WriteAsync(buffer.AsMemory(0, buffer.Length));
int bytesRead = await stream.ReadAsync(receiveBuffer.AsMemory(0, receiveBuffer.Length));
string receivedMessage = Encoding.Unicode.GetString(receiveBuffer, 0, bytesRead);
Console.WriteLine($"recv: {receivedMessage}");
i++;
await Task.Delay(1000);
}
}
}
}
socket에 대해 직접 read/write 한 것이 아니라 GetStream()을 통해 NetworkStream 객체를 가져오면서 send/recv를 했다는 것에 주목해보자.
send시에는 스트림에 Write를 하였고, recv 시에는 Read를 하였다.
NetworkStream 객체가 SocketAsyncEventArgs와 비슷한 역할을 한다고 볼 수도 있겠다.
async/await를 이용해서 Event처리 기반이 아닌 비동기 기반으로 서버를 만들면 코드 읽기는 편할 것같다. 하지만 많은 클라이언트를 처리하기 위해서는 이벤트 처리 기반이 더 낫다는 것 같다.
클라이언트가 많을 때, async/await의 경우 처리하는 이후 코드를 실행하는 스레드가 너무 자주 바뀌어서 그런가..?
'Language > C#' 카테고리의 다른 글
Environment.TickCount 오버플로우 (대안) (0) | 2023.11.29 |
---|---|
싱글턴 멀티 스레드 주의 (1) | 2023.11.23 |
[C#] .NET 환경에서 지원하는 C# 버전 (0) | 2023.08.14 |
.NET Framework, .NET Core, .NET Standard (0) | 2023.08.14 |
[C#][Serilog] Serilog 멀티스레드 (0) | 2023.08.11 |