티스토리 뷰
- 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);**
- 관심있는 네트워크 이벤트 확인
- FD_ACCEPT : 접속한 클라가 있음 accept
- FD_READ : 데이터 수신 가능 recv, recvfrom
- FD_WRITE : 데이터 송신 가능 send, sendto
- FD_CLOSE : 상대가 접속 종료
- FD_CONNECT : 통신을 위한 연결 절차 완료
- 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);
}
'서버 공부 > 네트워크' 카테고리의 다른 글
TCP/IP) Overlapped I/O(1) 모델(Event) (0) | 2022.07.11 |
---|---|
TCP/IP 윈도우 소켓 프로그래밍 10장 연습문제(with 스레드풀) (0) | 2022.07.10 |
TCP/IP) Select 모델 서버 (0) | 2022.07.08 |
이상적인 소켓 입출력 모델의 특징 (0) | 2022.07.08 |
비동기 형식 TCP/IP 서버 (0) | 2022.07.08 |
댓글