[WinAPI] WinSock2를 이용한 소켓 예제 ( 동기 방식 )

소켓 프로그래밍의 전반적인 개념은 이 블로그를 참고해주세요.

오늘은 WinSock2를 이용해서 소켓 프로그래밍을 해보겠습니다.
소켓 프로그래밍에 대한 개념은 위 링크를 보고 하시길 바랍니다. 찾아본 블로그 중에 제일 정리가 잘 되어 있습니다.

소켓 프로그래밍 방식에는 동기, 비동기 방식이 있습니다.
동기(Synchronous) 방식은 요청과 결과가 동기된 방식이고, 비동기(Asynchronous) 방식은 요청과 결과가 동기되지 않은 방식입니다.

예를 들어 콜센터에서 일하는 직원으로 설명하겠습니다. 
콜센터의 직원에게 콜이 와서 고객 대응을 하고있습니다. 그런데 고객이 택배가 와서 잠시 자리를 비운다고 했을 때,
동기 방식은 고객이 택배를 받고 다시 전화기에 말을 할 때까지 대기를 하며 다른 일을 하지 않고 기다리는 방식이고,
비동기 방식은 고객에게 다시 전화를 달라고 하고 다른 일을 하다가 다시 전화가 오면 고객을 대응하는 방식입니다.

각각의 장단점이 있어 어떤것이 더 좋다고 할 순 없기에 세개 다 알아두시면 좋습니다.

오늘은 동기 방식을 이용하여 만들겠습니다.

동기 방식은 절차 지향 프로그래밍에 더 적합하므로 콘솔 응용 프로그램으로 만들겠습니다.

main.cpp를 생성 하신 후 아래와 같이 입력하세요. 아래 예제는 서버입니다.

#pragma once
#include <iostream>

#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include <WinSock2.h>
#pragma comment( lib, "ws2_32.lib")

using namespace std;

void main()
{
	// 윈도우 소켓 라이브러리 초기화
	WSADATA wsdata;
	int iRes = ::WSAStartup( MAKEWORD( 0x02, 0x02 ), &wsdata );
	if ( ERROR_SUCCESS != iRes )
		return;

	// 소켓 만들기
	SOCKET hSocket;
	hSocket = ::socket( PF_INET, SOCK_STREAM, 0 );
	if ( INVALID_SOCKET == hSocket )
		return;

	// IP와 포트를 생성한 소켓에 결합
	SOCKADDR_IN servAddr;
	servAddr.sin_family = AF_INET;
	servAddr.sin_addr.s_addr = inet_addr( "192.168.0.5" );
	servAddr.sin_port = htons( 1234 );
	iRes = ::bind( hSocket, (LPSOCKADDR)&servAddr, sizeof( servAddr ) );
	if ( ERROR_SUCCESS != iRes )
		return;

	// Lienten
	iRes = ::listen( hSocket, SOMAXCONN );
	if ( ERROR_SUCCESS != iRes )
		return;

	// 클라이언트 Accept
	sockaddr accept_addr;
	int iLen = sizeof( accept_addr );
	SOCKET sockAccept = ::accept( hSocket, &accept_addr, &iLen );
	if ( ERROR_SUCCESS != iRes )
		return;

	while ( TRUE )
	{
		char cBuff;
		memset( &cBuff, 0, sizeof( cBuff ) );
		int iRecv = ::recv( sockAccept, &cBuff, sizeof( cBuff ), 0 );

		if ( 0 < iRecv )
		{
			cout << "Recv(" << cBuff << ")" << endl;

			// ESC 종료
			if ( 0x1b == cBuff )
			{
				cout << "EXIT" << endl;
				break;
			}
		}
	}

	// 소켓 종료
	::closesocket( hSocket );

	// 윈도우 소켓 사용 종료
	WSACleanup();
}

클라이언트에서 1문자씩 받고 콘솔에 표시하다가 ESC 입력을 받으면 서버가 종료 되는 코드입니다.

프로젝트를 하나 더 추가 하신 후 아래와 같이 클라이언트 코드를 입력해 줍니다.

#pragma once
#include <iostream>
#include <conio.h>

#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include <WinSock2.h>
#pragma comment( lib, "ws2_32.lib")

using namespace std;

void main()
{
	WSADATA wsdata;
	int iRes = ::WSAStartup( MAKEWORD( 0x02, 0x02 ), &wsdata );
	if ( ERROR_SUCCESS != iRes )
		return;

	// 소켓 만들기
	SOCKET hSocket;
	hSocket = ::socket( PF_INET, SOCK_STREAM, 0 );
	if ( INVALID_SOCKET == hSocket )
		return;

	// 서버에 연결
	SOCKADDR_IN servAddr;
	servAddr.sin_family = AF_INET;
	servAddr.sin_addr.s_addr = inet_addr( "192.168.0.5" );
	servAddr.sin_port = htons( 1234 );
	iRes = ::connect( hSocket, (LPSOCKADDR)&servAddr, sizeof( servAddr ) );

	while ( true )
	{
		char cBuff = _getch();
		cout << "Insert Key : " << cBuff << endl;
		::send( hSocket, &cBuff, sizeof( cBuff ), 0 );

		// ESC 종료
		if ( 0x1b == cBuff )
		{
			cout << "EXIT" << endl;
			break;
		}
	}

	::closesocket( hSocket );

	WSACleanup();
}

실행 파일을 생성 하신 후 서버와 클라이언트 콘솔 프로그램 두개 모두 실행하면 됩니다.