1. 내부 주소와 외부 주소

(1) 내부 주소

공유기 등을 이용하여 할당받는 주소(192.168.0.XXX)는 공유기 네트워크(내 집, 직장) 내에서만 사용되는 내부 주소이다.

 

(2) 외부 주소

네트워크 망에서 서비스 공급자(KT, SKT, LG)에 의해 할당받은 IP.

 

 

2. TCP/IP 모델

(1) 피지컬

물리적인 신호 처리가 발생하는 계층.

주요 장치 : 케이블, 허브

 

(2) 데이터 링크(네트워크 인터페이스 계층)

네트워크 내의 경로 설정. 하드웨어와 소프트웨어 간 상호작용.

주요 장치 : 네트워크 어댑터, 스위치, 허브

 

(3) 네트워크(인터넷 계층)

네트워크 간 경로를 설정한다. IPv4, IPv6 프로토콜을 이용하여 패킷을 목적지까지 전송한다.

주요 장치 :  라우터

 

(4) 트랜스포트(전송 계층)

전송 확인과 오류 해결. 포트 번호를 사용하여 응용 프로그램 간의 통신을 구분할 수 있다.

TCP / UDP 프로토콜을 사용한다.

 

(5) 어플리케이션(응용 계층)

유저와 상호작용하여 서비스를 제공한다(유저 인터페이스).

HTTP, FTP, DNS 등의 프로토콜을 사용한다.

 

 

3. TCP vs UDP

(1) 신뢰성

- TCP

흐름 제어, 순서 보장, 재전송 매커니즘 등 신뢰성이 높음

 

- UDP

복구 메커니즘이 존재하지 않아 신뢰성이 낮음

 

 

(2) 연결 지향성 vs 비연결성

- TCP ; 연결 지향성(= 연결형 서비스)

연결을 위해 할당되는 논리적 경로가 존재한다.

-> 패킷의 전송 순서가 보장된다.

 

- UDP ; 비연결성(= 비연결형 서비스)

연결의 개념이 부재한다.

-> 패킷의 전송 순서가 보장되지 않는다.

경계(Boundary)의 개념이 존재한다.

 

 

(3) 속도

- TCP

연결 설정과 신뢰성을 보장하기 위해 속도가 상대적으로 느림.

 

- UDP

연결 설정이 없고 신뢰성이 낮기 때문에 속도가 상대적으로 빠름.

 

 

(4) 데이터 경계

- TCP

경계의 개념이 없으므로 데이터가 쪼개져서 보내질 수 있음.

 

-UDP

경계의 개념이 있어서 데이터가 섞이지 않고 보내짐.

 

(5) 총 정리

- TCP와 UDP 모두 장단점이 있다.

- 데이터 분실에 대한 추가 보정을 UDP에서 실시해 줄 수도 있다(Reliable UDP).

- 대부분의 MMORPG의 서버는 TCP 방식을 사용한다.

- TCP 방식에서는 패킷이 끊겨서 오기 때문에 패킷이 완성되는 순간에 작업을 처리하는 부분이 추가되어야 한다.

ex) Hello, World 2개의 패킷 전송 -> Hello Wo, rld 2개의 패킷 받음

 

 

4. UDP 서버

TCP 서버에서의 Listen과 Accept 단계가 생략된다.

(1) 순서

- 소켓 생성(Socket)

- 소켓에 IP주소와 포트 번호 설정 (Bind)

- 클라이언트와 통신

 

 

(2) 소켓 생성 - UDP 소켓으로 생성하기

int main()
{
	// Windows Socket 시작
	WSADATA wsaData;
	// 2.2버전 사용
	if (::WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		return 0;

	// IPv4를 사용, TCP 사용(<->UDP : SOCK_DGRAM), protocol
	// int32 errorCode = ::WSAGetLastError();
	SOCKET listenSocket = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (listenSocket == INVALID_SOCKET)
		return 0;

SOCK_DGRAM, IPPROTO_UDP 매개변수를 이용하여 소켓의 형태를 UDP로 설정한다.

 

 

(3) IP, 포트 바인딩

	// 바인딩 - Bind
	SOCKADDR_IN serverAddr;
	::memset(&serverAddr, 0, sizeof(serverAddr));
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_addr.s_addr = ::htonl(INADDR_ANY);
	serverAddr.sin_port = ::htons(7777); // 80 : HTTP

	if (::bind(listenSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
		return 0;

기존 코드와 동일하다.

 

 

(4) 비연결성 - Listen과 Accept 단계 제거

(5) 클라이언트와 통신

	// 클라이언트 접속 - Accept
	while (true)
	{
		SOCKADDR_IN clientAddr;
		::memset(&clientAddr, 0, sizeof(clientAddr));
		int32 addrLen = sizeof(clientAddr);

		char recvBuffer[100];
		int32 recvLen = ::recvfrom(listenSocket, recvBuffer, sizeof(recvBuffer), 0, (SOCKADDR*)&clientAddr, &addrLen);

		if (recvLen <= 0)
			return 0;

		cout << "Recv Data : " << recvBuffer << endl;
		cout << "Recv Data Len : " << recvLen << endl;

		this_thread::sleep_for(1s);
	}

	::closesocket(listenSocket);
	// 종료
	::WSACleanup();
}

 

 

5. 클라이언트에서 UDP 서버로 통신하기

(1) 순서

- 소켓 생성(Socket)

- 통신

TCP에서의 서버에 연결을 요청하는 Connect 단계가 제거된다.

 

 

(2) 소켓 생성 - DummyClient

	// 소켓 생성
	SOCKET clientSocket = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (clientSocket == INVALID_SOCKET)
		return 0;

	// 서버의 주소와 포트 번호 설정
	SOCKADDR_IN serverAddr;
	::memset(&serverAddr, 0, sizeof(serverAddr));
	serverAddr.sin_family = AF_INET;
	//serverAddr.sin_addr.s_addr = ::htonl(INADDR_ANY);
	::inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr);
	serverAddr.sin_port = ::htons(7777); // 80 : HTTP

 

서버에서의 코드와 마찬가지로 두 매개변수를 UDP로 수정해준다.

이하의 코드는 동일하다.

 

(3) Connect 제거

(4) 서버와 통신

	while (true)
	{
		char sendBuffer[100] = "클라이언트 메세지 전송 테스트";
		int32 resultCode = ::sendto(clientSocket, sendBuffer, sizeof(sendBuffer), 0, (SOCKADDR*)&serverAddr, sizeof(serverAddr));

		this_thread::sleep_for(1s);
	}

 

 

 

6. TCP 서버에서의 데이터 경계 부재를 극복하는 방법

TCP 통신에서는 패킷의 경계가 없기 때문에 일부분씩 짤려서 오게 될 것이다.

이를 해결하기 위한 방법이 있다.

 

- 패킷의 앞에 헤더를 붙여서 번호와 크기를 정한 다음 데이터를 보낸다.

- 해당 크기만큼 패킷을 받으면 작업을 수행한다.

- 헤더의 무결성을 보장한다(해킹으로 인해 헤더가 변경될 수 있음).