새소식

Programming

C++ 윈도우 소켓 - 비동기 에코 서버

  • -
소스만 바로 받아서 보는게 속편하겠죠 ㅎㅎ



 음.. 비쥬얼스튜디오 2005에서 컴파일 했을때 워닝 하나 없이 잘돌아간 소스입니다.  주석에도 있듯이 열혈강의 TCP/IP 예제를 클레스화 한것이고요. WSAEventSelect 모델 기반의 소켓 서버입니다. 쉽게 풀어 쓰면 소켓의 상태의 변화가 있을때 WSAEventSelect 이란 놈(API)가 알아채서 그상황에 맞는 행동을하는것입니다. 멀티 쓰레드 서버 인데 처리 방식이 비동기 식 이라고 생각하면 편할듯 하네요. 

 비동기! 비동기? 라고 계속 말하는데 "그게 뭐야?" 라는 의문을 가지는 분께 누구나 알아 먹는 설명! 간단 명료한 행기표 설명 따라 들어갑니다. ㅋㅋㅋ 

 저녁 준비하던 엄마가 콩나물 500원 어치를 사오라는  심부름일 시켰습니다. 행기는 동내 슈퍼로 콩나물을 사러 갑니다. 여기서 집중!!

1. 엄마는 콩나물이 올때까지 아무일도하지 않고 행기를 기다린다.
2. 엄마는 콩나물이 올때까지 다른일을 하고 행기가 돌아온뒤 콩나물 요리를 한다.

1번은 동기식입니다. 일이 발생했을때 그일이 다처리된후 나머지 일들을 처리 하는 방법이지요.
2번은 비동기식입니다. 일이 발생했을때 그일이 처리 될때까지 나머일을 처리하며 방법 입니다.

말로만 들으면 비동기가 무조건 좋다! 라고 생각할수 있지만 상황에 따라 잘 골라써야겠죠. 

 소스 설명을 좀더 붙이 면 "//루프" 라고 주석 처리된부분에서 계속 반복문을 돌면서 소켓의 상태를 살펴서 이놈이 지금  처음 접속인지, 데이터를 보내오는지, 접속을 종류하는지를 살피고 그에따른 if문을 찾아 들어가는거지요. while문 안쪽의 for문이 쓰레드가 되는 부분이겠죠. 

소켓의 구조(단계?) 를 알고 구조만 살피면 쉽게 이해되는 소스입니다.

아참!! 레포트로 배껴 가지마세요.. 책에 있는 내용이라서 배껴쓰면 교수님이 금방 알아 보실거라는.. 그리고 쓰레드는 60개 정도가 한개일것 같네요.



//////////////////////////////////////////////////////////////////////////////////////////////////
// 
// 비동기 소켓 서버
// 열혈강의 TCP/IP 20장의 예제를 클레스화
// 일단 에코 서버이며. 167 번째 줄에서 서버에서 해줄 코드를 입력하면 될듯..
//

#define BUFSIZE 100 // 받아올 데이터 최대 크기
#define PORT 3000 // 포트번호 할당

// 해더파일 선언
#include <winsock2.h>
#include <iostream>

using namespace std;

// ws2_32.lib 링크
#pragma comment(lib, "ws2_32.lib")

class socketServer
{
private:
// 변수 선언
WSADATA wsaData;
SOCKET hServSock;
SOCKADDR_IN servAddr;

    //소켓 핸들배열 - 연결 요청이 들어올 때마다 생성되는 소켓의 핸들을 이 배열에 저장.     SOCKET hSockArray[WSA_MAXIMUM_WAIT_EVENTS];
 
SOCKET hClntSock;
SOCKADDR_IN clntAddr;

WSAEVENT hEventArray[WSA_MAXIMUM_WAIT_EVENTS]; // 이벤트 배열 
WSAEVENT newEvent;
WSANETWORKEVENTS netEvents;

int clntLen;
int sockTotal;
int index, i;  
char message[BUFSIZE];
int strLen;

void CompressSockets(SOCKET* hSockArray, int omitIndex, int total);
void CompressEvents(WSAEVENT* hEventArray, int omitIndex, int total);
void ErrorHandling(char *message);

public:
socketServer();
int StartServer();
};

socketServer::socketServer()
{
sockTotal=0;
}

int socketServer::StartServer()
{

// 윈속 초기화 (성공시 0, 실패시 에러 코드리턴)
if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0){
ErrorHandling("WSAStartup() error!");
}

// 소켓 생성 (성공시 핸들을, 실패시 "INVALID_SOCKET" 반환)
hServSock = socket(PF_INET, SOCK_STREAM, 0);
// 소켓 생성 실패 처리
if(hServSock==INVALID_SOCKET){
ErrorHandling("socket() error");
}


// 소켓 통신을 위한 기본 정보 
servAddr.sin_family = AF_INET;
servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
servAddr.sin_port = htons(PORT);


// 주소와 Port 할당 (바인드!!)
if(bind(hServSock, (struct sockaddr *) &servAddr, sizeof(servAddr))==SOCKET_ERROR){
ErrorHandling("bind() error");
}


// 이벤트 발생을 확인 (성공시 0, 실패시 "SOCKET_ERROR" 반환)
newEvent = WSACreateEvent();
if(WSAEventSelect(hServSock, newEvent, FD_ACCEPT)==SOCKET_ERROR){
ErrorHandling("WSAEventSelect() error");
}


// 연결 대기 요청 상태로의 진입 (신호가 들어올때까지 대기)
if(listen(hServSock, 5)==SOCKET_ERROR){
ErrorHandling("listen() error");
}
  

// 서버 소켓 핸들 정보
hSockArray[sockTotal]=hServSock;

// 이벤트 오브젝트 핸들 정보
hEventArray[sockTotal]=newEvent;

// 전체 소켓수
sockTotal++;

  

// 루프
while(1)
{
// 이벤트 종류 구분하기(WSAWaitForMultipleEvents)
index = WSAWaitForMultipleEvents(sockTotal, hEventArray, FALSE, 
WSA_INFINITE, FALSE);
index = index-WSA_WAIT_EVENT_0;

for(i=index; i<sockTotal; i++)
{
index = WSAWaitForMultipleEvents(1, &hEventArray[i], TRUE, 0, FALSE);
if((index==WSA_WAIT_FAILED || index==WSA_WAIT_TIMEOUT)) continue;
else
{
index = i;
WSAEnumNetworkEvents(hSockArray[index],
 hEventArray[index], &netEvents);

// 초기 연결 요청의 경우.
if(netEvents.lNetworkEvents & FD_ACCEPT) 
{
if(netEvents.iErrorCode[FD_ACCEPT_BIT] != 0)
{
puts("Accept Error");
break;
}

clntLen = sizeof(clntAddr);
// 연결을 수락 
    // (accept | 성공시 소켓핸들 실패시 "INVALID_SOCKET" 반환)
hClntSock = accept(hSockArray[index],
(SOCKADDR*)&clntAddr, &clntLen);

// 이벤트 커널 오브젝트 생성(WSACreateEvent)
newEvent=WSACreateEvent();

// 이벤트 발생 유무 확인(WSAEventSelect)
WSAEventSelect(hClntSock, newEvent, FD_READ|FD_CLOSE);

hEventArray[sockTotal]=newEvent;
hSockArray[sockTotal]=hClntSock;
sockTotal++;
printf("새로 연결된 소켓의 핸들 %d \n", hClntSock);
}

// 데이터 전송해올 경우.
if(netEvents.lNetworkEvents & FD_READ) 
{
if(netEvents.iErrorCode[FD_READ_BIT] != 0)
{
puts("Read Error");
break;
}

//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
//
// 서버 작업은 여기서 다하겠지..
//

// 데이터를 받음 (message에 받은 데이터를 담음)
strLen=recv(hSockArray[index-WSA_WAIT_EVENT_0],
message, sizeof(message), 0);
// 데이터 끝에 널값 입력
message[strLen]=0;
// 에코(데이터를준 클라이언트에 다시 데이터쏘기)
send(hSockArray[index-WSA_WAIT_EVENT_0], 
message, strLen,0);
// 예의상 화면에도 한번 보여줘야지
cout<<message;

//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////
}


// 연결 종료 요청의 경우.
if(netEvents.lNetworkEvents & FD_CLOSE)
{
if(netEvents.iErrorCode[FD_CLOSE_BIT] != 0)
{
puts("Close Error");
break;
}
WSACloseEvent(hEventArray[index]);
// 소켓 종류
closesocket(hSockArray[index]);
printf("종료 된 소켓의 핸들 %d \n", hSockArray[index]);

sockTotal--;
// 배열 정리.
CompressSockets(hSockArray, index, sockTotal);
CompressEvents(hEventArray, index, sockTotal);
}

}//else
}//for
}//while

  
  
// 할당 받은 리소스 반환.
WSACleanup();
return 0;
}

/************************************
/* 
/* CompressSockets
/*
*/
void socketServer::CompressSockets(SOCKET* hSockArray, int omitIndex, int total)
{
int i;
for(i=omitIndex; i<total; i++)
{
hSockArray[i]=hSockArray[i+1];
}
}

/************************************
/* 
/* CompressEvents
/*
*/
void socketServer::CompressEvents(WSAEVENT* hEventArray, int omitIndex, int total)
{
int i;

for(i=omitIndex; i<total; i++)
{
hEventArray[i]=hEventArray[i+1];
}
}

/************************************
/* 
/* ErrorHandling
/*
*/
void socketServer::ErrorHandling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}




// 한번 실행해보는 메인함수!!
/************************************
/* 
/* main
/*
*/
int main(){

// 서버 인스턴트
socketServer server;
// 서버시작
server.StartServer();
}
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.