[WinAPI] (파일첨부)WinSock2를 이용한 소켓 예제 ( 비동기 방식 – 클라이언트 )

[WinAPI] (파일첨부)WinSock2를 이용한 소켓 예제 ( 비동기 방식 – 클라이언트 )

서버를 만드는 방식은 여기를 참고해주세요

오늘은 클라이언트를 만들어 보도록 하겠습니다. MFC 프로젝트를 아래와 같이 하나 추가해 주세요.

stdafx.h 맨 위에 아래와 같이 define을 해줍니다.

#pragma once
#define _WINSOCK_DEPRECATED_NO_WARNINGS // 정의

대화상자 헤더파일에 아래와 같이 SOCKET 변수를 추가합니다.

SOCKET m_socketClient;

리소스 뷰에서 연결, 보내기, 종료 버튼을 생성 합니다. ( 더블 클릭하여 이벤트 처리 함수까지 생성하게 합니다. )

연결 버튼 이벤트 함수에 아래와 같이 코딩합니다.

void CASyncSockClientDlg::OnBnClickedButton1()
{
	// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
	WSADATA wsdata;
	int iRes = ::WSAStartup( MAKEWORD( 0x02, 0x02 ), &wsdata );

	// 소켓 만들기
	m_socketClient = ::socket( PF_INET, SOCK_STREAM, 0 );

	iRes = ::WSAAsyncSelect( m_socketClient, m_hWnd, 10000, FD_CONNECT | FD_READ | FD_WRITE | FD_CLOSE );

	// 서버에 연결
	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( m_socketClient, (LPSOCKADDR)&servAddr, sizeof( servAddr ) );
}

클라이언트가 접속을 완료하고 패킷을 수신하고, 종료되었을 때 메시지를 받기위해 아래와 같이 WindowProc 함수를 추가합니다.

그리고 내부에 아래와 같이 코딩합니다.

LRESULT CASyncSockClientDlg::WindowProc( UINT message, WPARAM wParam, LPARAM lParam )
{
	if ( 10000 == message )
	{
		SOCKET hSocket = (SOCKET)wParam;
		switch ( lParam )
		{
			case FD_CONNECT:
				// 연결 됨
			break;
			case FD_READ:
            	// 패킷 수신
			{
				char cBuff;
				memset( &cBuff, 0, sizeof( cBuff ) );
				int iLen = ::recv( hSocket, &cBuff, sizeof( cBuff ), 0 );
			}
			break;
			case FD_WRITE:
            	// 송신 가능함
			break;
			case FD_CLOSE:
           	 // 종료
				int iRes = ::closesocket( hSocket );
			break;
		}
	}

	return CDialogEx::WindowProc( message, wParam, lParam );
}

보내기 버튼 함수에 아래와 같이 코딩합니다.

void CASyncSockClientDlg::OnBnClickedButton2()
{
	// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
	char cBuff = 1;
	int iSend = ::send( m_socketClient, &cBuff, sizeof( cBuff ), 0 );
	if ( SOCKET_ERROR == iSend )
	{
		// Send error
	}
}

종료 버튼 함수에 아래와 같이 코딩합니다.

void CASyncSockClientDlg::OnBnClickedButton3()
{
	// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
	int iRes = ::closesocket( m_socketClient );

	::WSACleanup();
}

클라이언트도 마찬가지로 WSAAsyncSelect로 논블로킹 모드로 변환 시켜주는게 핵심입니다.

int
WSAAPI
WSAAsyncSelect(
    _In_ SOCKET s,		// 소켓
    _In_ HWND hWnd,		// 네트워크 이벤트가 발생할때 메시지를 받을 윈도우 핸들
    _In_ u_int wMsg,	// 네트워크 이벤트가 발생할때 수신되는 메시지
    _In_ long lEvent	// 네트워크 이벤트
    );

클라이언트를 만들 때 참고해야할 네트워크 이벤트는 아래와 같습니다.

// 	FD_READ: 데이터 수신이 가능하면 윈도우 메시지를 발생시킨다.
// 	FD_WRITE: 데이터 송신이 가능하면 윈도우 메시지를 발생시킨다.
// 	FD_CLOSE: 상대가 접속을 종료하면 윈도우 메시지를 발생시킨다.
// 	FD_CONNECT: 접속이 완료되면 윈도우 메시지를 발생시킨다.
// 	FD_OOB: OOB 데이터가 도착하면 윈도우 메시지를 발생시킨다.

저번 글에 만들었던 서버 프로그램과 이번에 만든 클라이언트 프로그램을 실행 했을 때 순서는 아래와 같습니다.

  1. 서버 소켓 생성
  2. 클라이언트 접속
  3. 서버 or 클라이언트에서 패킷 전송
  4. 서버 or 클라이언트에서 소켓 종료

저번 서버 글도 그렇고 이번 클라이언트도 작동하게만 만들어 놓아서 뭔가 좀 아쉽습니다.

그래서 제가 추가적으로 수정한 파일을 첨부하였습니다. 아래 파일을 다운받으셔서 참고하세요.