220 lines
7.0 KiB
C#
220 lines
7.0 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using Newtonsoft.Json;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace UdpClientLib
|
|
{
|
|
public class SingleUdpClient : NetworkClientBase, IDisposable
|
|
{
|
|
private readonly UdpClient _udpClient;
|
|
private readonly Queue<Func<Task>> _sendQueue;
|
|
private readonly SemaphoreSlim _sendSemaphore;
|
|
private bool _disposed;
|
|
private byte _counter = 1; // 1 ~ 255 범위의 카운터
|
|
|
|
public string jsonResponse;
|
|
|
|
public SingleUdpClient(string name, string serverIp, int serverPort)
|
|
{
|
|
Name = name;
|
|
_serverEndPoint = new IPEndPoint(IPAddress.Parse(serverIp), serverPort);
|
|
_sendQueue = new Queue<Func<Task>>();
|
|
_sendSemaphore = new SemaphoreSlim(1, 1);
|
|
|
|
_udpClient = new UdpClient();
|
|
Console.WriteLine($"[{Name}] UDP 클라이언트 생성, 서버: {_serverEndPoint}");
|
|
}
|
|
|
|
public async Task RunAsync(CancellationToken cancellationToken)
|
|
{
|
|
Console.WriteLine($"[{Name}] 클라이언트 Task 시작");
|
|
|
|
try
|
|
{
|
|
while (!cancellationToken.IsCancellationRequested)
|
|
{
|
|
await ProcessSendQueueAsync();
|
|
await CheckForIncomingMessages();
|
|
|
|
await Task.Delay(10, cancellationToken);
|
|
}
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
Console.WriteLine($"[{Name}] 클라이언트 Task 정상 종료");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"[{Name}] 클라이언트 Task 오류: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private async Task ProcessSendQueueAsync()
|
|
{
|
|
await _sendSemaphore.WaitAsync();
|
|
try
|
|
{
|
|
while (_sendQueue.Count > 0)
|
|
{
|
|
var sendAction = _sendQueue.Dequeue();
|
|
await sendAction();
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_sendSemaphore.Release();
|
|
}
|
|
}
|
|
|
|
private async Task CheckForIncomingMessages()
|
|
{
|
|
try
|
|
{
|
|
if (_udpClient.Available > 0)
|
|
{
|
|
var result = await _udpClient.ReceiveAsync();
|
|
jsonResponse = Encoding.UTF8.GetString(result.Buffer);
|
|
Console.WriteLine($"[{Name}] 수신: \"{jsonResponse}\" (from {result.RemoteEndPoint})");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"[{Name}] 수신 오류: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
public Task SendMessageAsync(string message)
|
|
{
|
|
|
|
var tcs = new TaskCompletionSource<bool>();
|
|
|
|
_sendQueue.Enqueue(async () =>
|
|
{
|
|
try
|
|
{
|
|
byte[] data = Encoding.UTF8.GetBytes(message);
|
|
await _udpClient.SendAsync(data, data.Length, _serverEndPoint);
|
|
Console.WriteLine($"[{Name}] 메시지 전송: \"{message}\"");
|
|
tcs.SetResult(true);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"[{Name}] 전송 실패: {ex.Message}");
|
|
tcs.SetException(ex);
|
|
}
|
|
});
|
|
|
|
return tcs.Task;
|
|
}
|
|
public Task SendBytesAsync(byte[] data)
|
|
{
|
|
|
|
var tcs = new TaskCompletionSource<bool>();
|
|
|
|
_sendQueue.Enqueue(async () =>
|
|
{
|
|
try
|
|
{
|
|
String hexString = BitConverter.ToString(data).Replace("-", "");
|
|
byte[] packet = Encoding.UTF8.GetBytes(hexString);
|
|
|
|
await _udpClient.SendAsync(packet, packet.Length, _serverEndPoint);
|
|
Console.WriteLine($"[{Name}] 바이트 전송: {data.Length} bytes");
|
|
|
|
if (data.Length <= 20)
|
|
{
|
|
Console.WriteLine($"데이터: [{string.Join(", ", data)}]");
|
|
}
|
|
|
|
tcs.SetResult(true);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"[{Name}] 바이트 전송 실패: {ex.Message}");
|
|
tcs.SetException(ex);
|
|
}
|
|
});
|
|
|
|
return tcs.Task;
|
|
}
|
|
|
|
public Task SendFilledBytesAsync(Dictionary<int, byte> customValues = null)
|
|
{
|
|
// 기본값: 0000000058000000000000 (12바이트)
|
|
byte[] data = { 0x00, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
|
|
|
data[11] = _counter; // 마지막 바이트에 더하기
|
|
|
|
if (customValues != null)
|
|
{
|
|
foreach (var kvp in customValues)
|
|
{
|
|
int index = kvp.Key;
|
|
byte value = kvp.Value;
|
|
|
|
if (index >= 0 && index < data.Length)
|
|
{
|
|
data[index] = value;
|
|
}
|
|
else
|
|
{
|
|
throw new ArgumentOutOfRangeException(nameof(customValues),
|
|
$"인덱스 {index}는 유효하지 않습니다. (0 ~ {data.Length - 1} 범위)");
|
|
}
|
|
}
|
|
}
|
|
|
|
// 카운터 증가 (255 다음엔 1로 되돌아감)
|
|
_counter++;
|
|
if (_counter > 255)
|
|
_counter = 1;
|
|
|
|
return SendBytesAsync(data);
|
|
}
|
|
|
|
// HTTP 요청 전송
|
|
public Task SendHttpRequestAsync(HttpRequest request)
|
|
{
|
|
//string httpPacket = CreateHttpRequest(request);
|
|
|
|
return SendMessageAsync($"{request.Method} {request.Path}\r\nContent-Type: application/json; charset=utf-8\r\n\r\n");
|
|
}
|
|
|
|
public Task SendGetRequestAsync(string path, Dictionary<string, string> headers = null)
|
|
=> SendHttpRequestAsync(new HttpRequest(HttpMethod.GET, path, headers));
|
|
|
|
public Task SendPostRequestAsync(string path, object body = null, Dictionary<string, string> headers = null)
|
|
{
|
|
string jsonBody;
|
|
|
|
if (body == null)
|
|
{
|
|
jsonBody = null;
|
|
}
|
|
else if (body is string str)
|
|
{
|
|
jsonBody = str;
|
|
}
|
|
else
|
|
{
|
|
jsonBody = JsonConvert.SerializeObject(body);
|
|
}
|
|
return SendHttpRequestAsync(new HttpRequest(HttpMethod.POST, path, headers, jsonBody));
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (!_disposed)
|
|
{
|
|
_udpClient?.Dispose();
|
|
_disposed = true;
|
|
Console.WriteLine($"[{Name}] UDP 클라이언트 종료");
|
|
}
|
|
}
|
|
}
|
|
} |