본문 바로가기
Server | Network

EPOLL (EPOLLIN | EPOLLOUT | EPOLLET |EPOLLONESHOT) 고성능 소켓서버 실무

by 두루물 2011. 2. 12.


서버구현


저번에 예고한(?) 대로 대충,EPOLL로 윈도우즈의 IOCP 와 거의 비슷한 구조로 이벤트 기반 소켓을 구현하였으며,
여기에 DB POOL,또 클라이언트 요청업무(비즈니스로직)를 담당할 Worker Thread Pool,패킷 구분을 위한 Tokenizer,MYSQL을 이용하기 위한 인터페이스 Class 인 MySQLDB(윈도우즈에서 메신저 서버에서 사용하던 것을 뮤텍스 추가하여 코드 재활용),트레이스 로그,conf 환경설정 읽기,EPOLL은 IN,OUT(Incomming,Outgoing) 데이타를 모두 이벤트로 처리하였으며,
Outgoing시에는 단일버퍼 큐에 모두 쌓아놓고 무조건 꺼내서 읽는것이 아니라,패킷 구조상 끊어서 보내야 하기 때문에,
좀더 구조적으로 하기위해 PostSend()에서 각 큐에 해당 명령패킷들을 (송신용 IOBUFF_CONTEXT 의 리스트) FIFO 처리하였다. 물론,막 보내고 클라이언트에서 Recv 시 알아서 패킷을 끊어서 처리하면 되지만,좀더 일목요연하게 하기위함이다.

IOCP는 해당 IO버퍼에 데이타와 길이를 담아주면 알아서 전송하였지만(send /recv 함수를 쓰지않는다),EPOLL은 내가 정의한 버퍼(위에서 말한 전송용 컨텍스트의 큐) 에 데이타를 버퍼링 해놓고 EPOLLOUT 이벤트를 적절한 보내는 시점에 생성해서  EPOLLOUT 이벤트가 epoll_wait에서 떨어지도록 유도하고 그 시점에 또 그 버퍼를 꺼내어서 내가 직접 send 하도록 해줘야 하는점이 다르다고 볼수있다.

또한,쓰레드 풀은 조건변수와 뮤텍스를 공용으로 사용하게 하여 노는 쓰레드를 깨워 Context Switching 이 일어나는 부분에서 임계영역에 대해 단일작업을 보장 하도록 하여야 하며, DB 풀 같은 경우는 각 MySQL 커넥션 별로 각자의 임계영역을 따로 두어 작업을 독립시켜줘야 한다.

void CDBPool::Lock(int i)//blocking until enable lock
{
#ifdef _WINDOWS
 EnterCriticalSection(&m_cs[i]);
#else
 pthread_mutex_lock(&m_mut[i]);
#endif
}

void CDBPool::UnLock(int i)
{
#ifdef _WINDOWS
 LeaveCriticalSection(&m_cs[i]);
#else
 pthread_mutex_unlock(&m_mut[i]);
#endif
}

bool CDBPool::TryLock(int i)//non-blocking check & lock,enable lock return !0,otherwise other use it,return 0
{
#ifdef _WINDOWS
 return (TryEnterCriticalSection(&m_cs[i]) != 0);
#else
 int rc = pthread_mutex_trylock(&m_mut[i]);
 return (rc == 0) ? true : false;//rc == 0 lock now,otherwise,rc == EBUSY || rc == EINVAL
#endif
}

한가지 팁,MySQL 은 쓰레드 safe 하게 빌드해야 하며,(-lmysqlclient_r 로 한다)
 if(!mysql_thread_safe()){
  tracelog(0,"Warning,the mysql client is not compiled as thread-safe.use -lmysqlclient_r\n");
 }
 else
  tracelog(0,"mysql Thread Safe\n");

기본적으로 연결시간이 지나면,자동으로 끊기는데 ,이를 프로그래밍으로 보장하려면,연결상태를 주기적을 체크하는 Ping 과 재사용을 등록해줘야 한다.
 /*
 * krkim 2010.06.15 for Error 2006 (MySQL server has gone away)
 * connect 이후에 호출해야 함,mysql_ping을 호출해야함
 *
 */
 my_bool reconnect = 1;
 mysql_options(&mysql, MYSQL_OPT_RECONNECT, &reconnect);

//connect를 한번도 안하고 호출하면 runtime error
bool CMySQLDB::IsConnected()
{
 int ok = 0;
 if(&mysql){
  ok = 1;
 }
 else
  return false;
 if(mysql.host == NULL)//bugfix 2010.06.11 krkim
  return false;
 if(Ping() != 0)
 {
  return false;
 }
 return true;
}

여튼,서버엔진의 틀이라 할수있는 기본 프레임 만들었고 한창 테스트 중이다. 여기에 비즈니스 로직만 어떤걸 더 붙이느냐에 따라,게임서버가 될것이고 웹하드 서버가 될것이고 코드재활용 및 응용범위는 다양하고 무궁무진 하다고 볼수 있다.
프로토콜은 메신저 프로토콜 기반과 비슷한 TEXT 포맷 명령 형태로 구성될 것이고 바이너리 데이타나 가변데이타는
PAYLOAD 서브명령집합으로 처리될 것이다.

소스목록
krkim@baikal:~/dev/server/src$
ls
clientsock.cpp  define.h    iofunc.h     liblog.h     network.cpp  relayserver.cpp  thread.cpp
clientsock.h    iocp.cpp    libconf.cpp  makefile     network.h    serverconfig.h   thread.h
dbpool.cpp      iocp.h      libconf.h    mysqldb.cpp  relayd       serversock.cpp   tokenizer.cpp
dbpool.h        iofunc.cpp  liblog.cpp   mysqldb.h    relayd.conf  serversock.h     tokenizer.h

서버구동 및 로깅
krkim@baikal:~/dev/server/src$ ./relayd
krkim@baikal:~/dev/server/src$
----------------------------------------------------------------
TeraSpeed WebHard RELAY Server Daemon v2.0 2010 64bit
BuildTime:Feb 12 2011 04:23:48 hostname[baikal]
Copyright(c) krkim (http://krkim.net)
----------------------------------------------------------------
To start: relayd <port number> ,To end  : type "kill 3234".
  If not killed,To end force,retry type "kill -9 3234".
base dir = /home/krkim/dev/server/src
conf file = /home/krkim/dev/server/src/relayd.conf
logpath = [.]
loglevel = [3]
[04:23:59][2677f720] trace start....
[04:23:59][2677f720] SetMaxClient: Request MaxClient(500),system limit max fd(1024)
[04:23:59][2677f720] Init:listenport=3009 maxclient=500 m_epolltimeout=5 maxthreadpool=5
[04:23:59][2677f720] InitDBPool:maxdbpool=10 host=192.168.0.2 dbname=webhard user=xxxx pass=xxxx port=3306
[04:23:59][2677f720] mysql Thread Safe
[04:23:59][2677f720] mysql Thread Safe
[04:23:59][2677f720] mysql Thread Safe
[04:23:59][2677f720] mysql Thread Safe
[04:23:59][2677f720] mysql Thread Safe
[04:23:59][2677f720] mysql Thread Safe
[04:23:59][2677f720] mysql Thread Safe
[04:23:59][2677f720] mysql Thread Safe
[04:23:59][2677f720] mysql Thread Safe
[04:23:59][2677f720] mysql Thread Safe
[04:23:59][2677f720] init poolsize=10
[04:23:59][2677f720] try connect db
[04:23:59][2677f720] dbpool:m_poolsize=10 Connect(192.168.0.2,xxxx,xxxx,webhard,3306)
[04:23:59][2677f720] Database Initialation ...  [ OK ]
[04:23:59][2677f720] InitDBPool:ConnectDB ok!
[04:23:59][2677f720] server started..
[04:24:18][2239e710] Accept OK:client sock(15)
[04:24:18][2239e710] client ip=[192.168.0.1]
[04:24:18][24db0710] client EPOLLOUT status detected.available to send now.
[04:24:18][24db0710] client send buffer is flush,so modify_epollfd to recv at nexttime.
[04:24:23][243a2710] client EPOLLIN status detected.available to recv incoming data now.
[04:24:23][243a2710] OnRecvPacket: Len = 14
[04:24:23][243a2710] cmd=[VER]
[04:24:23][23ba1710] client EPOLLOUT status detected.available to send now.
[04:24:23][23ba1710] OnSendPacket: ToSendLen=15 SentLen=15 pbuff=[VER 123 DRMP1]
[04:24:23][23ba1710] OnSendPacket: Send Completed[0],SentLen=15 pbuff=[VER 123 DRMP1]
[04:24:23][23ba1710] client send buffer is flush,so modify_epollfd to recv at nexttime.
[04:24:34][233a0710] client EPOLLIN status detected.available to recv incoming data now.
[04:24:34][233a0710] OnRecvPacket: Len = 26
[04:24:34][233a0710] cmd=[VER]
[04:24:34][22b9f710] client EPOLLOUT status detected.available to send now.
[04:24:34][22b9f710] OnSendPacket: ToSendLen=15 SentLen=15 pbuff=[VER 333 DRMP1]
[04:24:34][22b9f710] OnSendPacket: Send Completed[0],SentLen=15 pbuff=[VER 333 DRMP1]
[04:24:34][22b9f710] client send buffer is flush,so modify_epollfd to recv at nexttime.
krkim@baikal:~/dev/server/src$
[04:26:23][2239e710] Accept OK:client sock(16)
[04:26:23][2239e710] client ip=[192.168.0.1]
[04:26:23][24db0710] client EPOLLOUT status detected.available to send now.
[04:26:23][24db0710] client send buffer is flush,so modify_epollfd to recv at nexttime.
[04:26:28][243a2710] client EPOLLIN status detected.available to recv incoming data now.
[04:26:28][243a2710] OnRecvPacket: Len = 14
[04:26:28][243a2710] cmd=[VER]
[04:26:28][23ba1710] client EPOLLOUT status detected.available to send now.
[04:26:28][23ba1710] OnSendPacket: ToSendLen=15 SentLen=15 pbuff=[VER 123 DRMP1]
[04:26:28][23ba1710] OnSendPacket: Send Completed[0],SentLen=15 pbuff=[VER 123 DRMP1]
[04:26:28][23ba1710] client send buffer is flush,so modify_epollfd to recv at nexttime.
[04:26:35][233a0710] client EPOLLIN status detected.available to recv incoming data now.
[04:26:35][233a0710] OnRecvPacket: Len = 15
[04:26:35][233a0710] cmd=[VER]
[04:26:35][22b9f710] client EPOLLOUT status detected.available to send now.
[04:26:35][22b9f710] OnSendPacket: ToSendLen=15 SentLen=15 pbuff=[VER 444 DRMP1]
[04:26:35][22b9f710] OnSendPacket: Send Completed[0],SentLen=15 pbuff=[VER 444 DRMP1]
[04:26:35][22b9f710] client send buffer is flush,so modify_epollfd to recv at nexttime.
[04:26:57][24db0710] freecontext:Delete Recv Context
[04:26:57][24db0710] freecontext:Delete Send Context [0]
[04:26:57][24db0710] error,epoll hangup detected.so call del_epollfd,client fd = 15

각자,자신의 노하우를 살려 안정적인 고성능의 서버를 개발해 보도록 하자.