Dev/C#

999

giftDev 2025. 7. 16. 22:04
반응형

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