본문 바로가기

C#

async TCP Server 만들기

C# 으로 TCP Server Async 만들때 예제..


먼저 Async 서버부터 만들어 본다.

using System;

using System.Net;

using System.Net.Sockets;

using System.Threading.Tasks;


namespace TCPSample.Handler

{

public enum ServerTypes { Equipment = 1, ManualCmd, Manager, Etc }

    public class CAsyncServer

    {

        private CLog log = CLog.Instance;

        private TcpListener m_tcpListener;

        private Boolean m_bServerRunning;

        public int Port { get; private set; }

        private ServerTypes serverType = ServerTypes.Equipment;


        public CAsyncServer(ServerTypes stype, int port, IPAddress ip = null)

        {

            serverType = stype;


            Port = port;

            m_tcpListener = new TcpListener(ip ?? IPAddress.Any, port);

            m_tcpListener.Server.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.ReuseAddress, true);

            m_tcpListener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true);


            m_bServerRunning = false;


            //log.WriteLog("Created tcp server for {0} - {1}", serverType, port);

        }


        public bool Start()

        {

            bool bRet = true;

            

            try

            {

                m_bServerRunning = true;

                m_tcpListener.Start();

                //log.WriteLog("Started async tcp server - {0}", Port);


                BeginAcceptClient();

            }

            catch(Exception ex)

            {

                log.WriteLog(ex.Message);

                bRet = false;

            }


            return bRet;

        }


        private void BeginAcceptClient()

        {

            try

            {

                while (GEnv.HotUpdate) { Task.Delay(1000); } // Hot Update 중임


                // Client Begin Accept

                m_tcpListener.BeginAcceptTcpClient(new System.AsyncCallback(OnClientConnect), m_tcpListener);

                log.WriteLog("{0} server is listenning at {1}", serverType, Port);

            }

            catch (Exception ex)

            {

                log.WriteLog(ex.Message);

            }

        }


        private void OnClientConnect(IAsyncResult ar)

        {

            try

            {

                while (GEnv.HotUpdate) { Task.Delay(1000); } // Hot Update 중임


                if (m_bServerRunning)

                {

                    // TCP 네트워크 Client 에서 연결을 수신

                    TcpListener listener = (TcpListener)ar.AsyncState;


                    // 접속한 클라이언트 연결

                    TcpClient client = listener.EndAcceptTcpClient(ar);

                    log.WriteLog("{0} server accepted client({1})", serverType, client.Client.Handle);


                    // Client 추가

                    if (client != null)

                    {

                        CAsyncServerHandler clientHandler;

                        switch (serverType)

                        {

                            case ServerTypes.Equipment:

                                clientHandler = new CDevHandler(client.Client);

                                break;

                            case ServerTypes.ManualCmd:

                                clientHandler = new CBizHandler(client.Client);

                                break;

                            case ServerTypes.Manager:

                                clientHandler = new CMngHandler(client.Client);

                                break;

                            default:

                                clientHandler = new CAsyncServerHandler();

                                clientHandler.ClientSocket = client.Client;

                                break;

                        }

                        clientHandler.Start();


                        BeginAcceptClient();

                    }

                }

            }

            catch (Exception ex)

            {

                // System Debug

                log.WriteLog(ex.Message);

            }

        }

        


        public bool Stop()

        {

            try

            {

                log.WriteLog("Stopped async tcp server - {0}", Port);

                m_bServerRunning = false;

                m_tcpListener.Stop();

            }

            catch(Exception ex)

            {

                log.WriteLog(ex.Message);

            }

            


            return true;

        }

    }

}



이거는 기본 서버로 만들고 메시지 받은거 처리하는 handler 도 하나 만들어 준다.

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace TCPSample.Handler
{
    // base echo server
    public class CAsyncServerHandler
    {
        protected CLog log = CLog.Instance;

        const int MAX_BUF_SIZE = 16384;
        const int DEFAULT_INTERVAL = 60000;

        protected Socket m_socket;
        public Socket ClientSocket
        {
            get { return m_socket; }
            set
            {
                if (m_socket != null) this.Stop();

                m_socket = value;

                m_socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.ReuseAddress, true);
                m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true);

                IPEndPoint ep = m_socket.RemoteEndPoint as IPEndPoint;
                m_clientIP = ep.Address.ToString();
            }
        }

        protected string m_clientIP;
        public string ClientIP => m_clientIP;

        public string ErrorMsg { get; protected set; }

        private byte[] m_buf = new byte[MAX_BUF_SIZE];
        private System.Timers.Timer m_tmOut = new System.Timers.Timer();

        // initHandler() 호출 이후에 활성화 해야함
        protected bool EnableTimeout
        {
            get { return m_tmOut.Enabled; }
            set
            {
                //if (value == true)
                //    m_tmOut.Start();
                //else
                //    m_tmOut.Stop();
                m_tmOut.Enabled = value;
            }
        }
        private double _connTmout = DEFAULT_INTERVAL;
        /// <summary>
        /// 연결된동안 Data 교환이 없이 Socket 유지되는 최대시간. 초과하면 Socket 닫는다.
        /// </summary>
        public double ConnectionTimeout
        {
            get { return _connTmout; }
            set
            {
                _connTmout = (value > 1000) ? value : DEFAULT_INTERVAL;
            }
        }
        /// <summary>
        /// Timeout 시간 확인 주기. 최소 1초(1000) 이상이어야 한다. default = 60000 (1분).
        /// </summary>
        protected double TimeoutCheckInterval
        {
            get { return m_tmOut.Interval; }
            set
            {
                m_tmOut.Interval = (value >= 1000) ? value : DEFAULT_INTERVAL;
            }
        }
        protected DateTime m_LastRecvTime = DateTime.Now;
        protected string myCName;

        public CAsyncServerHandler()
        {
            myCName = this.GetType().Name;

            initHandler();

            m_tmOut.Elapsed += M_tmOut_Elapsed;
        }

        /// <summary>
        /// m_socket 할당후에 호출해야함
        /// </summary>
        private void initHandler()
        {
            if(m_socket != null)
            {
                m_socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.ReuseAddress, true);
                m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true);

                IPEndPoint ep = m_socket.RemoteEndPoint as IPEndPoint;
                m_clientIP = ep.Address.ToString();
            }
            m_tmOut.Interval = (TimeoutCheckInterval >= 1000) ? TimeoutCheckInterval : DEFAULT_INTERVAL;
            m_tmOut.Enabled = false;
        }

        private void M_tmOut_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (DateTime.Now.AddMilliseconds(-_connTmout) > m_LastRecvTime)
            {
                m_tmOut.Stop();
                Stop();
            }
        }

        public virtual void Start()
        {
            // 여기서 override 로 welcome 메시지 던진담에 base 호출하면 될꺼임

            if (IsSocketConnected(m_socket))
                m_socket.BeginReceive(m_buf, 0, m_buf.Length, SocketFlags.None, new AsyncCallback(OnRecv), m_socket);
        }

        protected virtual void OnRecv(IAsyncResult ar)
        {
            // 요기서 override 로 hotupdate 딜레이 시킨담에 base 호출하면 될꺼임

            try
            {
                m_LastRecvTime = DateTime.Now;

                Socket socket = (Socket)ar.AsyncState;

                if (IsSocketConnected(socket))
                {
                    int nRead = socket.EndReceive(ar);
                    if (nRead > 0)
                    {
                        MessageProcess(m_buf, nRead);

                        if (IsSocketConnected(socket))
                            socket.BeginReceive(m_buf, 0, m_buf.Length, SocketFlags.None, new AsyncCallback(OnRecv), socket);
                    }
                }
            }
            catch (Exception ex)
            {
                ErrorMsg = string.Format("{0}.OnRecv Exception : {1}",myCName, ex.Message);
                log.WriteLog(ErrorMsg);
            }
        }

        protected virtual bool MessageProcess(byte[] recvBuf, int recvBytes)
        {
            bool bRet = true;
            try
            {
                string recvStr = Encoding.ASCII.GetString(recvBuf, 0, recvBytes);
                //log.WriteLog("Recv:{0}", recvStr);

                BeginSend("ECHO:" + recvStr);
            }
            catch(Exception ex)
            {
                ErrorMsg = string.Format("{0}.MessageProcess: {1}",myCName, ex.Message);
                log.WriteLog(ErrorMsg);
                bRet = false;
            }
            return bRet;
        }

        public virtual bool BeginSend(string msg)
        {
            bool bRet = true;
            try
            {
                MsgStateObject mso = new MsgStateObject();
                mso.workSocket = m_socket;
                mso.strMessage = msg;
                mso.buffer = Encoding.ASCII.GetBytes(msg);
                mso.bufferSize = mso.buffer.Length;

                //log.WriteLog("Send:{0}", msg);
                BeginSend(mso);
            }
            catch(Exception ex)
            {
                ErrorMsg = string.Format("{0}.BeginSend : {1}", myCName, ex.Message);
                log.WriteLog(ErrorMsg);
                bRet = false;
            }

            return bRet;
        }

        public virtual bool BeginSend(MsgStateObject mso)
        {
            bool bRet = true;
            try
            {
                if (IsSocketConnected(m_socket))
                {
                    m_socket.BeginSend(mso.buffer, 0, mso.bufferSize, SocketFlags.None, new AsyncCallback(OnSend), mso);
                }
                else
                {
                    ErrorMsg = string.Format("{0}.BeginSend : Socket is not connected. fail send message({0})", myCName, mso.strMessage);
                    bRet = false;
                }
            }
            catch(Exception ex)
            {
                ErrorMsg = string.Format("{0}.BeginSend. fail send message because {1}",myCName, ex.Message);
                bRet = false;
            }

            if (!bRet) log.WriteLog(ErrorMsg);

            return bRet;
        }
        protected virtual void OnSend(IAsyncResult ar)
        {
            try
            {
                MsgStateObject mso = (MsgStateObject)ar.AsyncState;
                if (mso.workSocket.EndSend(ar) <= 0)
                {
                    ErrorMsg = string.Format("{0}.OnSend. Socket problem. Fail send message ({0})", myCName, mso.strMessage);
                    log.WriteLog(ErrorMsg);
                }
            }
            catch (SocketException sex)
            {
                ErrorMsg = string.Format("{0}.OnSend. Error:{1}",myCName, sex.Message);
                log.WriteLog(ErrorMsg);
            }
            catch (Exception ex)
            {
                ErrorMsg = string.Format("{0}.OnSend. Error:{1}", myCName, ex.Message);
                log.WriteLog(ErrorMsg);
            }
        }

        public virtual void Stop()
        {
            if (m_socket != null)
            {
                log.WriteLog("{0} socket({1}) stoped", myCName, m_socket.Handle);

                m_socket.Close();
                m_socket.Dispose();
            }
        }

        protected bool IsSocketConnected(Socket socket)
        {
            try
            {
                bool bPart1 = socket.Poll(100, SelectMode.SelectRead);
                bool bPart2 = (socket.Available == 0);
                if (bPart1 && bPart2)
                    return false;
                else
                    return true;
            }
            catch (Exception ex)
            {
                ErrorMsg = string.Format("{0}.IsSocketConnected - {1}",myCName, ex.Message);
                return false;
            }
        }
    }

    public class MsgStateObject
    {
        public Socket workSocket = null;
        public int bufferSize = 0;
        public byte[] buffer = null;
        public string strMessage = string.Empty;
        public object Tag = null;

        public string ObjectID = string.Empty;
        public string CommandID = string.Empty;

        public int ReTryCount = 0;
        public DateTime FirstSendTime = DateTime.Now;
        public bool SendSuccess = false;
    }
}

이거 Handler 만들고 이거 base 로 상속받아서 서버 type에 맞는 handler 들 쭉쭉 만들어서 상황에 맞는 처리기로 사용하면 된다.

아래 예제처럼 상속받은 Handler..

using System;

using System.Linq;

using System.Net.Sockets;


namespace TCPSample.Handler

{

    class CMngHandler : CAsyncServerHandler

    {


        public CMngHandler(Socket clientSocket)

        {

            base.ClientSocket = clientSocket;


            base.TimeoutCheckInterval = GEnv.DefaultConnTimeOut;

            base.ConnectionTimeout = GEnv.DefaultConnTimeOut;

            base.EnableTimeout = true;


            log.WriteLog("Created {0} for client({1})", myCName, clientSocket.Handle);

        }


        protected override bool MessageProcess(byte[] recvBuf, int recvBytes)

        {

            bool bRet = true;

            try

            {

                string recvStr = GEnv.byte2str(recvBuf, 0, recvBytes);

                //log.WriteLog("Recv:" + recvStr);

                string sRes = string.Empty;

                switch (recvStr)

                {

                    case "01": // alived device

                        sRes= string.Join(",", GEnv.Boxlist.Where(c => c.IsConnecting == true).Select(o => o.ObjectID));

                        break;

                    case "02": // dead device

                        sRes= string.Join(",", GEnv.Boxlist.Where(c => c.IsConnecting == false).Select(o => o.ObjectID));

                        break;

                    case "99": // hot update start. 나중에 다 구현해라... 점검필요

                        GEnv.HotUpdate = true;

                        log.WriteLog("Hot Update started");


                        bool bSucc = GEnv.Conf.HotUpdate(GEnv.BaseDir + GEnv.ConfigFileName);


                        if (!bSucc) log.WriteLog("Hot update is failed. {0}", GEnv.Conf.ErrorMsg);

                        else log.WriteLog("Hot update is finished");


                        GEnv.HotUpdate = false;

                        break;

                    default:

                        sRes= "NOCMD";

                        break;

                }


                sRes = string.IsNullOrEmpty(sRes) ? "EMPTY" : sRes;

                //log.WriteLog("Send:" + sRes);

                BeginSend(sRes);

            }

            catch (Exception ex)

            {

                ErrorMsg = string.Format("{0}.MessageProcess: {1}",myCName, ex.Message);

                log.WriteLog(ErrorMsg);

                bRet = false;

            }

            return bRet;

        }

        

    }

}



여기까지하면 이제 메인에서 호출해서 사용하면된다.

메인이든 어디든 암데나...

Handler.CAsyncServer aServer = new Handler.CAsyncServer(sType, port);

aServer.Start();


여기까지하면 서버하나 돌아간다.

'C#' 카테고리의 다른 글

Thread 보다는 Task 가 어떨까.  (0) 2018.11.06
INI 사용하기  (0) 2018.11.01
파일 한줄씩 읽기  (0) 2018.05.17
WPF 바닥에 간단하게 점찍기  (0) 2017.06.16