본문 바로가기
Network

Socket Programming 간단한 실습

by W00gie 2021. 12. 27.

C로 작성된 Socket Programming 실습 코드이다.

client에서 루프백 어드레스(로컬 호스트)를 통해 소켓을 보내고 서버에서 수신하는 간단한 코드이다.

코드를 실습해보며 윈도우에서 제공하는 소켓이 어떻게 구성되었는지, 송수신과정에서 소켓이 어떤 역할을 하는지 알 수 있다. 소켓 프로그래밍에 필요한 함수, 라이브러리등은 microsoft docs에서 많은 정보를 얻을 수 있다.

 

https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsastartup

 

WSAStartup function (winsock.h) - Win32 apps

Initiates use of the Winsock DLL by a process.

docs.microsoft.com

 

소켓의 통신과정은 다음과 같다.

 

클라이언트 : 

1. winsock 라이브러리 초기화

2. 소켓 생성

3. 서버주소로 연결 (connect 함수)

 

서버:

1. Listener 소켓 생성

2. Bind

3. Listen

4. Accept

 

*) Bind : 소켓에 주소, 프로토콜, 포트를 할당

 


Client Part

#include "pch.h"
#include <iostream>
#include <winsock2.h>
#include <mswsock.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")

int main()
{
	// ws2_32 라이브러리 초기화
	WSAData wsaData;
	if (::WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		return 0;

	SOCKET clientSocket = ::socket(AF_INET, SOCK_STREAM, 0);
	if (clientSocket == INVALID_SOCKET)
	{
		int32 errCode = ::WSAGetLastError();
		cout << "Socket ErrorCode : " << errCode << endl;
		return 0;
	}

	SOCKADDR_IN serverAddr; // IPv4
	::memset(&serverAddr, 0, sizeof(serverAddr));
	serverAddr.sin_family = AF_INET;
	::inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);
	serverAddr.sin_port = ::htons(7777); 

	if (::connect(clientSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
	{
		int32 errCode = ::WSAGetLastError();
		cout << "Connect ErrorCode : " << errCode << endl;
		return 0;
	}

	// ---------------------------
	// 데이터 송수신 가능

	cout << "Connected To Server!" << endl;

	while (true)
	{
		// TODO

		this_thread::sleep_for(1s);
	}

	::closesocket(clientSocket);

	// 종료
	::WSACleanup();
}

 


 

Server Part

#include <winsock2.h>
#include <mswsock.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")

int main()
{
	WSAData wsaData;
	if (::WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		return 0;

	// ad : Address Family (AF_INET = IPv4, AF_INET6 = IPv6)
	// type : TCP(SOCK_STREAM) vs UDP(SOCK_DGRAM)
	// protocol : 0
	// return : descriptor
	SOCKET listenSocket = ::socket(AF_INET, SOCK_STREAM, 0);
	if (listenSocket == INVALID_SOCKET)
	{
		int32 errCode = ::WSAGetLastError();
		cout << "Socket ErrorCode : " << errCode << endl;
		return 0;
	}

	SOCKADDR_IN serverAddr; // IPv4
	::memset(&serverAddr, 0, sizeof(serverAddr));
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_addr.s_addr = ::htonl(INADDR_ANY); 
	serverAddr.sin_port = ::htons(7777); 

	// 소켓에 서버 주소를 바인딩
	if (::bind(listenSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
	{
		int32 errCode = ::WSAGetLastError();
		cout << "Bind ErrorCode : " << errCode << endl;
		return 0;
	}

	// 
	if (::listen(listenSocket, 10) == SOCKET_ERROR)
	{
		int32 errCode = ::WSAGetLastError();
		cout << "Listen ErrorCode : " << errCode << endl;
		return 0;
	}

	// -----------------------------

	while (true)
	{
		SOCKADDR_IN clientAddr; // IPv4
		::memset(&clientAddr, 0, sizeof(clientAddr));
		int32 addrLen = sizeof(clientAddr);

		SOCKET clientSocket = ::accept(listenSocket, (SOCKADDR*)&clientAddr, &addrLen);
		if (clientSocket == INVALID_SOCKET)
		{
			int32 errCode = ::WSAGetLastError();
			cout << "Accept ErrorCode : " << errCode << endl;
			return 0;
		}

		// 연결해서 소켓의 정보 받아옴
		char ipAddress[16];
		::inet_ntop(AF_INET, &clientAddr.sin_addr, ipAddress, sizeof(ipAddress));
		cout << "Client Connected! IP = " << ipAddress << endl;

		// TODO
	}



	// 종료
	::WSACleanup();
}

서버쪽에서 중요한점은 listen socket과 client socket 두 개의 소켓을 이용한다는 점이다.

listen socket의 경우 실질적인 서버의 데이터교환에는 관여하지않고, 클라이언트의 연결에만 관여한다.

client socket은 listen socket을 통해 연결된 client와 실질적인 데이터 교환을 수행한다는 점.

공식적인 명칭은 아니지만 두 소켓의 이용방안에 대해 착각해선 안된다.

+) UDP 기반의 통신에서는 한개의 소켓 사용

 

 

타 프로젝트와 달리 해당 솔루션내에서는

Client와 Server 프로젝트를 분리하여 작성하고, 위와 같이 동시실행하는 환경을 구성해야 정상 작동한다.

 

실행결과

 

'Network' 카테고리의 다른 글

Non-Blocking 에 대한 정의  (0) 2021.12.29