티스토리 뷰

  • WSAEventSelect 함수가 핵심
  • 소켓과 관련된 네트워크 이벤트를 **[이벤트 객체]**를 통해 감지
  • 이벤트 객체 관련 함수들
    • 생성 : WSACreateEvent (수동 리셋 Manual-Reset + Non-Signaled 상태 시작)
    • 삭제 : WSACloseEvent
    • 신호 상태 감지 : WSAWaitForMultipleEvents
      • count, event
      • waitAll : 모두 기다림? 하나만 완료되어도 OK?
      • timeout : 타임 아웃
      • 지금은 False
      • return : 완료된 첫번째 인덱스
    • 구체적인 네트워크 이벤트 알아내기 : WSAEnumNetworkEvents
      •  socket
      • eventObject : socket과 연동된 이벤트 객체 핸들을 넘겨주면, 이벤트 객체를 non-signaled
      • networkEvent : 네트워크 이벤트 / 오류 정보가 저장
  • 소켓 ↔ 이벤트 객체 연동 함수들
    • **WSAEventSelect(socket, event, networkEvents);**
    • 관심있는 네트워크 이벤트 확인
      1. FD_ACCEPT : 접속한 클라가 있음 accept
      2. FD_READ : 데이터 수신 가능 recv, recvfrom
      3. FD_WRITE : 데이터 송신 가능 send, sendto
      4. FD_CLOSE : 상대가 접속 종료
      5. FD_CONNECT : 통신을 위한 연결 절차 완료
      6. FD_00B

※ 주의사항

  • WSAEventSelect 함수를 호출하면, 해당 소켓은 자동으로 논블로킹 모드 전환
  • accept() 함수가 리턴하는 소켓은 listenSocket과 동일한 속성을 갖는다.
  • 따라서 clientSocket은 FD_READ, FD_WRITE 등을 다시 등록 필요
  • 드물게 WSAEWOULDBLOCK 오류가 뜰 수 있으니 예외 처리 필요
  • 중요)
    • 이벤트 발생 시, 적절한 소켓 함수 호출해야 함
    • 아니면 다음번에는 동일 네트워크 이벤트 발생 X

//Server Code 
#pragma warning(disable:4996)
#pragma comment(lib, "ws2_32.lib")
#include<stdio.h>
#include<Winsock2.h>
#include<stdlib.h>

#define PORT	9000
#define BUFSIZE 512

//소켓 정보 저장을 위한 구조체와 변수
struct SOCKETINFO {
    SOCKET sock;
    char buf[BUFSIZE + 1];
    int recvBytes;
    int sendBytes;
};

int nTotalSockets = 0;
SOCKETINFO *SocketInfoArray[WSA_MAXIMUM_WAIT_EVENTS];
WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];

//소켓 관리 함수
BOOL AddSocketInfo(SOCKET sock);
void RemoveSocketInfo(int nIndex);

//오류 출력 함수
void err_quit(const char* msg);
void err_display(int errcode);

int main()
{
    int retVal;

    WSADATA wsaData;
    SOCKET listen_s;
    SOCKADDR_IN sock_addr;

    //윈속 초기화
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
        err_quit("WSAStartup");

    //소켓 생성
    listen_s = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_s == INVALID_SOCKET)
        err_quit("socket");

    ZeroMemory(&sock_addr, sizeof(struct sockaddr_in));
    sock_addr.sin_family = PF_INET;
    sock_addr.sin_port = htons(PORT);
    sock_addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

    if (bind(listen_s, (struct sockaddr*)&sock_addr, sizeof(struct sockaddr_in)) == SOCKET_ERROR) err_quit("Bind");
    if (listen(listen_s, SOMAXCONN) == SOCKET_ERROR) err_quit("listen");

    //소켓 정보 추가 & WSAEventSelect()
    AddSocketInfo(listen_s);
    retVal = WSAEventSelect(listen_s, EventArray[nTotalSockets - 1], FD_ACCEPT | FD_CLOSE);
    if (retVal == SOCKET_ERROR) err_quit("WSAEventSelect()");

    WSANETWORKEVENTS NetworkEvents;
    SOCKET clientsock;
    SOCKADDR_IN clientaddr;
    int addrlen, i;

    while (1) {
        //이벤트 객체 관찰하기
        i = WSAWaitForMultipleEvents(nTotalSockets, EventArray, FALSE, WSA_INFINITE, FALSE);
        if (i == WSA_WAIT_FAILED) continue;
        //여기서 리턴 하는 값은 인덱스 + WSA_WAIT_EVENT_0 의 값이므로 WAS값을 빼줘야 한다.
        i -= WSA_WAIT_EVENT_0;

        //구체적인 이벤트 알아내기
        retVal = WSAEnumNetworkEvents(SocketInfoArray[i]->sock, EventArray[i], &NetworkEvents);
        if (retVal == SOCKET_ERROR) continue;

        //FD_ACCEPT 이벤트 처리
        if (NetworkEvents.lNetworkEvents & FD_ACCEPT) {
            if (NetworkEvents.iErrorCode[FD_ACCEPT_BIT] != 0) {
                err_display(NetworkEvents.iErrorCode[FD_ACCEPT_BIT]);
                continue;
            }
            addrlen = sizeof(clientaddr);
            clientsock = accept(SocketInfoArray[i]->sock, (SOCKADDR*)&clientaddr, &addrlen);
            if (clientsock == INVALID_SOCKET) {
                err_quit("accept()");
                continue;
            }
            printf("[TCP서버] 클라이언트 접속 IP : %s %d\r\n",
                inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));

            if (nTotalSockets >= WSA_MAXIMUM_WAIT_EVENTS) {
                printf("[오류] 더이상 접속을 받아들일 수 없습니다.\n");
                closesocket(clientsock);
                continue;
            }

            //소켓 정보 추가 & WSAEventSelect();
            AddSocketInfo(clientsock);
            retVal = WSAEventSelect(clientsock, EventArray[nTotalSockets - 1], FD_READ | FD_WRITE | FD_CLOSE);
            if (retVal == SOCKET_ERROR) continue;
        }

        // FD_READ | FD_WRTIE 이벤트 처리
        if ((NetworkEvents.lNetworkEvents & FD_READ) || (NetworkEvents.lNetworkEvents & FD_WRITE)) {
            if ((NetworkEvents.lNetworkEvents & FD_READ) && (NetworkEvents.iErrorCode[FD_READ_BIT] != 0)) {
                err_display(NetworkEvents.iErrorCode[FD_READ_BIT]);
                continue;
            }
            if ((NetworkEvents.lNetworkEvents & FD_WRITE) && (NetworkEvents.iErrorCode[FD_WRITE_BIT] != 0)) {
                err_display(NetworkEvents.iErrorCode[FD_WRITE_BIT]);
                continue;
            }
            SOCKETINFO* ptr = SocketInfoArray[i];

            if (ptr->recvBytes == 0) {
                //데이터 받기
                retVal = recv(ptr->sock, ptr->buf, BUFSIZE, 0);
                if (retVal == SOCKET_ERROR) {
                    if (WSAGetLastError() != WSAEWOULDBLOCK) {
                        err_quit("recv()");
                        RemoveSocketInfo(i);
                    }
                    continue;
                }
                ptr->recvBytes = retVal;

                //받은 데이터 출력
                addrlen = sizeof(clientaddr);
                getpeername(ptr->sock, (SOCKADDR*)&clientaddr, &addrlen);
                ptr->buf[retVal] = '\0';
                printf("[TCP/%s : %d] %s\r\n", inet_ntoa(clientaddr.sin_addr),
                    ntohs(clientaddr.sin_port), ptr->buf);
            }

            if (ptr->recvBytes > ptr->sendBytes) {
                //데이터 보내기
                retVal = send(ptr->sock, ptr->buf + ptr->sendBytes, ptr->recvBytes - ptr->sendBytes, 0);
                if (retVal == SOCKET_ERROR) {
                    if (WSAGetLastError() != WSAEWOULDBLOCK) {
                        err_quit("send()");
                        RemoveSocketInfo(i);
                    }
                    continue;
                }
                ptr->sendBytes += retVal;
                //받은 데이터 전부 보냈는지 체크
                if (ptr->recvBytes == ptr->sendBytes)
                    ptr->recvBytes = ptr->sendBytes = 0;
            }
        }
        //FD_CLOSE 이벤트 처리
        if (NetworkEvents.lNetworkEvents & FD_CLOSE) {
            if (NetworkEvents.iErrorCode[FD_CLOSE_BIT] != 0)
                err_display(NetworkEvents.iErrorCode[FD_CLOSE_BIT]);
            RemoveSocketInfo(i);
        }
    }
    WSACleanup();
    return 0;
}

//소켓 정보 추가
BOOL AddSocketInfo(SOCKET sock) {

    SOCKETINFO* ptr = new SOCKETINFO;
    if (ptr == NULL) {
        printf("[오류] 메모리가 부족합니다.\n");
        return FALSE;
    }
    WSAEVENT hEvent = WSACreateEvent();
    if (hEvent == WSA_INVALID_EVENT) {
        err_quit("WSACreateEvent()");
        return FALSE;
    }

    ptr->sock = sock;
    ptr->recvBytes = 0;
    ptr->sendBytes = 0;
    SocketInfoArray[nTotalSockets] = ptr;
    EventArray[nTotalSockets] = hEvent;

    ++nTotalSockets;

    return TRUE;
}

//소켓 정보 삭제
void RemoveSocketInfo(int nIndex) {

    SOCKETINFO* ptr = SocketInfoArray[nIndex];

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

    printf("[TCP 서버] 클라이언트 종료 : IP  주소 = %s, 포트번호 = %d\r\n",
        inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));

    closesocket(ptr->sock);
    delete ptr;
    WSACloseEvent(EventArray[nIndex]);
    if (nIndex != (nTotalSockets - 1)) {
        SocketInfoArray[nIndex] = SocketInfoArray[nTotalSockets - 1];
        EventArray[nIndex] = EventArray[nTotalSockets - 1];
    }

    --nTotalSockets;
}
void err_quit(const 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\n", msg, (char*)lpMsgBuf);
    LocalFree(lpMsgBuf);
    exit(1);
}
void err_display(int errcode) {
    LPVOID lpMsgBuf;
    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
        NULL, errcode,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        (LPTSTR)&lpMsgBuf, 0, NULL);
    printf("[ 오류 ] %s\n", (char*)lpMsgBuf);
    LocalFree(lpMsgBuf);
    exit(1);
}
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함