WSAAsyncSelect 모델 서버 프로그래밍
서버 코드
더보기
#pragma warning(disable : 4996)
#pragma comment(lib, "ws2_32")
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <stdio.h>
#include <tchar.h>
#include <locale.h>
#define SERVERPORT 9000
#define BUFSIZE 512
#define WM_SOCKET (WM_USER+1)
// 소켓 정보 저장을 위한 구조체와 변수
struct SOCKETINFO
{
SOCKET sock;
char buf[BUFSIZE + 1];
int recvbytes;
int sendbytes;
BOOL recvdelayed;
SOCKETINFO* next;
};
SOCKETINFO* socketInfoList;
// 윈도우 메시지 처리 함수
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void ProcessSocketMessage(HWND, UINT, WPARAM, LPARAM);
// 소켓 관리 함수
BOOL AddSocketInfo(SOCKET sock);
SOCKETINFO* GetSocketInfo(SOCKET sock);
void RemoveSocketInfo(SOCKET sock);
int main()
{
_tsetlocale(LC_ALL, TEXT(""));
int retval;
// 윈도우 클래스 등록
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = NULL;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = TEXT("MyWndClass");
if (!RegisterClass(&wndclass))
return 1;
// 윈도우 생성
HWND hWnd = CreateWindow(
TEXT("MyWndClass"),
TEXT("TCP 서버"),
WS_OVERLAPPEDWINDOW,
0, 0,
600, 200,
NULL,
NULL,
NULL,
NULL);
if (hWnd == NULL)
return 1;
ShowWindow(hWnd, SW_SHOWNORMAL);
UpdateWindow(hWnd);
// 윈속 초기화
WSADATA wsa;
retval = WSAStartup(MAKEWORD(2, 2), &wsa);
if (retval != 0)
{
_tprintf_s(TEXT("error WSAStartup() %d\n"), retval);
return 1;
}
// listen socket()
SOCKET listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock == INVALID_SOCKET)
{
_tprintf_s(TEXT("error listen socket()\n"));
return 1;
}
// bind()
SOCKADDR_IN serverAddr;
ZeroMemory(&serverAddr, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(SERVERPORT);
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
retval = bind(listen_sock, (SOCKADDR*)&serverAddr, sizeof(serverAddr));
if (retval == SOCKET_ERROR)
{
_tprintf_s(TEXT("error bind()\n"));
return 1;
}
// listen()
retval = listen(listen_sock, SOMAXCONN);
if (retval == SOCKET_ERROR)
{
_tprintf_s(TEXT("error listen()\n"));
return 1;
}
// WSAAsyncSelect()
retval = WSAAsyncSelect(listen_sock, hWnd, WM_SOCKET, FD_ACCEPT | FD_CLOSE);
if (retval == SOCKET_ERROR)
{
_tprintf_s(TEXT("error WSAAsyncSelect()\n"));
return 1;
}
// 메시지 루프
MSG msg;
while (GetMessage(&msg, 0, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// 윈속 종료
WSACleanup();
return msg.wParam;
}
// 윈도우 메시지 처리
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_SOCKET: // 소켓 관련 윈도우 메시지
ProcessSocketMessage(hWnd, uMsg, wParam, lParam);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
// 소켓 관련 윈도우 메시지 처리
void ProcessSocketMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// 데이터 통신에 사용할 변수
SOCKETINFO* ptr;
SOCKET client_sock;
SOCKADDR_IN clientaddr;
int addrlen, retval;
TCHAR buf[16];
// 오류 발생 여부 확인
if (WSAGETSELECTERROR(lParam))
{
_tprintf_s(TEXT("WSAGETSELECTERROR %d\n"), lParam);
RemoveSocketInfo(wParam);
return;
}
// 메시지 처리
switch (lParam)
{
case FD_ACCEPT:
addrlen = sizeof(clientaddr);
client_sock = accept(wParam, (SOCKADDR*)&clientaddr, &addrlen);
if (client_sock == INVALID_SOCKET)
{
_tprintf_s(TEXT("error accept()\n"));
return;
}
InetNtop(AF_INET, &clientaddr.sin_addr, buf, _countof(buf));
_tprintf_s(TEXT("\n[TCP 서버] 클라이언트 접속: IP 주소=%s, 포트 번호=%d\n"),
buf, ntohs(clientaddr.sin_port));
AddSocketInfo(client_sock);
retval = WSAAsyncSelect(client_sock, hWnd,
WM_SOCKET, FD_READ | FD_WRITE | FD_CLOSE);
if (retval == SOCKET_ERROR)
{
_tprintf_s(TEXT("error client_sock WSAAsyncSelect()\n"));
RemoveSocketInfo(client_sock);
}
break;
case FD_READ:
ptr = GetSocketInfo(wParam);
if (ptr->recvbytes > 0)
{
ptr->recvdelayed = TRUE;
return;
}
// 데이터 받기
retval = recv(wParam, (char*)ptr->buf, BUFSIZE, 0);
if (retval == SOCKET_ERROR)
{
_tprintf_s(TEXT("error recv() \n"));
RemoveSocketInfo(wParam);
return;
}
ptr->recvbytes = retval;
// 받은 데이터 출력
ptr->buf[retval] = '\0';
addrlen = sizeof(clientaddr);
getpeername(wParam, (SOCKADDR*)&clientaddr, &addrlen);
InetNtop(AF_INET, &clientaddr.sin_addr, buf, _countof(buf));
_tprintf_s(TEXT("[TCP/%s:%d] "), buf, clientaddr.sin_port);
printf_s("%s\n", ptr->buf);
case FD_WRITE:
ptr = GetSocketInfo(wParam);
if (ptr->recvbytes <= ptr->sendbytes)
return;
// 데이터 보내기
retval = send(wParam, (char*)ptr->buf + ptr->sendbytes,
ptr->recvbytes - ptr->sendbytes, 0);
if (retval == SOCKET_ERROR)
{
_tprintf_s(TEXT("error send() SOCKET_ERROR\n"));
RemoveSocketInfo(wParam);
return;
}
ptr->sendbytes += retval;
// 받은 데이터를 모두 보냈는지 체크
if (ptr->recvbytes == ptr->sendbytes)
{
ptr->recvbytes = 0;
ptr->sendbytes = 0;
if (ptr->recvdelayed)
{
ptr->recvdelayed = FALSE;
PostMessage(hWnd, WM_SOCKET, wParam, FD_READ);
}
}
break;
case FD_CLOSE:
RemoveSocketInfo(wParam);
break;
}
}
// 소켓 정보 추가
BOOL AddSocketInfo(SOCKET sock)
{
SOCKETINFO* ptr = new SOCKETINFO;
if (ptr == NULL)
{
_tprintf_s(TEXT("[오류] 메모리가 부족합니다!\n"));
return FALSE;
}
ptr->sock = sock;
ptr->recvbytes = 0;
ptr->sendbytes = 0;
ptr->recvdelayed = FALSE;
ptr->next = socketInfoList;
socketInfoList = ptr;
return TRUE;
}
// 소켓 정보 얻기
SOCKETINFO* GetSocketInfo(SOCKET sock)
{
SOCKETINFO* ptr = socketInfoList;
while (ptr)
{
if (ptr->sock == sock)
return ptr;
ptr = ptr->next;
}
return NULL;
}
// 소켓 정보 제거
void RemoveSocketInfo(SOCKET sock)
{
SOCKADDR_IN clientaddr;
int addrlen = sizeof(clientaddr);
getpeername(sock, (SOCKADDR*)&clientaddr, &addrlen);
TCHAR buf[16];
InetNtop(AF_INET, &clientaddr.sin_addr, buf, _countof(buf));
_tprintf_s(TEXT("[TCP 서버] 클라이언트 종료: IP 주소=%s, 포트 번호=%d\n"),
buf, ntohs(clientaddr.sin_port));
SOCKETINFO* curr = socketInfoList;
SOCKETINFO* prev = NULL;
while (curr)
{
if (curr->sock == sock)
{
if (prev)
prev->next = curr->next;
else
socketInfoList = curr->next;
closesocket(curr->sock);
delete curr;
return;
}
prev = curr;
curr = curr->next;
}
}
클라이언트 코드
반응형
'공부' 카테고리의 다른 글
WSAEventSelect 모델 (0) | 2020.10.20 |
---|---|
복습) 문제 만들기 20201020 (0) | 2020.10.20 |
Select모델 에코서버 프로그래밍 (0) | 2020.10.17 |
TCP 파일전송 프로그램 (0) | 2020.10.13 |
TCP 에코서버 프로그래밍 (0) | 2020.10.10 |