저번회에 간단하게 IOCP에 대해서 알아 보았다.

저번회를 바탕으로 이번회엔 IOCP Echo Server를 만들것다. 클라이언트로 부터 들어온 메시지를 해당 클라이언트로 되돌려 주는 기능을 하는 아주 단순한 서버이다.

하지만, 근래에 들어서 느끼는 점이 하나 있다. "단순한 놈일수록 견고하게 만들어라." 이말에 공감하시는 분들도 꽤 될거라 생각한다.

각설하고, 우선 소스를 먼저 보자.

#include
#include
#include

#define BUFSIZE 512

// 소켓 정보 저장을 위한 구조체
struct SOCKETINFO
{
OVERLAPPED overlapped;
SOCKET sock;
char buf[BUFSIZE+1];
int recvbytes;
int sendbytes;
WSABUF wsabuf;
};

// 소켓 입출력 함수
DWORD WINAPI WorkerThread(LPVOID arg);
// 오류 출력 함수
void err_quit(char *msg);
void err_display(char *msg);

int main(int argc, char* argv[])
{
int retval;

// 윈속 초기화
WSADATA wsa;
if(WSAStartup(MAKEWORD(2,2), &wsa) != 0)
return -1;

// 입출력 완료 포트 생성
HANDLE hcp = CreateIoCompletionPort(
INVALID_HANDLE_VALUE, NULL, 0, 0);
if(hcp == NULL) return -1;

// CPU 개수 확인
SYSTEM_INFO si;
GetSystemInfo(&si);

// (CPU 개수 * 2)개의 작업자 스레드 생성
HANDLE hThread;
DWORD ThreadId;
for(int i=0; i<(int)si.dwNumberOfProcessors*2; i++){
hThread = CreateThread(NULL, 0, WorkerThread, hcp, 0, &ThreadId);
if(hThread == NULL) return -1;
CloseHandle(hThread);
}

// socket()
SOCKET listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if(listen_sock == INVALID_SOCKET) err_quit("socket()");

// bind()
SOCKADDR_IN serveraddr;
ZeroMemory(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(9000);
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
retval = bind(listen_sock, (SOCKADDR *)&serveraddr, sizeof(serveraddr));
if(retval == SOCKET_ERROR) err_quit("bind()");

// listen()
retval = listen(listen_sock, SOMAXCONN);
if(retval == SOCKET_ERROR) err_quit("listen()");

while(1){
// accept()
SOCKADDR_IN clientaddr;
int addrlen = sizeof(clientaddr);
SOCKET client_sock = accept(listen_sock, (SOCKADDR *)&clientaddr, &addrlen);
if(client_sock == INVALID_SOCKET){
err_display("accept()");
continue;
}
printf("[TCP 서버] 클라이언트 접속: IP 주소=%s, 포트 번호=%d\\n",
inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));

// 소켓과 입출력 완료 포트 연결
HANDLE hResult = CreateIoCompletionPort((HANDLE)client_sock, hcp,
(DWORD)client_sock, 0);
if(hResult == NULL) return -1;

// 소켓 정보 구조체 할당
SOCKETINFO *ptr = new SOCKETINFO;
if(ptr == NULL){
printf("[오류] 메모리가 부족합니다!\\n");
break;
}
ZeroMemory(&(ptr->overlapped), sizeof(ptr->overlapped));
ptr->sock = client_sock;
ptr->recvbytes = 0;
ptr->sendbytes = 0;
ptr->wsabuf.buf = ptr->buf;
ptr->wsabuf.len = BUFSIZE;

// 비동기 입출력 시작
DWORD recvbytes;
DWORD flags = 0;
retval = WSARecv(client_sock, &(ptr->wsabuf), 1, &recvbytes,
&flags, &(ptr->overlapped), NULL);
if(retval == SOCKET_ERROR){
if(WSAGetLastError() != ERROR_IO_PENDING){
err_display("WSARecv()");
}
continue;
}
}

// 윈속 종료
WSACleanup();
return 0;
}

DWORD WINAPI WorkerThread(LPVOID arg)
{
HANDLE hcp = (HANDLE)arg;
int retval;

while(1){
// 비동기 입출력 완료 기다리기
DWORD cbTransferred;
SOCKET client_sock;
SOCKETINFO *ptr;
retval = GetQueuedCompletionStatus(hcp, &cbTransferred,
(LPDWORD)&client_sock, (LPOVERLAPPED *)&ptr, INFINITE);

// 클라이언트 정보 얻기
SOCKADDR_IN clientaddr;
int addrlen = sizeof(clientaddr);
getpeername(ptr->sock, (SOCKADDR *)&clientaddr, &addrlen);

// 비동기 입출력 결과 확인
if(retval == 0 || cbTransferred == 0){
if(retval == 0){
DWORD temp1, temp2;
WSAGetOverlappedResult(ptr->sock, &(ptr->overlapped),
&temp1, FALSE, &temp2);
err_display("WSAGetOverlappedResult()");
}
closesocket(ptr->sock);
printf("[TCP 서버] 클라이언트 종료: IP 주소=%s, 포트 번호=%d\\n",
inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
delete ptr;
continue;
}

// 데이터 전송량 갱신
if(ptr->recvbytes == 0){
ptr->recvbytes = cbTransferred;
ptr->sendbytes = 0;
// 받은 데이터 출력
ptr->buf[ptr->recvbytes] = '\\0';
printf("[TCP/%s:%d] %s\\n", inet_ntoa(clientaddr.sin_addr),
ntohs(clientaddr.sin_port), ptr->buf);
}
else{
ptr->sendbytes += cbTransferred;
}

if(ptr->recvbytes > ptr->sendbytes){
// 데이터 보내기
ZeroMemory(&(ptr->overlapped), sizeof(ptr->overlapped));
ptr->wsabuf.buf = ptr->buf + ptr->sendbytes;
ptr->wsabuf.len = ptr->recvbytes -ptr->sendbytes;

DWORD sendbytes;
retval = WSASend(ptr->sock, &(ptr->wsabuf), 1,
&sendbytes, 0, &(ptr->overlapped), NULL);
if(retval == SOCKET_ERROR){
if(WSAGetLastError() != WSA_IO_PENDING){
err_display("WSASend()");
}
continue;
}
}
else{
ptr->recvbytes = 0;

// 데이터 받기
ZeroMemory(&(ptr->overlapped), sizeof(ptr->overlapped));
ptr->wsabuf.buf = ptr->buf;
ptr->wsabuf.len = BUFSIZE;

DWORD recvbytes;
DWORD flags = 0;
retval = WSARecv(ptr->sock, &(ptr->wsabuf), 1,
&recvbytes, &flags, &(ptr->overlapped), NULL);
if(retval == SOCKET_ERROR){
if(WSAGetLastError() != WSA_IO_PENDING){
err_display("WSARecv()");
}
continue;
}
}
}

return 0;
}

// 소켓 함수 오류 출력 후 종료
void err_quit(char *msg)
{
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER|
FORMAT_MESSAGE_FROM_SYSTEM,
NULL, WSAGetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpMsgBuf, 0, NULL);
MessageBox(NULL, (LPCTSTR)lpMsgBuf, msg, MB_ICONERROR);
LocalFree(lpMsgBuf);
exit(-1);
}

// 소켓 함수 오류 출력
void err_display(char *msg)
{
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER|
FORMAT_MESSAGE_FROM_SYSTEM,
NULL, WSAGetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpMsgBuf, 0, NULL);
printf("[%s] %s", msg, (LPCTSTR)lpMsgBuf);
LocalFree(lpMsgBuf);

'공부 해 Boa요. > Network' 카테고리의 다른 글

Raw Socket  (0) 2006.09.29
Consol Echo Client  (0) 2006.09.27
'실전 네트웍 프로그래밍 -3  (0) 2005.06.29
'실전 네트웍 프로그래밍 - 2  (0) 2005.06.29
실전 네트웍 프로그래밍 - 1  (0) 2005.06.29

+ Recent posts