(C++ : IOCP-25) TCP 서버 실습

Posted by : at

Category : Cpp   iocp


Client와 Server가 연결되면 아래와 같이 Kernel 영역에 Recv, Send Buffer가 생성된다.


|   Client   |           |   Server   |
|            |           |            |
|            |           |            |
|            |           |            |
--------------           --------------	
|   Kernel   |           |   Kernel   |
|            |           |            |
|  Recv Buf  |           |  Recv Buf  |
|  Send Buf  |           |  Send Buf  |
|            |           |            |
--------------           --------------


// 만약 Send를 한다고 하면 Client가 직접 Server로 보내는 것이 아니라
// Send Buf에 데이터를 채우게 된다.
// 따라서 아래에 나오겠지만 Send를 한다고해서 코드가 블럭이 되지않는다
	// (단순 Send Buf에 데이터를 옮기기만 했기에)

|   Client             |           |   Server   |
|                      |           |            |
|                      |           |            |
|                      |           |            |
------------------------           --------------
|   Kernel             |           |   Kernel   |
|                      |           |            |
|  Recv Buf            |           |  Recv Buf  |
|  Send Buf[SendData]  |           |  Send Buf  |
|                      |           |            |
------------------------           --------------


// 반대로 Recv를 했다면?
// Recv Buffer에서 데이터를 긁어온다.
// 없다면 무한대기를 하게된다.

|   Client   |           |   Server             |
|            |           |                      |
|            |           |                      |
|            |           |                      |
--------------           ------------------------
|   Kernel   |           |   Kernel             |
|            |           |                      |
|  Recv Buf  |           |  Recv Buf[RecvData]  |
|  Send Buf  |           |  Send Buf            |
|            |           |                      |
--------------           ------------------------


// 만약 Send Buf가 다 차버린 상황이라면?
// Send Buf에 자리가 날때까지 코드가 대기하게된다.

|   Client                 |           |   Server   |
|                          |           |            |
|   [SendData]             |           |            |
|                          |           |            |
----------------------------           --------------
|   Kernel                 |           |   Kernel   |
|                          |           |            |
|  Recv Buf                |           |  Recv Buf  |
|  Send Buf[SendDataFull]  |           |  Send Buf  |
|                          |           |            |
----------------------------           --------------

그런데 이런식으로 코드가 블럭되는 형태로 게임을 구현하는게 좋은방법일까??
그에 대한 설명은 다음강좌에서 진행


Client 측 데이터 보내기

#include "pch.h"
#include <iostream>

#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;

	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); // 80 : HTTP

	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
		char sendBuffer[100] = "Hello World!";

        // 100바이트 * 10 = 1000바이트를 거의 한 순간에 보내게 된다.
		for (int32 i = 0; i < 10; i++)
		{
			int32 resultCode = ::send(clientSocket, sendBuffer, sizeof(sendBuffer), 0);
            // 서버에서 recv를 하지 않아도 아래로 내려간다(블락이 안됨)
			if (resultCode == SOCKET_ERROR)
			{
				int32 errCode = ::WSAGetLastError();
				cout << "Send ErrorCode : " << errCode << endl;
				return 0;
			}
		}
		

		cout << "Send Data! Len = " << sizeof(sendBuffer) << endl;

		/*
		char recvBuffer[1000];

		int32 recvLen = ::recv(clientSocket, recvBuffer, sizeof(recvBuffer), 0);
        // 여기서 데이터를 받지 않으면 무한대기
		if (recvLen <= 0)
		{
			int32 errCode = ::WSAGetLastError();
			cout << "Recv ErrorCode : " << errCode << endl;
			return 0;
		}

		cout << "Recv Data! Data = " << recvBuffer << endl;
		cout << "Recv Data! Len = " << recvLen << endl;
		*/

		this_thread::sleep_for(1s);
	}

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

	// 소켓 리소스 반환
	::closesocket(clientSocket);

	// 윈속 종료
	::WSACleanup();
}

Server측 데이터 받기

#include "pch.h"
#include <iostream>
#include "CorePch.h"
#include <atomic>
#include <mutex>
#include <windows.h>
#include <future>
#include "ThreadManager.h"

#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;

	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); // 80 : HTTP

	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
		while (true)
		{
			char recvBuffer[1000];

			this_thread::sleep_for(1s);

            // 100바이트 * 10 = 1000바이트를 거의 한 순간에 보냈는데
            // 100바이트씩 10번 들어올까??
            // Nope! = 1000바이트로 들어온다.
            // RecvBuffer를 이용하기 때문!
            // 여기서 또 생기는 문제는 1000바이트로 들어온 데이터를 어떻게 송신자가 보낸 형태로 잘 자르냐이다(다음에 설명)
			int32 recvLen = ::recv(clientSocket, recvBuffer, sizeof(recvBuffer), 0);
			if (recvLen <= 0)
			{
				int32 errCode = ::WSAGetLastError();
				cout << "Recv ErrorCode : " << errCode << endl;
				return 0;
			}

			cout << "Recv Data! Data = " << recvBuffer << endl;
			cout << "Recv Data! Len = " << recvLen << endl;

			/* 에코서버
            int32 resultCode = ::send(clientSocket, recvBuffer, recvLen, 0);
			if (resultCode == SOCKET_ERROR)
			{
				int32 errCode = ::WSAGetLastError();
				cout << "Send ErrorCode : " << errCode << endl;
				return 0;
			}*/
		}
	}

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


	// 윈속 종료
	::WSACleanup();
}

About Taehyung Kim

안녕하세요? 8년차 현업 C++ 개발자 김태형이라고 합니다. 😁 C/C++을 사랑하며 다양한 사람과의 협업을 즐깁니다. ☕ 꾸준한 자기개발을 미덕이라 생각하며 노력중이며, 제가 얻은 지식을 홈페이지에 정리 중입니다. 좀 더 상세한 제 이력서 혹은 Private 프로젝트 접근 권한을 원하신다면 메일주세요. 😎

Star
Useful Links