■ C#으로 TCP 통신을 하기 위한 클래스입니다. 기존의 예제들을 수정하여 만들었습니다.

    접속 단절이 반복되면서 소용량 데이터를 주고 받는 자동화 프로그래밍 환경에서 안정되게 동작합니다.

 

    8MB이상의 대용량 통신은 아직 테스트되지 않았습니다.

 


Server 측 


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;

enum csConnStatus
{
    Closed,
    Listening,
    Connecting,
    Connected
};

static class TSocket
{
    public static char sSTX() { return Convert.ToChar(0x02); }
    public static char sETX() { return Convert.ToChar(0x03); }
    public static char sEOT() { return Convert.ToChar(0x04); }
    public static char sENQ() { return Convert.ToChar(0x05); }
    public static char sACK() { return Convert.ToChar(0x06); }
    public static char sNAK() { return Convert.ToChar(0x15); }
    public static char sCR() { return Convert.ToChar(13); }
    public static char sLF() { return Convert.ToChar(10); }
    public static string sCRLF() { return "\r\n"; }

    public static string HostName()
    {
        return Dns.GetHostName();
    }

    public static IPAddress[] HostAddresses()
    {
        string hostname = HostName();
        IPAddress[] ips = Dns.GetHostAddresses(hostname);
        return ips;
    }
}

// 데이터 수신 이벤트핸들링 함수 delegate 원형
public delegate void ServerDataArrivalHandler();

class TServer
{
    private const int buffersize = 1024;

    // Server용 Socket 객체
    private TcpListener listener = null;        // Listening중에만존재, 연결되면null
    private TcpClient clientForServer = null;
    private NetworkStream streamServer = null;

    // 현재의 상태
    private csConnStatus serverStatus = csConnStatus.Closed;

    // Server용 수신 thread
    private Thread threadServerRcv = null;
    private Thread threadChkPartnerDeath = null;

    // Server 수신 데이터
    private string serverRcvMessage = "";

    // 수신이벤트를 위한 델리게이트
    private ServerDataArrivalHandler DataArrivalCallback;

    //=========================================================================
    //  Server의 상태전이 명령에 대응 Rule
    //-------------------------------------------------------------------------
    //  1. Closed    -> Closed    : 해도 되고 안해도 되고
    //  2. Closed    -> Listening : 대응
    //  3. Closed    -> Connected : X
    //  4. Listening -> Closed    : 대응
    //  5. Listening -> Listening : X
    //  6. Listening -> Connected : auto
    //  7. Connected -> Closed    : 대응 || auto
    //  8. Connected -> Listening : X
    //  9. Connected -> Connected : X
    //=========================================================================

    //=========================================================================
    //  생성자 1 : 수신데이터 이벤트핸들링 없은 경우.
    //  생성자 2 : 수신데이터 이벤트핸들링 함수를 지정하는 경우
    //=========================================================================
    public TServer()
    {
        DataArrivalCallback = null;
    }
    public TServer(ServerDataArrivalHandler callback)
    {
        DataArrivalCallback = new ServerDataArrivalHandler(callback);
    }

    //=========================================================================
    //  Server : Start
    //=========================================================================
    public void ServerStartListen(string serverIP, int serverPort)
    {
        if (serverStatus == csConnStatus.Listening) return;
        if (serverStatus == csConnStatus.Connected) return;

        // 1단계 : Start
        try
        {
            // server측 접속 IP 객체 얻기
            IPEndPoint serverAddress = new IPEndPoint(IPAddress.Parse(serverIP), serverPort);

            // server 객체 얻기
            listener = new TcpListener(serverAddress);

            // Start
            listener.Start();
        }
        catch (SocketException e)
        {
            Console.WriteLine(e);
            return;
        }

        // 2단계 : Listen 시작
        bool success = ServerBeginListen();

        serverStatus = csConnStatus.Listening;
    }

    //=========================================================================
    //  Server : Listen 대기 [blocking 모드!!!] - Thread아니면 사용안함
    //=========================================================================
    private void ServerListenNU()
    {
        try
        {
            // Listen 대기. Blocking됨 !!!!
            clientForServer = listener.AcceptTcpClient();

            // stream 객체 얻기
            streamServer = clientForServer.GetStream();

            // 수신 thread 시작
            threadServerRcv = new Thread(ServerReceiveThreadMain);
            threadServerRcv.IsBackground = true;
            threadServerRcv.Start();

            // 상대 돌발죽음 감시 thread 시작
            threadChkPartnerDeath = new Thread(ServerCheckPartnerDeathThread);
            threadChkPartnerDeath.IsBackground = true;
            threadChkPartnerDeath.Start();
        }
        catch { }
    }

    //=========================================================================
    //  Server : Listen 시작 [Async 모드]
    //=========================================================================
    private bool ServerBeginListen()
    {
        try  // listening중에 다시 같은 포트로 들어오는 경우 대비
        {
            listener.BeginAcceptTcpClient(new AsyncCallback(DoAcceptTcpClientCallback), listener);
        }
        catch { return false; }

        return true;
    }

    //-------------------------------------------------------------------------
    //  Listen CallBack 함수
    //-------------------------------------------------------------------------
    private void DoAcceptTcpClientCallback(IAsyncResult ar)
    {
        try  // Listen 대기중 Close 했을때의 exception 처리를 위해
        {
            // Get the listener that handles the client request.
            listener = (TcpListener)ar.AsyncState;

            // End the operation
            clientForServer = listener.EndAcceptTcpClient(ar);

            // stream 객체 얻기
            streamServer = clientForServer.GetStream();

            // 수신 thread 시작
            threadServerRcv = new Thread(ServerReceiveThreadMain);
            threadServerRcv.IsBackground = true;
            threadServerRcv.Start();

            // 상대 돌발죽음 감시 thread 시작
            threadChkPartnerDeath = new Thread(ServerCheckPartnerDeathThread);
            threadChkPartnerDeath.IsBackground = true;
            threadChkPartnerDeath.Start();

            serverStatus = csConnStatus.Connected;
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            serverStatus = csConnStatus.Closed;
        }
        finally
        {
            // 연결되건, 최소되건 listener는 필요 없어짐
            listener.Stop();
            listener = null;
        }
    }

    //=========================================================================
    //  Server : Close
    //=========================================================================
    public void ServerClose()
    {
        if (clientForServer != null) clientForServer.Close();
        if (listener != null) listener.Stop();

        while (true)
        {   // listening중에 close되면
            // DoAcceptTcpClientCallback()에서 listener처리할때까지 대기
            if (listener == null) break;
        }

        // 순서 바뀌면 안됨
        if (threadServerRcv != null && threadServerRcv.IsAlive)
        {
            threadServerRcv.Abort();
            threadServerRcv.Join();
        }

        if (threadChkPartnerDeath != null && threadChkPartnerDeath.IsAlive)
        {
            threadChkPartnerDeath.Abort();
            threadChkPartnerDeath.Join();
        }

        serverStatus = csConnStatus.Closed;
    }

    //=========================================================================
    //  Server Status
    //  8가지 경우로 나누었음
    //      listener  clientForServer  clientForServer.Client
    //  (1)   null         null               null             : Closed
    //  (2)   null         null                O               : N/A
    //  (3)   null          O                 null             : Closed(발생안함)
    //  (4)   null          O                  O               : Closed/Connected
    //  (5)    O           null               null             : Listening
    //  (6)    O           null                O               : N/A
    //  (7)    O            O                 null             : Listening(발생안함)
    //  (8)    O            O                  O               : Listening
    //=========================================================================
    public csConnStatus ServerStatus()
    {
        return serverStatus;

        //csConnStatus rtn = csConnStatus.Closed;

        //if (listener == null)
        //{
        //    if (clientForServer == null)
        //        rtn = csConnStatus.Closed;
        //    else
        //    {
        //        if (clientForServer.Client == null)
        //            rtn = csConnStatus.Closed;
        //        else
        //        {
        //            if (clientForServer.Client.Connected == false)
        //                rtn = csConnStatus.Closed;
        //            else
        //                rtn = csConnStatus.Connected;
        //        }
        //    }
        //}
        //else
        //{
        //    if (clientForServer == null)
        //        rtn = csConnStatus.Listening;
        //    else
        //    {
        //        if (clientForServer.Client == null)
        //            rtn = csConnStatus.Listening;
        //        else
        //            rtn = csConnStatus.Listening;
        //    }
        //}

        //return rtn;
    }

    //=========================================================================
    //  Server Send
    //=========================================================================
    public void ServerSend(string st)
    {
        try
        {
            if (streamServer == null || !streamServer.CanWrite) return;
            byte[] msg = Encoding.UTF8.GetBytes(st);
            streamServer.Write(msg, 0, msg.Length);
        }
        catch { }
    }

    //=========================================================================
    //  수신 데이터 꺼내기
    //=========================================================================
    public string GetRcvMsg()
    {
        lock (serverRcvMessage)  //<- thread간 동기화
        {
            string tmp = serverRcvMessage;
            serverRcvMessage = "";
            return tmp;
        }
    }

    //=========================================================================
    //  Server Receive Thread Main
    //=========================================================================
    private void ServerReceiveThreadMain()
    {
        try
        {
            byte[] bytebuff = new byte[buffersize];
            while (true)
            {
                Thread.Sleep(1);

                if (streamServer == null) continue;
                if (!streamServer.CanRead) continue;

                StringBuilder strbuff = new StringBuilder();
                int nbyteRead = 0;

                // 도착 message가 buffer 크기보다 큰 경우를 위해 loop
                do
                {
                    nbyteRead = streamServer.Read(bytebuff, 0, bytebuff.Length);    // blocking
                    if (nbyteRead == 0)
                    {   // 상대방이 close했음을 감지
                        serverStatus = csConnStatus.Closed;
                        ServerClose();                      //<- 없으면 안됨!!
                    }
                    strbuff.AppendFormat("{0}", Encoding.UTF8.GetString(bytebuff, 0, nbyteRead));
                }
                while (streamServer.DataAvailable);

                // 수신버퍼에 복사
                lock (serverRcvMessage)  //<- thread간 동기화
                {
                    serverRcvMessage += strbuff;
                }

                // 데이터 수신 callback 함수 호출
                if (DataArrivalCallback != null) { DataArrivalCallback(); }
            }
        }
        catch { }
    }

    //=========================================================================
    //  Server Check Partner Death Thread
    //=========================================================================
    private void ServerCheckPartnerDeathThread()
    {
        try
        {
            while (true)
            {
                Thread.Sleep(5);

                // 상대방 client가 connected에서 갑자기 꺼져 버리면
                // Closed로 바꾸는 방법이 정기적인 검사밖에 없음.
                // 상황보다 약간 늦게 알게되지만 상관없음.
                if (serverStatus == csConnStatus.Connected)
                {
                    if (clientForServer != null && clientForServer.Client != null)
                    {
                        if (clientForServer.Client.Connected == false)
                            serverStatus = csConnStatus.Closed;
                    }
                }
                if (serverStatus != csConnStatus.Connected) break;
            }
        }
        catch { }
    }
}


Client측 


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;

enum csConnStatus
{
    Closed,
    Listening,
    Connecting,
    Connected
};

static class TSocket
{
    public static char sSTX() { return Convert.ToChar(0x02); }
    public static char sETX() { return Convert.ToChar(0x03); }
    public static char sEOT() { return Convert.ToChar(0x04); }
    public static char sENQ() { return Convert.ToChar(0x05); }
    public static char sACK() { return Convert.ToChar(0x06); }
    public static char sNAK() { return Convert.ToChar(0x15); }
    public static char sCR() { return Convert.ToChar(13); }
    public static char sLF() { return Convert.ToChar(10); }
    public static string sCRLF() { return "\r\n"; }

    public static string HostName()
    {
        return Dns.GetHostName();
    }

    public static IPAddress[] HostAddresses()
    {
        string hostname = HostName();
        IPAddress[] ips = Dns.GetHostAddresses(hostname);
        return ips;
    }
}

// 데이터 수신 이벤트핸들링 함수 delegate 원형
public delegate void ClientDataArrivalHandler();

class TClient
{
    private const int buffersize = 1024;

    // Client용 Socket 객체
    private TcpClient clientForClient = null;
    private NetworkStream streamClient = null;

    // 현재의 상태
    private csConnStatus clientStatus = csConnStatus.Closed;

    // Client용 수신 thread
    private Thread threadClientRcv = null;
    private Thread threadChkPartnerDeath = null;

    // Client 수신 데이터
    private string clientRcvMessage = "";

    // 수신이벤트를 위한 델리게이트
    private ClientDataArrivalHandler DataArrivalCallback;

    //=========================================================================
    //  Client의 상태전이 명령에 대응 Rule
    //-------------------------------------------------------------------------
    //  1. Closed    -> Closed    : 해도 되고 안해도 되고
    //  2. Closed    -> Connected : 대응
    //  3. Connected -> Closed    : 대응 || auto
    //  4. Connected -> Connected : X
    //=========================================================================

    //=========================================================================
    //  생성자 1 : 수신데이터 이벤트핸들링 없은 경우.
    //  생성자 2 : 수신데이터 이벤트핸들링 함수를 지정하는 경우
    //=========================================================================
    public TClient()
    {
        DataArrivalCallback = null;
    }
    public TClient(ClientDataArrivalHandler callback)
    {
        DataArrivalCallback = new ClientDataArrivalHandler(callback);
    }

    //=========================================================================
    //  Client : Connect 시도 [서버측 준비안돼 있으면 exception] - - Thread아니면 사용안함
    //=========================================================================
    private bool ClientConnectNU(string serverIP, int serverPort, string clientIP)
    {
        // server측 접속 IP 객체 얻기
        IPEndPoint serverAddress = new IPEndPoint(IPAddress.Parse(serverIP), serverPort);

        // client측 접속 IP 객체 얻기
        // port = 0은 임의의 할당포트 사용하겠다는 뜻
        int dumport = 0;
        IPEndPoint clientAddress = new IPEndPoint(IPAddress.Parse(clientIP), dumport);

        // client 객체 얻기
        clientForClient = new TcpClient(clientAddress);

        // connect
        try
        {
            clientForClient.Connect(serverAddress);
            streamClient = clientForClient.GetStream(); // stream 객체 얻기

            // 수신 thread 시작
            threadClientRcv = new Thread(ClientReceiveThreadMain);
            threadClientRcv.IsBackground = true;
            threadClientRcv.Start();

            // 상대 돌발죽음 감시 thread 시작
            threadChkPartnerDeath = new Thread(ClientCheckPartnerDeathThread);
            threadChkPartnerDeath.IsBackground = true;
            threadChkPartnerDeath.Start();

            return true;
        }
        catch { return false; }
    }

    //=========================================================================
    //  Client : Connect 시도 [Async 모드] - 반응이 빠른 장점이 있음
    //=========================================================================
    public void ClientBeginConnect(string serverIP, int serverPort, string clientIP)
    {
        if (clientStatus == csConnStatus.Connected) return;
        if (clientStatus == csConnStatus.Connecting) return;

        // server측 접속 IP 객체 얻기
        IPEndPoint serverAddress = new IPEndPoint(IPAddress.Parse(serverIP), serverPort);

        // client측 접속 IP 객체 얻기
        // port = 0은 임의의 할당포트 사용하겠다는 뜻
        int dumport = 0;
        IPEndPoint clientAddress = new IPEndPoint(IPAddress.Parse(clientIP), dumport);

        // client 객체 얻기
        clientForClient = new TcpClient(clientAddress);

        // connected 시작
        IPAddress serverIpAddress = IPAddress.Parse(serverIP);
        clientForClient.BeginConnect(serverIpAddress, serverPort, new AsyncCallback(DoConnectTcpClientCallback), clientForClient);

        clientStatus = csConnStatus.Connecting;
    }

    //-------------------------------------------------------------------------
    //  Connect CallBack 함수
    //-------------------------------------------------------------------------
    private void DoConnectTcpClientCallback(IAsyncResult ar)
    {
        try
        {
            clientForClient = (TcpClient)ar.AsyncState;
            clientForClient.EndConnect(ar);
            streamClient = clientForClient.GetStream(); // stream 객체 얻기

            // 수신 thread 시작
            threadClientRcv = new Thread(ClientReceiveThreadMain);
            threadClientRcv.IsBackground = true;
            threadClientRcv.Start();

            // 상대 돌발죽음 감시 thread 시작
            threadChkPartnerDeath = new Thread(ClientCheckPartnerDeathThread);
            threadChkPartnerDeath.IsBackground = true;
            threadChkPartnerDeath.Start();

            clientStatus = csConnStatus.Connected;
        }
        catch
        {
            clientStatus = csConnStatus.Closed;
        }
    }

    //=========================================================================
    //  Client : Close
    //=========================================================================
    public void ClientClose()
    {
        if (clientForClient != null) clientForClient.Close();
        clientForClient = null;

        // 순서 바뀌면 안됨
        if (threadClientRcv != null && threadClientRcv.IsAlive)
        {
            threadClientRcv.Abort();
            threadClientRcv.Join();
        }

        if (threadChkPartnerDeath != null && threadChkPartnerDeath.IsAlive)
        {
            threadChkPartnerDeath.Abort();
            threadChkPartnerDeath.Join();
        }

        clientStatus = csConnStatus.Closed;
    }

    //=========================================================================
    //  Client Status
    //=========================================================================
    public csConnStatus ClientStatus()
    {
        return clientStatus;

        //if (clientForClient == null || clientForClient.Client == null)
        //    return csConnStatus.Closed;
        //else if (clientForClient.Client.Connected == false)
        //    return csConnStatus.Closed;
        //else
        //    return csConnStatus.Connected;
    }

    //=========================================================================
    //  Client Send
    //=========================================================================
    public void ClientSend(string st)
    {
        try
        {
            if (streamClient == null || !streamClient.CanWrite) return;
            byte[] msg = Encoding.UTF8.GetBytes(st);
            streamClient.Write(msg, 0, msg.Length);
        }
        catch { }
    }

    //=========================================================================
    //  수신 데이터 꺼내기
    //=========================================================================
    public string GetRcvMsg()
    {
        lock (clientRcvMessage)  //<- thread간 동기화
        {
            string tmp = clientRcvMessage;
            clientRcvMessage = "";
            return tmp;
        }
    }

    //=========================================================================
    //  Client Receive Thread Main
    //=========================================================================
    private void ClientReceiveThreadMain()
    {
        try
        {
            byte[] bytebuff = new byte[buffersize];
            while (true)
            {
                Thread.Sleep(1);

                if (streamClient == null) continue;
                if (!streamClient.CanRead) continue;

                StringBuilder strbuff = new StringBuilder();
                int nbyteRead = 0;

                // 도착 message가 buffer 크기보다 큰 경우를 위해 loop
                do
                {
                    nbyteRead = streamClient.Read(bytebuff, 0, bytebuff.Length);    // blocking
                    if (nbyteRead == 0)
                    {   // 상대방이 close했음을 감지
                        clientStatus = csConnStatus.Closed;
                        ClientClose();
                    }
                    strbuff.AppendFormat("{0}", Encoding.UTF8.GetString(bytebuff, 0, nbyteRead));
                }
                while (streamClient.DataAvailable);

                // 수신버퍼에 복사
                lock (clientRcvMessage)  //<- thread간 동기화
                {
                    clientRcvMessage += strbuff;
                }

                // 데이터 수신 callback 함수 호출
                if (DataArrivalCallback != null) { DataArrivalCallback(); }
            }
        }
        catch
        { }
    }

    //=========================================================================
    //  Client Check Partner Death Thread
    //=========================================================================
    private void ClientCheckPartnerDeathThread()
    {
        try
        {
            while (true)
            {
                Thread.Sleep(5);

                // 상대방이 connected에서 갑자기 꺼져 버리면
                // Closed로 바꾸는 방법이 정기적인 검사밖에 없음.
                // 상황보다 약간 늦게 알게되지만 상관없음.
                if (clientStatus == csConnStatus.Connected)
                {
                    if (clientForClient != null && clientForClient.Client != null)
                    {
                        if (clientForClient.Client.Connected == false)
                            clientStatus = csConnStatus.Closed;
                    }
                }
                if (clientStatus != csConnStatus.Connected) break;
            }
        }
        catch { }
    }
}

Posted by 마스샘