WSASend로 packet을 전송하는 과정은 2가지 과정으로 나뉜다.

 

우리가 작성하는 ChattingServer에서 Session은 WSASend를 한번만 걸 수 있다.

즉, 하나의 워커 쓰레드가 WSASend를 걸면 해당 WSASend가 완료될때까지 다른 워커 쓰레드에서는 WSASend를 걸 수 없다.

SendPacket은 여러 쓰레드에서 호출할 수 있기 때문에 SendPacket에서는 packet을 큐잉을 중점으로 구현한다. ( 물론 SendPost를 호출해 WSASend 걸기를 시도 한다 )

1️⃣ SendPacket

WSASend를 걸 session을 찾고, 전송할 packet을 큐잉하고, WSASend를 건다.

 

void CoreNetwork::SendPacket(__int64 sessionId, Packet* packet)
{	
	Session* sendSession = FindSession(sessionId);
	if (sendSession == nullptr)
	{
		return;
	}

	packet->Encode();

	// 패킷 큐잉
	sendSession->sendQueue.push(packet);

	// WSASend 등록
	SendPost(sendSession);

	ReturnSession(sendSession);
}

 

2️⃣ SendPost

WSASend를 걸기전 isSend를 1로 바꾸면서 진입하고, 다른 워커쓰레드에서 isSend가 false가 될때까지 WSASend를 걸지 못하도록 막는다. 보내려고 진입했으나 sendUseSize가 0일 경우 바로나가지 않고 sendQueue의 사이즈를 다시 한번 더 확인해서 SendPost를 다시 진행한다. 

 

이후 전송할 packet을 WSABUF에 담고 WSASend를 건다.

 

void CoreNetwork::SendPost(Session* sendSession)
{
	int sendUseSize;
	int sendCount = 0;
	WSABUF sendBuf[500];

	do
	{
		// Session의 isSend를 1로 바꾸면서 진입한다.
		// 다른 워커 쓰레드가 WSASend를 하지 않도록 막는다.
		if (InterlockedExchange(&sendSession->isSend, 1) == 0)
		{
			sendUseSize = sendSession->sendQueue.size();

			if (sendUseSize == 0)
			{
				InterlockedExchange(&sendSession->isSend, 0);

				if (!sendSession->sendQueue.empty())
				{
					continue;
				}
				else
				{
					return;
				}
			}			
		}
		else
		{
			return;
		}
	} while (0);

	// session이 보내야할 packet의 개수를 지정한다.
	sendSession->sendPacketCount = sendCount = sendUseSize;

	// 보내야할 패킷의 개수만큼 sendQueue에서 패킷을 꺼내서 WSABUF에 넣는다.
	for (int i = 0; i < sendCount; i++)
	{
		if (sendSession->sendQueue.size() == 0)
		{
			break;
		}

		InterlockedIncrement(&_sendPacketTPS);

		Packet* packet = sendSession->sendQueue.front();
		sendSession->sendQueue.pop();
		
		sendBuf[i].buf = packet->GetHeaderBufferPtr();
		sendBuf[i].len = packet->GetUseBufferSize();

		sendSession->sendPacket[i] = packet;
	}

	// WSAsend를 걸기 전에 한번 청소해준다.
	memset(&sendSession->sendOverlapped, 0, sizeof(OVERLAPPED));

	// IOCount를 1 증가시켜 해당 session이 release 대상이 되지 않도록 한다.
	InterlockedIncrement64(&sendSession->IOBlock->IOCount);
		
	int WSASendRetval = WSASend(sendSession->clientSocket, sendBuf, sendCount, NULL, 0, (LPWSAOVERLAPPED)&sendSession->sendOverlapped, NULL);
	if (WSASendRetval == SOCKET_ERROR)
	{
		DWORD error = WSAGetLastError();
		if (error != ERROR_IO_PENDING)
		{
			if (InterlockedDecrement64(&sendSession->IOBlock->IOCount) == 0)
			{
				ReleaseSession(sendSession);
			}
		}
	}
}

 

+ Recent posts