공부

WSAAsyncSelect 모델 서버 프로그래밍

글로벌디노 2020. 10. 19. 00:39

WSAAsyncSelect 모델 서버 프로그래밍

MSDN

 

 

 

서버 코드

더보기
#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