최재웅/C, C++

TCP/IP 통신 함수 사용과 WinSock2.h

MapU 2019. 7. 12. 19:06

                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 

목  차

 
1. 소켓 프로그래밍?

     1-1. Server와 Client

     1-2. Socket이란?

     1-3. TCP/IP 통신 방식과 함수 사용


2. WinSock2.h

 

     2-1. socket 함수

     2-2. accept 함수

     2-3. bind 함수

     2-4. connect 함수

     2-5. listen함수

     2-6. send/recv 함수


3. 프로그래밍 예제

 

4. 비고

 

 













1. 소켓 프로그래밍

 


1-1. Server(서버)와 Client(사용자)

 

  1. Server란?

    - 여러 컴퓨터를 연결한 통신망에서 사용하는 정보를 저장, 사용 빈도수가 높은 프로그램들을 한 곳에 모아놓은 컴퓨터.

  2. Client란?

    - 서버와 정보를 주고받는 통신망에 연결된 많은 컴퓨터들.

1-2. Socket이란?

 

- 네트워크 상에서 Server(서버)와 Client(사용자) 각각 두 개의 프로그램이 특정 포트를 통해 양방향 통신이 가능하도록 만들어주는 소프트웨어 장치.

 

- Socket은 IP Address와 Port Number가 합쳐진, 네트워크 상에서 Server(서버) 프로그램과 Client(사용자) 프로그램이 통신을 할 수 있도록 해주는 소프트웨어 장치.

* Socket과 Port의 차이 *

 

- Socket : 프로세스가 네트워크를 통해서 데이터를 주고받기위해 반드시 열어야하는 창문, 창구와 같은 것.

 

- Port : 네트워크를 통해 데이터를 주고받는 프로세스를 식별해내기 위해 호스트 내부적으로 프로세스가 할당받는 고유한 값.

 

 

다시 말해, 소켓을 이용한 통신을 하기 위해선 포트를 반드시 할당받아야 한다.

 

포트 넘버를 할당받은 프로세스는 여러 개의 소켓을 개방할 수 있다.

 


1-3. TCP/IP 통신 방식과 함수 사용

 

- 오늘 날 인터넷 통신의 기본이되는 패킷 통신을 위한 인터넷의 규약이 TCP/IP이다.

- TCP/IP는 두 개의 프로토콜이다.

- IP : 복잡한 네트워크 망을 통하여 가장 효율적인 방법으로 데이터의 조각들을 빠르게 보내는 일을 한다.

- TCP : 데이터를 잘게 잘라 전송하며, 순서와 데이터의 누락을 점검하여 재요청하는 일을 담당한다.

 

 

 

다시 말해, IP는 패킷 전달 여부를 보증하지 않으며, 패킷의 송/수신 순서가 달라질 수 있다.

이를 보완하기 위해 TCP를 사용한다. IP 위에서 동작하는 프로토콜인 TCP는 데이터의 전달을 보장하고,

 

송/수신의 순서를 맞춰준다.

 

 

 

 

 

 

<그림 1-1> TCP/IP 통신 함수 사용 순서


 

 

 

 

 

 

 

 


 2. WinSock2.h

 


2-1. socket() 함수

 

...더보기

[ 구조 ]

SOCKET socket
{
	int af,
    int type,
    int protocol
};

 

[ 인자 ]

 

af :

    주소 영역을 지정하기 위해 사용. WinSock2.h에 지정 되어있다.

 

AF_UNSPEC 0 정의되지 않은 주소 영역
AF_INET 2 IPv4 주소 영역에서 사용
AF_IPX 6 IPX/SPX 주소 영역에서 사용
AF_APPLETALK 17 AppleTalk 에서 사용
AF_NETBIOS 17 NetBIOS 주소 영역에서 사용
AF_INET6 23 IPv6 주소 영역에서 사용
AF_IRDA 26 Infrared Data Associatino 주소 영역에서 사용
AF_BTH 32 bluetooth 주소 영역에서 사용

 

type :

    소켓의 통신 타입을 지정하기 위해 사용. WinSock2.h에 지정 되어있다.

 

SOCK_STREAM 1 연결지향, 양방향의 TCP/IP 기반의 통신을 위해 사용
SOCK_DGRAM 2 UDP/IP 기반의 통신을 위해 사용
SOCK_RAW 3 IP 헤더를 직접 제어하기 위해 사용

 

protocol :

    호스트간 통신에 사용할 프로토콜을 결정하기 위해 사용.

 

BTHPROTO_RFCOMM 3 Bluetooth Radio Frequency 통신을 위해 사용, SOCK_STREAM type과 함께 사용
IPPROTO_TCP 6 TCP를 사용, AF_INET 혹은 AF_INET6 af와 SOCK_STREAM type과 함께 사용
IPPROTO_UDP 6 UPD를 사용, AF_INET 혹은 AF_INET6 af와 SOCK_DGRAM type과 함께 사용

 


2-2. accept() 함수

 

...더보기

[ 구조 ]

SOCKET accept
{
	__in	SOCKET s,
    __out	sturct sockaddr *addr,
    __inout	int *addrlen
};

 

[ 인자 ]

 

s :

    listen(:4100) 함수의 매개 변수로 Client(사용자)의 연결을 기다리는 소켓 기술자, Client(사용자)의 요청을 듣는다고 하      여 흔히 듣기 소켓이라고 한다.

 

 

addr :

    Client(사용자) 연결을 가져오면, 이 매개 변수에 Client(사용자) 주소 정보를 복사해서 저장. (옵션으로 생략 가능)

 

 

addrlen :

    두 번쨰 매개 변서 addr의 자료 구조의 크기. (옵션으로 생략 가능)

 

 

 


2-3. bind() 함수

 

...더보기

[ 구조 ]

int bind
{
	SOCKET s,
    const struct sockaddr *name,
    int namelen
};

 

[ 인자 ]

 

s :

    socket()으로 생성된 소켓 객체

 

 

name :

    소켓 객체에 부여할 주소 정보를 포함한 구조체

 

 

namelen :

    name의 데이터 길이. byte 단위

 

 


2-4. connect() 함수

 

...더보기

[ 구조 ]

int connect
{
	__in	SOCKET s,
    __in	const struct sockaddr *name,
    __int	int namelen
};

 

[ 인자 ]

 

s :

    연결되지 않은 소켓 기술자

 

 

name :

    연결 정보를 담고 있는 sockaddr(:12) 구조체의 포인터

 

 

namelen :

    sockaddr 구조체 포인터가 가리키는 데이터의 크기

 

 


2-5. listen() 함수

 

...더보기

[ 구조 ]

int listen
{
	__in	SOCKET s,
    __in	int backlog
};

 

[ 인자 ]

 

s :

    socket() 함수로 생성된 SOCKET 객체

 

 

backlog :

    연결 대기열의 크기.

    네트워크 상태와 서비스 종류에 따라서 달라진다. 보통 5정도를 사용.

    윈속 2부터는 SOMAXCONN라는 상수 값을 사용. SOMAXCONN을 지정하면 소켓 서비스 제공자가

    알아서 backlog 값을 설정

 


2-6. send/recv() 함수

 

...더보기

[ 구조 ]

int send
{
	SOCKET		s,
    const char	*buf,
    int			len,
    int			flags
};




int recv
{
	SOCKET	s,
    char	*buf,
    int		len,
    int		flags
};

 

[ 인자 ]

 

s :

   send - 데이터를 보낼 대상의 소켓

   recv - 데이터를 받을 대상의 소켓

 

buf :

   send - 전송할 데이터가 들어있는 버퍼에 대한 포인터

   recv - 들어오는 데이터를 수신 할 버퍼에 대한 포인터

 

len :

   send - buf 매개 변수가 가리키는 버퍼의 데이터 길이 (byte 단위)

   recv - 동일

 

flags :

   send - 호출이 이루어지는 방식을 지정하는 플래그 세트.

   recv - 함수의 동작에 영향을 주는 플래그 세트




3. 프로그래밍 예제

 

main.h ( Server ) 

...더보기
#ifndef SERVER_MAIN_H
#define SERVER_MAIN_H
#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include 

#pragma comment(lib, "ws2_32")

#include 
using std::cout;
using std::endl;

#include 
using std::random_device;
using std::mt19937;
using std::uniform_int_distribution;

#include 
#include 

#include 
#include 

#define SERVER_PORT 11235
#define BUF_SIZE 4096
#define QUEUE_SIZE 10
#define IPAddress "127.0.0.1"

#endif

 

main.cpp ( Server ) 

...더보기
#include "main.h"

int makeRand();

int main(void)
{
	WORD wVersionRequested;
	WSADATA wsaData;
	SOCKADDR_IN servAddr, cliAddr;

	int err;
	int bytesSent;
	char buf[50];

	wVersionRequested = MAKEWORD(1, 1);
	err = WSAStartup(wVersionRequested, &wsaData);

	if (err != 0)
	{
		cout << "WSAStartup error " << WSAGetLastError() << endl;
		WSACleanup();
		return false;
	}
		
	servAddr.sin_family = AF_INET;
	servAddr.sin_port = htons(SERVER_PORT);
	servAddr.sin_addr.s_addr = inet_addr(IPAddress);

	SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	if (s == INVALID_SOCKET)
	{
		cout << "Socket error " << WSAGetLastError() << endl;
		WSACleanup();
		return false;
	}

	int x = bind(s, reinterpret_cast(&servAddr), sizeof(servAddr));
	
	if (x == SOCKET_ERROR)
	{
		cout << "Binding failed. Error code: " << WSAGetLastError() << endl;
		WSACleanup();
		return false;
	}

	cout << "Wating for client..." << endl;

	listen(s, 5);
	
	int xx = sizeof(cliAddr);
	SOCKET s2 = accept(s, reinterpret_cast(&cliAddr), &xx);

	cout << "Connection established . New socket num is " << s2 << endl;

	int iRand = 0;
	int n = 0;

	while (true)
	{
		n = recv(s2, buf, 50, 0);

		if (n <= 0)
		{
			cout << "Got nothing" << endl;
			break;
		}

		buf[n] = 0;

		if (!strcmp(buf, "HELLO"))
		{
			iRand = makeRand();
			auto sRand = std::to_string(iRand);

			cout << "Sending random number " << iRand << " to the client." << endl;
			bytesSent = send(s2, sRand.c_str(), sRand.length(), 0);

			continue;
		}

		std::string sNum(buf);
		
		try
		{
			iRand = stoi(sNum);

			cout << "Server got " << "\"" << iRand << "\"" << endl;

			std::this_thread::sleep_for(std::chrono::seconds(1));

			cout << "Sending \"" << ++iRand << "\"" << "to client" << endl;

			auto sRand = std::to_string(iRand);

			bytesSent = send(s2, sRand.c_str(), sRand.length(), 0);
		}
		catch (const std::invalid_argument &ex)
		{
			std::cerr << "Invalid argument while converting string to number" << endl;
			std::cerr << "Error: " << ex.what() << endl;
			break;
		}
		catch (const std::out_of_range &ex)
		{
			std::cerr << "Invalid argument while converting string to number" << endl;
			std::cerr << "Error: " << ex.what() << endl;
			break;
		}
	}

	closesocket(s);
	WSACleanup();

	return 0;
}

int makeRand()
{
	random_device rd;
	mt19937 rng(rd());
	uniform_int_distribution ud(1, 1024);

	return ud(rng);
}

 

main.h ( Client ) 

...더보기
#ifndef CLIENT_MAIN_H
#define CLIENT_MAIN_H

#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include 

#pragma comment(lib,"ws2_32")

#include 
using std::cout;
using std::endl;

#include 
#include 

#include 
#include 

#define SERVER_PORT 11235
#define BUF_SIZE 4096
#define QUEUE_SIZE 10
#define IPAddress "127.0.0.1"

#endif

 

main.cpp ( Client ) 

...더보기
#include "main.h"

int main(void)
{
	WORD wVersionRequested;
	WSADATA wsaData;
	SOCKADDR_IN target;
	SOCKET s;
	
	int err;
	int bytesSent;
	char buf[50];

	wVersionRequested = MAKEWORD(1, 1);
	err = WSAStartup(wVersionRequested, &wsaData);

	if (err != 0)
	{
		cout << "WSAStartup error: " << WSAGetLastError() << endl;
		WSACleanup();
		return false;
	}

	target.sin_family = AF_INET;
	target.sin_port = htons(SERVER_PORT);
	target.sin_addr.s_addr = inet_addr(IPAddress);

	s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	if (s == INVALID_SOCKET)
	{
		cout << "socket()error : " << WSAGetLastError() << endl;
		WSACleanup();
		return false;
	}

	if (connect(s, reinterpret_cast(&target), sizeof(target)) == SOCKET_ERROR)
	{
		cout << "connect() error : " << WSAGetLastError() << endl;
		cout << "서버 먼저 실행해주세요." << endl;
		WSACleanup();
		return false;
	}

	cout << "Sending HELLO..." << endl;
	
	bytesSent = send(s, "HELLO", strlen("HELLO"), 0);

	int n;
	std::string sRand;
	int iRand;

	while (true)
	{
		try
		{
			n = recv(s, buf, 50, 0);

			if (n <= 0)
			{
				cout << "Got nothing" << endl;
				break;
			}

			buf[n] = 0;

			cout << "Received: " << buf << endl;

			sRand = buf;
			iRand = stoi(sRand);

			std::this_thread::sleep_for(std::chrono::seconds(1));

			cout << "Sending\"" << ++iRand << "\"" << " to client" << endl;

			sRand = std::to_string(iRand);
			bytesSent = send(s, sRand.c_str(), sRand.length(), 0);
		}
		catch (const std::invalid_argument &ex)
		{
			std::cerr << "Invalid argument while converting string to number" << endl;
			std::cerr << "Error: " << ex.what() << endl;
			break;
		}
		catch (const std::out_of_range &ex)
		{
			std::cerr << "Invalid argument while converting string to number" << endl;
			std::cerr << "Error: " << ex.what() << endl;
			break;
		}
	}

	closesocket(s);
	WSACleanup();

	return 0;

}

4. 비고

 

- WinSock2.h 에 해당하는 수 많은 구조체와 클래스, 함수 등등의 반환값과 자세한 설명은

  https://docs.microsoft.com/en-us/windows/win32/api/winsock2/ 여기에서 자세히 확인 가능합니다.

 

 

- 위에 설명되어 있는 것은 예제 공부를 하면서 사용한 간단한 함수들만 추려서 설명한 것입니다.

 

 

- 오타와 잘못된 내용 피드백 모두 받습니다.