using System;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using Systehttp://m.Threading.Tasks;
// --- SAEA 기반 TCP 서버 ---
public class SaeaTcpServer
{
private readonly int _port;
private readonly Socket _listenSocket;
private readonly int _maxConnections;
private readonly BufferManager _bufferManager;
private readonly ConcurrentStack<SocketAsyncEventArgs> _saeaPool;
private readonly SemaphoreSlim _connectionLimiter;
public SaeaTcpServer(int port, int maxConnections, int bufferSize)
{
_port = port;
_maxConnections = maxConnections;
_listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_bufferManager = new BufferManager(bufferSize * maxConnections, bufferSize);
_saeaPool = new ConcurrentStack<SocketAsyncEventArgs>();
_connectionLimiter = new SemaphoreSlim(maxConnections, maxConnections);
// SAEA 풀 생성
for (int i = 0; i < maxConnections; i++)
{
var saea = new SocketAsyncEventArgs();
saea.SetBuffer(_bufferManager.TakeBuffer(), 0, bufferSize);
saea.Completed += IO_Completed;
saea.UserToken = new ClientState();
_saeaPool.Push(saea);
}
}
public void Start()
{
_listenSocket.Bind(new IPEndPoint(IPAddress.Any, _port));
_listenSocket.Listen(512);
StartAccept(null);
Console.WriteLine($"[SaeaTcpServer] Listening on port {_port}");
}
private void StartAccept(SocketAsyncEventArgs? acceptEventArg)
{
if (acceptEventArg == null)
{
acceptEventArg = new SocketAsyncEventArgs();
acceptEventArg.Completed += Accept_Completed;
}
else
{
acceptEventArg.AcceptSocket = null;
}
_connectionLimiter.Wait();
if (!_listenSocket.AcceptAsync(acceptEventArg))
{
ProcessAccept(acceptEventArg);
}
}
private void Accept_Completed(object? sender, SocketAsyncEventArgs e) => ProcessAccept(e);
private void ProcessAccept(SocketAsyncEventArgs e)
{
try
{
if (!_saeaPool.TryPop(out var saea))
{
Console.WriteLine("[WARN] No available SAEA in the pool.");
e.AcceptSocket?.Close();
_connectionLimiter.Release();
StartAccept(e);
return;
}
((ClientState)saea.UserToken).Reset(e.AcceptSocket);
if (!e.AcceptSocket!.ReceiveAsync(saea))
ProcessReceive(saea);
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] Accept: {ex.Message}");
}
finally
{
StartAccept(e);
}
}
private void IO_Completed(object? sender, SocketAsyncEventArgs e)
{
try
{
switch (e.LastOperation)
{
case SocketAsyncOperation.Receive: ProcessReceive(e); break;
case SocketAsyncOperation.Send: ProcessSend(e); break;
default: throw new ArgumentException("Unknown operation.");
}
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] IO_Completed: {ex.Message}");
CloseClientSocket(e);
}
}
private void ProcessReceive(SocketAsyncEventArgs e)
{
var state = (ClientState)e.UserToken;
if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
{
state.AppendReceived(e.Buffer, e.Offset, e.BytesTransferred);
while (state.HasCompleteLine(out string line))
{
state.AdvanceProtocol(line, out string? response);
if (!string.IsNullOrEmpty(response))
{
var sendBytes = Encoding.UTF8.GetBytes(response + "\n");
Buffer.BlockCopy(sendBytes, 0, e.Buffer, e.Offset, sendBytes.Length);
e.SetBuffer(e.Offset, sendBytes.Length);
if (!e.AcceptSocket!.SendAsync(e))
ProcessSend(e);
return;
}
}
// 다음 데이터 수신
if (!e.AcceptSocket!.ReceiveAsync(e))
ProcessReceive(e);
}
else
{
CloseClientSocket(e);
}
}
private void ProcessSend(SocketAsyncEventArgs e)
{
// 송신 후 계속 수신대기 (다음 단계)
if (!e.AcceptSocket!.ReceiveAsync(e))
ProcessReceive(e);
}
private void CloseClientSocket(SocketAsyncEventArgs e)
{
try
{
e.AcceptSocket?.Shutdown(SocketShutdown.Both);
}
catch { }
e.AcceptSocket?.Close();
((ClientState)e.UserToken).Reset(null);
_saeaPool.Push(e);
_connectionLimiter.Release();
}
// ---- BufferManager: 버퍼 풀 관리 ----
public class BufferManager
{
private readonly byte[] _buffer;
private readonly int _bufferSize;
private int _currentIndex;
public BufferManager(int totalBytes, int bufferSize)
{
_buffer = new byte[totalBytes];
_bufferSize = bufferSize;
_currentIndex = 0;
}
public byte[] TakeBuffer()
{
if (_currentIndex + _bufferSize > _buffer.Length)
throw new InvalidOperationException("No more buffer.");
var slice = new byte[_bufferSize];
Buffer.BlockCopy(_buffer, _currentIndex, slice, 0, _bufferSize);
_currentIndex += _bufferSize;
return slice;
}
}
// ---- ClientState: 클라이언트 프로토콜 및 상태 ----
public class ClientState
{
private Socket? _socket;
private readonly StringBuilder _sb = new();
private int _protocolStep = 0;
public void Reset(Socket? socket)
{
_socket = socket;
_sb.Clear();
_protocolStep = 0;
}
public void AppendReceived(byte[] buffer, int offset, int count)
{
_sb.Append(Encoding.UTF8.GetString(buffer, offset, count));
}
// 라인 단위 파싱
public bool HasCompleteLine(out string line)
{
var str = _sb.ToString();
int idx = str.IndexOf('\n');
if (idx >= 0)
{
line = str.Substring(0, idx).Trim('\r', '\n');
_sb.Remove(0, idx + 1);
return true;
}
line = "";
return false;
}
// --- 프로토콜 8단계 처리 ---
public void AdvanceProtocol(string input, out string? response)
{
response = null;
switch (_protocolStep)
{
case 0: // 연결 즉시 "ACCESS ALLOW" 송신
response = "ACCESS ALLOW";
_protocolStep++;
break;
case 1: // MyName 수신 → "OK"
response = "OK";
_protocolStep++;
break;
case 2: // HEART BEAT 수신 → "OK"
response = "OK";
_protocolStep++;
break;
case 3: // Status 값 수신 → DB 저장 → "END"
SaveStatusToDatabaseAsync(input);
response = "END";
_protocolStep++;
break;
default:
// 모든 단계 이후에는 더 이상 응답 없음 (연결 종료)
break;
}
}
// Status를 DB에 비동기로 저장 (실제 DB 연동 코드로 교체)
private void SaveStatusToDatabaseAsync(string status)
{
// 예시: Task.Run, 실서비스는 비동기 Queue/Worker 권장
_ = Task.Run(() =>
{
Console.WriteLine($"[DB] Status 저장: {status}");
// 실제 DB INSERT 코드 위치
});
}
}
}
// --- 메인 함수(실행 예시) ---
class Program
{
static void Main()
{
// maxConnections, bufferSize는 서버 성능/메모리에 맞춰 조절하세요.
var server = new SaeaTcpServer(port: 12345, maxConnections: 60000, bufferSize: 2048);
server.Start();
Console.WriteLine("Press Enter to quit...");
Console.ReadLine();
}
}
'Dev > C#' 카테고리의 다른 글
C# interop.Excel 데이터 export (1) | 2024.06.09 |
---|---|
평생 해온게 C#인데 뭘 올려야할쥐.............. (0) | 2023.04.27 |