C++ TCP 서버 실습

정은성·2023년 5월 28일
1
post-thumbnail

※ Rookiss님의 [C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버 강의를 보고 정리한 글입니다.

이젠 소켓 연결을 마무리 한 후 송/수신을 해볼 것이다.

송신

char sendBuffer[100] = "Hello World"; // 보낼 정보 세팅

// send(소켓, 보낼 버퍼, 버퍼 크기, 시작 인덱스)
int32 resultCode = ::send(clientSocket, sendBuffer, sizeof(sendBuffer), 0);
if (resultCode == SOCKET_ERROR) {
	int32 errCode = ::WSAGetLastError();
	cout << "Send ErrorCode: " << errCode << endl;
	return 0;
}

수신

송신은 자신이 몇 바이트를 보내는 지 알 수 있어서 그에 맞게 배열 크기를 설정 할 수 있었다.

하지만 수신의 경우 상대방이 어느 크기의 데이터를 보낼 지 알 수 없으므로 최대한 많이 설정해준다.

char recvBuffer[1000]; // 몇 바이트가 올 지 예상 불가 => 최대한 넉넉하게
int32 recvLen = ::recv(clientSocket, recvBuffer, sizeof(recvBuffer), 0); // listenSocket은 클라의 접속을 받는 소켓, accept해준 client 소켓을 사용해준다.

if (recvLen <= 0) { // -1 => 소켓 에러
	int32 errCode = ::WSAGetLastError();
	cout << "Recv ErrorCode: " << errCode << endl;
	return 0;
}

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

recv와 send는 블로킹 함수다. ⇒ 끝나기 전까진 다음으로 넘어가지 않는다.

하지만 막상 실해해보면 recv에선 막히지만 send는 막히지 않고 넘어간다. ⇒ 블로킹 함수가 아닌가?

그것은 send와 recv의 동작 원리 때문이다.

send와 recv는 바로 받아지고, 보내지는 것이 아닌 커널 쪽에있는 RecvBuffer와 SendBuffer를 통해 간다. SendBuffer에 저장해 두었다가 Recv요청이 들어오면 SendBuffer에 있는 데이터를 꺼내주고 RecvBuffer에 데이터가 들어오면 데이터를 받는 것이다.

send는 바로 상대에게 데이터를 보내는 것이 아닌 자신의 sendBuffer에 저장만 해두고 나오기 때문에 블로킹이 되지않는 것이다. 하지만 SendBuffer가 꽉 찼다면 SendBuffer가 비워질 때 까지 기다릴 것이다.(블로킹)

반대로 recv는 RecvBuffer에서 데이터를 가져오는 과정이다. → 없다면 블로킹

이러한 함수들을 사용했지만 해야할 것이 많은 게임 서버에서 블로킹 함수들은 좋지않다.

이러한 문제에 대책으로 스레드를 여러개하면? → 좋지않다. 유저 수 만큼 스레드를 배정하는 것은 유저 수가 많아지면 말이안된다.

데이터의 사이즈

client에서 100바이트를 10번 보내보자.

char sendBuffer[100] = "Hello World";
	
for (int32 i = 0; i < 10; i++) {
	int32 resultCode = ::send(clientSocket, sendBuffer, sizeof(sendBuffer), 0);
	if (resultCode == SOCKET_ERROR) {
		int32 errCode = ::WSAGetLastError();
		cout << "Send ErrorCode: " << errCode << endl;
		return 0;
	}
}

그리고 server에선 1초 정도 기다렸다 이것을 한번에 받아보자.

char recvBuffer[1000];

this_thread::sleep_for(1s);
int32 recvLen = ::recv(clientSocket, recvBuffer, sizeof(recvBuffer), 0); // listenSocket은 클라의 접속을 받는 소켓, accept해준 client 소켓을 사용해준다.

if (recvLen <= 0) { // -1 => 소켓 에러
	int32 errCode = ::WSAGetLastError();
	cout << "Recv ErrorCode: " << errCode << endl;
	return 0;
}

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

이렇게 하면 데이터는 1000바이트가 한번에 들어오게된다.

받은 입장에선 보낸사람이 어떤식으로 데이터를 나눠 보냈는지 모른다.

→ 그래서 나중에 데이터 안에 크기를 포함해서 보내줘야한다.

0개의 댓글