GameChoi 2023. 1. 10. 19:30

1. TCP

1.1 TCP의 의미

 - 인터넷상에서 데이터를 메세지의 형태로 보내기 위해 IP를 함께 사용하는 프로토콜 

1.2 특징

 - 연결 지향 방식

 - 3-way handshaking과정을 통해 연결 설정 및 4-way handshaking을 통해 해제
   - 3-way handshaking 의미

     - TCP/IP프로토콜을 이용해서 통신을 하는 응용프로그램이 데이터를 전송하기 전에 먼저 정확한

         전송을 보장하기 위해 상대방 컴퓨터와 사전에 세전을 수립하는 과정

   - 3-way handshaking 역할

     - 양쪽 모두 데이터를 전송할 준비가 되었다는 것을 보장

     - 실제로 데이터 전달이 시작하기전에 한쪽이 다른 쪽이 준비되엇다는 것을 알수 있음

   - 4-way handshaking 의미

     - 세션을 종료하기 위해 수행되는 절차

 - 흐름 제어 및 혼잡 제어

 - 높은 신뢰성 보장

 - UDP보다 속도 느림

1.3 TCP SERVER

 - 연결을 위해 할당되는 논리적인 경로가 존재

 - 데이터 경계가 없음

 - 전송 순서가 보장

 - 분실이 일어나면 책임지고 다시 전송 (높은 신뢰성 보장)

 - 패킷에 대한 응답을 해야하므로 성능이 낮음

 

2 TCP SERVER Create

2.1 Winsock

 - Windows Sockets API(WSA)라는 Windows API

   - 인터넷 네트워크 및 소켓과 관련된 함수들을 제공

 - Winsock을 사용할 수 있도록 초기화

   - WSAStartup

     - 첫번 째 인자는 버전을 받음

       - WORD 타입이므로 하위 바이트에 주 버전 번호, 상위 바이트에 부 버전 번호가 저장

     - 두번 째 인자는 윈도우 소켓 구현과 관련된 정보를 저장하는 구조체

     - 성공 시 0을 반환, 실패하면 여러가지 값을 반환

   - WSACleaerup

     - Winsock 종료

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

	::WSACleanup();
}

2.2 Create Socket

 - SOCKET(address family, type, protocol)

   - 소켓 생성 실패 시 INVALID_SOCKET 반환

   - address family

     - AF_INET: IPv4 사용

     - AF_INET6: IPv6 사용

   - type

     - SOCK_STREAM: 연결지향의 TCP/IP 프로토콜 사용

     - SOCK_DGRAM: UDP/IP 사용

   - protocol

     - IPPROTO_TCP

     - IPPROTO_UDP

     - 0

       - 0으로 설정 시 자동으로 서버에 맞는 값 설정

int main()
{
	SOCKET listenSocket = ::socket(AF_INET, SOCK_STREAM, 0);
	if (listenSocket == INVALID_SOCKET) return 0;
}

2.3 소켓에 대한 주소 및 포트 번호 설정

 - 서버의 주소 설정 및 포트 설정

   - SOCKADDR_IN 구조체 사용

 - 서버는 big endian 방식을 사용

   - host to network long: little endian에서 big endian 방식으로 변경

   - host to network short:: big endian에서 little endian 방식으로 변경

int main()
{
	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);
}

2.4 Socket binding

 - 운영체제에 어떤 소켓이 특정 주소와 전송 계층 포트를 쓰겠다고 알려주는 절차

 - 클라이언트가 내 서버로 연결 요청할 떄 상대방의 연결 요청을 받아 여러 개의 프로세스 중 적절한 프로세스로 전달

   - 어떤 프로세스로 전달할지 구분하기 위해 필요한 식별자가 포트 번호

     - 각 프로세스는 중복된 포트 번호를 사용X

 - 수신한 패킷의 목적지 주소 및 포트가 바인딩에 사용 된 IP 주소 및 포트와 일치

   - 운영체제는 해당 패킷을 바인딩 된 소켓으로 넘겨줌

int main()
{
	if (::bind(listenSocket, reinterpret_cast<SOCKADDR*>(&serverAddr), sizeof(serverAddr)) == SOCKET_ERROR) return 0;
}

2.5 Server Open

 - listen 함수를 사용하여 리스닝 상태에 들어간 소켓은 외부에 들어오는 클라이언트 요청을 받을 수 있게 함

int main()
{
	if (::listen(listenSocket, SOMAXCONN) == SOCKET_ERROR) return 0; // SOcket MAX CONNection
}

2.5 클라이언트 접속

 - 소켓을 통해 들어오는 클라이언트의 요청을 accept

   - 성공하면 클라이언트의 데이터 저장

   - 내부적으로 클라이언트와 통신에 사용할 수 있는 소켓을 만들고 반환

 - 클라이언트의 요청이 없을 경우 accept를 호출한 스레드는 block

   - Nonblocking Socket Create (TODO)

int main()
{
	// listen
	while (true)
	{
		SOCKADDR_IN clientAddr;
		::memset(&serverAddr, 0, sizeof(serverAddr));
		int32 addrLength = sizeof(clientAddr);
		
		SOCKET clientSocket = ::accept(listenSocket, reinterpret_cast<SOCKADDR*>(&clientAddr), &addrLength);
		if (clientSocket == INVALID_SOCKET) return 0;
	}
}

2.6 클라이언트와 서버와의 통신

 - recv

   - 클라이언트로 부터 메시지를 받게 되면 메시지의 크기를 반환

   - 데이터가 없을 경우 블로킹

int main()
{
	// listen
	while (true)
	{
		// accept
		while (true)
		{
			char recvBuffer[MAX_BUFFER];
			int32 recvLength = ::recv(clientSocket, recvBuffer, sizeof(recvBuffer), 0);
			if (recvLength <= 0) return;
			cout << recvBuffer << endl;
		}
	}
}

 

3. TCP Client

int main()
{
	// WSA 생성
	WSADATA Init;
	if (::WSAStartup(MAKEWORD(2, 2), &Init) != 0) return 0;

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

	// 서버 주소 및 포트 생성
	SOCKADDR_IN serverAddr;
	::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, reinterpret_cast<SOCKADDR*>(&serverAddr), sizeof(serverAddr)) == SOCKET_ERROR) return 0;

	// 서버와의 통신
	while (true)
	{
		char sendBuffer[MAX_BUFFER] = "TEST SERVER";
		// 서버에게 메세지를 보냄
		int32 sendLength = ::send(clientSocket, sendBuffer, sizeof(sendBuffer), 0);
		if (sendLength <= SOCKET_ERROR) return 0;

		this_thread::sleep_for(1s);
	}
}