01
07

읽을 파일이 없는데, Read()함수를 호출하면 읽을 내용이 생길때까지

대기하는 블록킹 소켓과 블록킹이 되지 않아, 조건을 만족하지 않을때

오류를 내서 일일히 관리해주는 논블로킹소켓이 있다.

 

이러한 소켓모드와 관계없이 여러 소켓을 한 스레드로 처리할 수

있는게 Select 함수이다.

 

Select() 함수

작업할 준비가 된 파일에 대해서만 작업을 하는 함수이다.

 

1
2
3
4
5
6
7
int select(
    _In_ int nfds,
    _Inout_opt_ fd_set FAR * readfds,
    _Inout_opt_ fd_set FAR * writefds,
    _Inout_opt_ fd_set FAR * exceptfds,
    _In_opt_ const struct timeval FAR * timeout
    );
cs

 

nfds 유닉스와 호환성을 위해 존재함, 윈도우에서 사용 안함, 관리할 파일의 개수를 지정하는 것이였나봄
readfds 읽기 set, 블록되지 않고 읽기가 가능한지 감시
writefds 쓰기 set, 블록되지 않고 쓰기가 가능한지 감시
exceptfds 예외 set, 예외 발생 감시
timeout 타임 아웃 값, 정해진 시간이 경과할때까지 블록

 

작업 순서

 

1, Socket Set을 비운다. (readfds, writefds)

 

 

2. 소켓셋에 소켓을 넣는다.

 

 

3. Select() 함수를 호출한다. 

 

 

4. Select 함수 리턴 후 소켓 셋에 남아 있는 모든 소켓에 대해 

맞는 소켓함수를 호출하여 처리한다.

 

 

5. 1~4 번 반복하면 된다.


timeOut 값에 5초를 넣으니까 Select() 함수에서

진짜 5초 블럭킹이 된다.

 

FD_SET으로 소켓을 넣고나서 FD_ISSET에서 해당 소켓을

넣으면 비트가 일치하니 바로 호출되는 줄 알았는데

그건 또 아닌 것 같다. 입출력이 발생해야 if문에 걸린다.

 

굉장히 편하지만, 대규모 서버에서는 적합하지 않다고 한다.

 

전체 코드

더보기
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
#include "KNetwork.h"
 
//서버 Select 모델
std::list<KNetworkUser> userlist;
int SendMsg(SOCKET sock, char* msg, WORD type)
{
    //1. 패킷을 객체 생성
    UPACKET packet;
    ZeroMemory(&packet, sizeof(packet));
    //패킷의 길이는 헤더 사이즈 + 메세지 사이즈
    packet.ph.len = strlen(msg) + PACKET_HEADER_SIZE;
    packet.ph.type = type;
    //메세지 받은걸로 memcpy로 패킷 만듬
    memcpy(packet.msg, msg, strlen(msg));
 
    // 2번 패킷 전송 : 운영체제 sendbuffer(short바이트)
    char* pMsg = (char*)&packet;
    int iSize = 0;
 
    do {
        //TCP는 연속적이지만 바이트 단위로 나눠져 보내질도 있음
        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 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 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 RecvUser(KNetworkUser& user)
{
    char szRecvBuffer[1024= { 0, };
    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;
}
void main()
{
    KNetwork net;
    net.InitNetwork();
    net.InitServer(SOCK_STREAM, 10000);
 
    SOCKADDR_IN clientAddr;
    int iLen = sizeof(clientAddr);
 
    std::cout<< "Server Start." << std::endl;
 
    FD_SET    rSet;
    FD_SET    wSet;
    timeval timeOut;
 
    timeOut.tv_sec = 1;
    timeOut.tv_usec = 0;
 
    while (1)
    {
        //매번 초기화 구조체의 주요 변수는 배열 
        FD_ZERO(&rSet);
        FD_ZERO(&wSet);
        //리슨 소켓에 rset를 저장함
        FD_SET(net.m_ListenSocket, &rSet);
 
        std::list<KNetworkUser>::iterator useriter;
        for (useriter = userlist.begin();
            useriter != userlist.end();)
        {
            //유저 중에 연결 끊긴 사람이 있다면 리스트에서 삭제함
            if ((*useriter).m_bConnect == false)
            {
                std::cout << (*useriter).m_csName << " 접속종료됨." << std::endl;
                useriter = userlist.erase(useriter);
                continue;
            }
            //유저의 소켓을 rset에 저장함
            FD_SET((*useriter).m_Sock, &rSet);
            // 만약 user에서 보낸 패킷이 있으면 wSet 구조체에 소켓 저장
            if ((*useriter).m_lPacketPool.size() > 0)
            {
                FD_SET((*useriter).m_Sock, &wSet);
            }
            useriter++;
        }
        //select 모델을 사용
        //select 함수는 fd_set 구조체에 할당한
        // FD의 이벤트가 발생하면 이를 감지하고
        // 어떤 FD 이벤트가 발생했는지 알려준다.
        int iRet = select(0,
            &rSet,
            &wSet,
            nullptr,
            &timeOut);
 
        //아무 활동이 없으면 0을 반환한다.
        if (iRet == 0)
        {
            continue;
        }std::cout << "..";
        //fdset중 소켓 fd에 해당하는 비트가 세트되어 있으면 양수값인 fd를 리턴한다.
        if (FD_ISSET(net.m_ListenSocket, &rSet))
        {
            std::cout << "test"<< std::endl;
            if (AddUser(net.m_ListenSocket) <= 0)
            {
                break;
            }
        }
        
        //유저 리스트를 돌면서 해당 유저 소켓이 저장되어 있다면
        for (KNetworkUser& user : userlist)
        {
            //연결 끊겼는지 확인 여기서는 불값 체크만
            if (FD_ISSET(user.m_Sock, &rSet))
            {
                int iRet = RecvUser(user);
                if (iRet <= 0)
                {
                    user.m_bConnect = false;
                }
            }
        }
        for (KNetworkUser& user : userlist)
        {
            if (FD_ISSET(user.m_Sock, &wSet))
            {
                if (user.m_lPacketPool.size() > 0)
                {
                    std::list<KPacket>::iterator iter;
                    for (iter = user.m_lPacketPool.begin();
                        iter != user.m_lPacketPool.end(); )
                    {
                        for (KNetworkUser& senduser : userlist)
                        {
                            int iRet = SendMsg(senduser.m_Sock, (*iter).m_uPacket);
                            if (iRet <= 0)
                            {
                                senduser.m_bConnect = false;
                            }
                        }
                        iter = user.m_lPacketPool.erase(iter);
                    }
                }
            }
        }
    }
    net.CloseNetwork();
}
cs

 

COMMENT