01
10

WSAAsyncSelect

 

윈도우(창)에서 사용하는 기본 모델이다.

Async은 비동기적 이라는 뜻이 있다.

동기적 서로 다른 주체가 하는 작업이 작업 시작, 종료시간이 연관있을때 작업 지시가 주체
비동기적 서로 다른 주체가 하는 작업이 작업 시작, 종료시간과는 관계가 없을 때
블록킹 하나의 작업이 흐름을 막는것 작업 처리가 주체
논블록킹 하나의 작업이 흐름을 막지 않는것

 

윈속의 소켓 이벤트를 윈도우 메시지를 통해서 비동기적으로 받는다.

윈도우 메세지를 통해 성공 여부를 판단해야한다. 메세지 큐에 의존하여

소켓의 처리가 이루워져 많은 수의 소켓 이벤트를 처리해야하는 경우

큐가 고갈될 수 있다. 그래서 클라이언트의 모델에 적합하다.

 

1
2
3
4
5
int WSAAsyncSelect(
            SOCKET socket,// 입출력 상황을 체크할 소켓
            HWND   hWnd, // 메시지 통지를 받을 윈도우 핸들
            unsigned int wMsg, // 이벤트가 발생하면 보낼 메시지
            long IEvent); // 통지받을 네트워크 이벤트 비트마스크 
cs

 

3번째의 인자가 이벤트가 발생하면 보낼 메세지라 되어있는데,

메세지는 윈도우에서 사용하는 메시지뿐만 아니라 사용자가 직접 등록할 수 있다.

[WM_USER + 숫자] 형식으로 윈도우 메시지와 중복되는 경우를 피할 수 있다.

 

위는 예시로 사용자 정의한 메시지와 원하는 이벤트를 기입한 코드이다.

 

네트워크 이벤트 

 

FD_WRITE

WSAConnect 함수 호출하고 소켓이 처음 연결했을떄,

WSAAccept 함수 호출하고 소켓의 연결이 수락되었을때,

Send, WSASend, send to, WSASendTO 함수 호출후 WSAEWOULDBLOCK이 리턴되서

전송버퍼가 비워졌을때 이벤트가 발생한다.

즉, 보낼수 있는 상태가 되면 이벤트가 발생한다고 생각할 수 있다. 

 

FD_ACCEPT

클라이언트가 접속했을떄

 

FD_READ

수신 가능할때

 

FD_CLOSE

접속 종료할때

 

FD_CONNECT

접속 완료될때

 

FD_OBB

데이터가 도착할때

 

재정의한 윈도우 메시지 프로시저

 

이런 식으로 구성하였다. 연결 상태를 체크하고

수신 가능하다면 연결된 socket으로부터 데이터를 수신한다. recv()함수

 

코드

KAsyncSelect.cpp

더보기
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include "KAsyncSelect.h"
//재정의 윈도우 메세지 프로시저
LRESULT  KAsyncSelect::Select_MsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case NETWORK_MSG:
    {
        WORD wRet = WSAGETSELECTEVENT(lParam);
        switch (wRet)
        {
        case FD_CONNECT:
        {
            m_bConnect = true;
        }break;
        case FD_CLOSE:
        {
            m_bConnect = false;
        }break;
        case FD_READ:
        {
            RecvUser(m_User);
        }break;
        case FD_WRITE:
        {
            m_bConnect = false;
        }break;
        }
    }break;
    }
    return 0;
}
bool KAsyncSelect::Connect(HWND hWnd, int type, int iport, const char* ip)
{
    //소켓 구조체 채우기
    m_Sock = socket(AF_INET, type, 0);
    SOCKADDR_IN sa;
    ZeroMemory(&sa, sizeof(sa));
    sa.sin_family = AF_INET;
    sa.sin_port = htons(iport);
    sa.sin_addr.s_addr = inet_addr(ip);
    m_User.m_Sock = m_Sock;
 
    //윈도우 메세지를 통해서 비동기적으로 소켓 활용 가능
    if (WSAAsyncSelect(m_Sock, hWnd, NETWORK_MSG,
        FD_CONNECT | FD_READ | FD_WRITE | FD_CLOSE) == SOCKET_ERROR)
    {
        return false;
    }
    // 블록킹 연결시도 오류가 발생하지 않으면 0을 반환,  다른 소켓 어플리케이션에 연결
    int iRet = WSAConnect(m_Sock, (sockaddr*)&sa, sizeof(sa), NULLNULLNULLNULL);
    if (iRet == SOCKET_ERROR)
    {
        return false;
    }
    return true;
}
cs

KNetwork.cpp

더보기
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#include "KNetwork.h"
 
bool KNetwork::InitNetwork()
{
    WSADATA wsa;
    if (WSAStartup(MAKEWORD(22), &wsa) != 0)
    {
        return false;
    }
    return true;
}
 
bool KNetwork::InitServer(int protocol, int iport, const char* ip)
{
    m_Sock = socket(AF_INET, SOCK_STREAM, 0);
    SOCKADDR_IN sa;
    ZeroMemory(&sa, sizeof(sa));
    sa.sin_family = AF_INET;
    sa.sin_port = htons(iport);
    if (ip == nullptr)
    {
        sa.sin_addr.s_addr = htonl(INADDR_ANY);
    }
    else
    {
        sa.sin_addr.s_addr = inet_addr(ip);
    }
 
    //소켓에 주소 할당
    int iRet = bind(m_Sock, (sockaddr*)&sa, sizeof(sa));
    if (iRet == SOCKET_ERROR)  return false;
 
    //클라이언트로 부터 연결 요청을 처리할수있는 상태를 만듬 
    iRet = listen(m_Sock, SOMAXCONN);
    if (iRet == SOCKET_ERROR)  return false;
    return true;
}
int KNetwork::SendMsg(SOCKET sock, char* msg, WORD type)
{
    //패킷 생성
    UPACKET    packet;
    ZeroMemory(&packet, sizeof(packet));
    packet.ph.len = strlen(msg) + PACKET_HEADER_SIZE; //ph가 헤더에는 크기와 타입을 알려줌
    packet.ph.type = type;
    memcpy(packet.msg, msg, strlen(msg));
    //운영체제 sendbuffer에 패킷 전송
    char* pMsg = (char*)&packet;
    int iSize = 0;
    do {
        //send함수
        int iSendByte = 0;
            iSendByte= send(sock, &pMsg[iSize],
            packet.ph.len - iSendByte, 0);
 
        //논블럭킹이 아니여서 이거 해줄 필요 없음
        if (iSendByte == SOCKET_ERROR)
        {
            if (WSAGetLastError() != WSAEWOULDBLOCK)
            {
                return -1;
            }
        }
        iSize += iSendByte;
    } while (iSize < packet.ph.len);
    return iSize;
}
int KNetwork::SendMsg(SOCKET sock, UPACKET& packet)
{
    char* pMsg = (char*)&packet;
    int iSize = 0;
    do {
        int iSendByte = send(sock, &pMsg[iSize],
            packet.ph.len - iSize, 0);
        if (iSendByte == SOCKET_ERROR)
        {
            if (WSAGetLastError() != WSAEWOULDBLOCK)
            {
                return -1;
            }
        }
        iSize += iSendByte;
    } while (iSize < packet.ph.len);
    //크기만큼 보내지게 반복문
    return iSize;
}
//int KNetwork::AddUser(SOCKET sock)
//{
//    SOCKADDR_IN clientAddr;
//    int iLen = sizeof(clientAddr);
//    SOCKET clientSock = accept(sock,
//        (sockaddr*)&clientAddr, &iLen);
//    if (clientSock == SOCKET_ERROR)
//    {
//        return -1;
//    }
//    else
//    {
//        KNetworkUser user;
//        user.set(clientSock, clientAddr);
//        userlist.push_back(user);
//        std::cout
//            << "ip =" << inet_ntoa(clientAddr.sin_addr)
//            << "port =" << ntohs(clientAddr.sin_port)
//            << "  " << std::endl;
//        std::cout << userlist.size() << " 명 접속중.." << std::endl;
//    }
//    return 1;
//}
int KNetwork::RecvUser(KNetworkUser& user)
{
    char szRecvBuffer[1024= { 0, };
    //recv 받기 연결된 socket으로부터 데이터를 수신합니다.
    int iRecvByte = recv(user.m_Sock, szRecvBuffer, 10240);
    if (iRecvByte == 0)
    {
        return 0;
    }
    if (iRecvByte == SOCKET_ERROR)
    {
        return -1;
    }
    user.DispatchRead(szRecvBuffer, iRecvByte);
    return 1;
}
bool KNetwork::CloseNetwork()
{
    closesocket(m_Sock);
    WSACleanup();
    return true;
}
 
cs

KNetworkUser.cpp

더보기
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include "KNetworkUser.h"
 
int KNetworkUser::DispatchRead(char* sRecvBuffer, int iRecvByte)
{
    //m_szRecvBuffer 2048을 넘으면 메모리 위치 바꿈, 초기화
    if (m_iWritePos + iRecvByte >= 2048)
    {
        if (m_iReadPos > 0)
        {
            memmove(&m_szRecvBuffer[0], &m_szRecvBuffer[m_iPacketPos], m_iReadPos);
        }
        m_iPacketPos = 0;
        m_iWritePos = m_iReadPos;
    }
 
    memcpy(&m_szRecvBuffer[m_iWritePos], sRecvBuffer, iRecvByte);
    m_iWritePos += iRecvByte;// 버퍼에 이전에 저장된 위치
    m_iReadPos += iRecvByte; // 패킷시작 위치로부터 받은 바이트
    
    if (m_iReadPos >= PACKET_HEADER_SIZE)
    {
        // 패킷 해석 가능
        UPACKET* pPacket = (UPACKET*)&m_szRecvBuffer[m_iPacketPos];
        // 적어도 1개의 패킷이 도착했다면
        if (pPacket->ph.len <= m_iReadPos)
        {
            do {
                KPacket kPacket(pPacket->ph.type);
                memcpy(&kPacket.m_uPacket,
                    &m_szRecvBuffer[m_iPacketPos],
                    pPacket->ph.len);
                m_lPacketPool.push_back(kPacket);
 
                // 다음패킷 처리
                m_iPacketPos += pPacket->ph.len;
                m_iReadPos -= pPacket->ph.len;
                if (m_iReadPos < PACKET_HEADER_SIZE)
                {
                    break;
                }
                pPacket = (UPACKET*)&m_szRecvBuffer[m_iPacketPos];
            } while (pPacket->ph.len <= m_iReadPos);
        }
    }
    return 1;
}
 
void KNetworkUser::set(SOCKET sock, SOCKADDR_IN addr)
{
    m_bConnect = true;
    ZeroMemory(m_szRecvBuffer, sizeof(char* 2048);
    m_iPacketPos = 0;
    m_iWritePos = 0;
    m_iReadPos = 0;
 
    m_Sock = sock;
    m_Addr = addr;
    //네트워크 주소 변환 함수 빅엔디안(32bit) ->  
    m_csName = inet_ntoa(addr.sin_addr);
    //엔디안은 메모리 연속된 대상을 배열하는 방법으로 
    // Network Byte 순서를 To Host의 Byte 순서로 바꿈
    m_iPort = ntohs(addr.sin_port);
}
 
cs

 

결과

COMMENT