티스토리 뷰

  • 실행 함수(비동기 + 논-블로킹)
    • WSASend
    • 비동기 입출력 소켓
      1. WSABUF 배열의 시작 주소 + 개수
      2. 보내고/받은 바이트 수
      3. 상세 옵션인데 0
      4. WSAOVERLAPPED 구조체 주소값
      5. 입출력이 완료되면 OS가 호출할 콜백 함수
    • WSARecv
    • Scatter-Gater
    • AcceptEx
    • ConnectEx
    • WSAWaitForMultipleEvents
    • WSAGetOverlappedResult 
      1. 비동기 소켓
      2. 넘겨준 overlapped 구조체
      3. 전송된 바이트 수
      4. 비동기 입출력 작업이 끝날때까지 대기할지? (false)
      5. 비동기 입출력 작업 관련 부가 정보. 거의 사용 안함.
  • 실행 과정 (이벤트 방식)
    • Overlapped 함수를 건다 (WSARecv, WSASend)
    • Overlapped 함수가 성공했는지 확인 후
      • → 성공했으면 결과를 얻어서 처리
      • → 실패했으면 사유를 확인
  • Overlapped 이벤트 기반 코드 작동 방식
    1. 비동기 입출력 지원하는 소켓 생성 + 통지 받기 위한 이벤트 객체 생성.
    2. 비동기 입출력 함수 호출 (1에서 만든 이벤트 객체를 같이 넘겨줌).
    3. 비동기 작업이 바로 완료되지 않으면, WSA_IO_PENDING 오류 코드.
    4. 운영체제는 이벤트 객체를 signaled 상태로 만들어서 완료 상태 알려줌.
    5. WSAWaitForMultipleEvents 함수 호출해서 이벤트 객체의 signal 판별
    6. WSAGetOverlappedResult 호출해서 비동기 입출력 결과 확인 및 데이터 처리

#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 {
    WSAOVERLAPPED overlapped;
    SOCKET sock;
    char buf[BUFSIZE + 1];
    int recvBytes;
    int sendBytes;
    WSABUF wsabuf;
};


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

//비동기 입출력 처리 함수
DWORD WINAPI WorkerThread(LPVOID arg);

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

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

int main()
{
    WSADATA wsaData;
    SOCKADDR_IN sock_addr;
    int retVal;

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

    //소켓 생성
    SOCKET 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");

    //더미(dummy) 이벤트 객체 생성
    WSAEVENT hEvent = WSACreateEvent();
    if (hEvent == WSA_INVALID_EVENT) err_quit("WSACreateEvent()");
    EventArray[nTotalSockets++] = hEvent;

    //스레드 생성
    HANDLE hThread = CreateThread(NULL, 0, WorkerThread, NULL, 0, NULL);
    if (hThread == NULL) return 1;
    CloseHandle(hThread);

    //데이터 통신에 사용할 변수
    SOCKET clientsock;
    SOCKADDR_IN clientaddr;
    int addrlen;
    DWORD recvbytes, flags;

    while (1) {
        //소켓 초기화
        addrlen = sizeof(clientaddr);
        clientsock = accept(listen_s, (SOCKADDR*)&clientaddr, &addrlen);
        if (clientsock == INVALID_SOCKET) {
            err_quit("accept()");
            break;
        }
        if (::WSAGetLastError() == WSAEWOULDBLOCK)
            continue;
        printf("[TCP서버] 클라이언트 접속 IP : %s %d\n",
            inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));

        //소켓 정보 추가
        if (AddSocketInfo(clientsock) == FALSE) {
            closesocket(clientsock);
            printf("[TCP서버] 클라이언트 종료 IP : %s %d\n",
                inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
            continue;
        }

        //비동기 입출력 시작
        SOCKETINFO* ptr = SocketInfoArray[nTotalSockets - 1];
        flags = 0;
        retVal = WSARecv(ptr->sock, &ptr->wsabuf, 1, &recvbytes, &flags, &ptr->overlapped, NULL);
        if (retVal == SOCKET_ERROR) {
            if (WSAGetLastError() != WSA_IO_PENDING) {
                err_quit("WSARecv()");
                RemoveSocketInfo(nTotalSockets - 1);
                continue;
            }
        }
        
        //소켓의 개수(nTotalSockets) 변화를 알림
        WSASetEvent(EventArray[0]);
    }

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

DWORD WINAPI WorkerThread(LPVOID arg) {

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

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

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

        //데이터 전송량 갱신
        if (ptr->recvBytes == 0) {
            ptr->recvBytes = cbTransferred;
            ptr->sendBytes = 0;

            //받은 데이터 출력
            ptr->buf[ptr->recvBytes] = '\0';
            printf("[TCP /%s : %d] %s\r\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->overlapped.hEvent = EventArray[index];
            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_quit("WSASend()");
                }
                continue;
            }
        }
        else {
            ptr->recvBytes = 0;

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

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

//소켓 정보 추가
BOOL AddSocketInfo(SOCKET sock) {
    EnterCriticalSection(&cs);
    if (nTotalSockets >= WSA_MAXIMUM_WAIT_EVENTS) {
        LeaveCriticalSection(&cs);
        return FALSE;
    }

    SOCKETINFO* ptr = new SOCKETINFO;
    if (ptr == NULL) {
        LeaveCriticalSection(&cs);
        return FALSE;
    }

    WSAEVENT hEvent = WSACreateEvent();
    if (hEvent == WSA_INVALID_EVENT) {
        LeaveCriticalSection(&cs);
        return FALSE;
    }
    ZeroMemory(&ptr->overlapped, sizeof(ptr->overlapped));
    ptr->overlapped.hEvent = hEvent;
    ptr->sock = sock;
    ptr->recvBytes = ptr->sendBytes = 0;
    ptr->wsabuf.buf = ptr->buf;
    ptr->wsabuf.len = BUFSIZE;

    SocketInfoArray[nTotalSockets] = ptr;
    EventArray[nTotalSockets] = hEvent;
    ++nTotalSockets;

    LeaveCriticalSection(&cs);
    return TRUE;
}

//소켓 정보 삭제
void RemoveSocketInfo(int nIndex) {
    EnterCriticalSection(&cs);
    SOCKETINFO* ptr = SocketInfoArray[nIndex];
    closesocket(ptr->sock);
    delete ptr;
    WSACloseEvent(EventArray[nIndex]);

    if (nIndex != (nTotalSockets- 1))
        SocketInfoArray[nIndex] = SocketInfoArray[nTotalSockets - 1];

    --nTotalSockets;
    LeaveCriticalSection(&cs);
}
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);
}
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
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
글 보관함