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;
}
}
}
여기까지하면 이제 메인에서 호출해서 사용하면된다.
메인이든 어디든 암데나...
여기까지하면 서버하나 돌아간다.