Network

Socket Programming 간단한 실습

W00gie 2021. 12. 27. 17:06

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 프로젝트를 분리하여 작성하고, 위와 같이 동시실행하는 환경을 구성해야 정상 작동한다.

 

실행결과