게임서버에서는 여러 클라이언트에게 같은 데이터를 보내야할 일이 많아서 SenBuffer안에 SendBufferChunk shared_ptr을 만들어서, 서버가 여러 세션에게 같은 데이터를 보낼 때 효율적으로 했다. 그리고 세션이 SendBuffer shared_ptr queue를 가지고있어서 같은 데이터를 보낼 여러 세션이 있을 때 모든 세션이 send를 마치면 SendBuffer와 SendBufferChunk가 사라지도록 만든 것.
게임서버 강의에서의 Send과정을 분석해보자면
- 보내고자 하는 내용을 protobuf 패킷 클래스에 담는다.
- 클라이언트(서버)패킷핸들러가 protobuf 패킷 클래스를 받아서 SendBufferRef를 만들어서 return한다.
- 세션의 Send(SendBuffeRef) 함수로, 보낼 데이터를 SendBufferQueue에 저장.
- Register 시작 [ 세션이 들고있는 SendEvent의 sendbuffers 벡터로 보낼 데이터를 전부 옮긴다.
- wasbuf 벡터에 각 버퍼 시작주소, writesize 담는다. (뭐지? WSASend쓰려면 필요한것들인듯?)
- WSASend로 보냄. ] Register끝
- 커널이 다 보내고 나면 완료통지를 한다. -> processSend호출.
- 다 보냈으니 세션의 SendBuffer 클리어. 그리고 OnSend호출 (뭐지? 컨텐츠 코드에서 오버로딩이라는데 정확히 뭐하는거지?)
- 샌드큐가 또 찼으면 다시 Register. 없으면 끝.
이것을 epoll에서의 동작으로 바꿔서 정리해보자.
그런데 인증서버는 인증 관련 정보를 다른 클라이언트에게 보낼 일은 절대로 없을테니, 굳이 여러 클라이언트에게 보내기 좋은 구조로 만들 필요가 없을 것 같다. 그리고 일단은 에코서버를 만드는게 목표니까 필요없는 부분은 생략하자.
- [워커스레드] 보내고자 하는 내용을 protobuf 패킷 클래스에 담는다. (생략)
- 클라이언트(서버)패킷핸들러가 protobuf 패킷 클래스를 받아서 SendBufferRef를 만들어서 return한다. (생략)
- 세션의 Send(SendBuffeRef) 함수로 writeBuffers deque에 저장. epoll_ctl로 fd를 EPOLLIN | EPOLLOUT 로 설정
- [메인스레드 ] epoll_wait에서 write이벤트 발생. ProcessWrite 함수를 호출한다.
- writeBuffers의 모든 WriteBuffer를 write할때가지 SSL_write를 호출한다.
- write를 마치고 epoll_ctl로 EPOLLIN 설정
WriteBuffer
SSL_write_ex 함수를 호출해서 10글자를 보내려 헀는데 커널버퍼에 공간이 5밖에 남아있지 않다면 5글자만 쓰고 성공(1)이 리턴된다.
그렇기 때문에 성공 시에도 몇글자를 쓰는데 성공했는지 확인한 뒤에 아직 보내지 못한 데이터를 다음번에 보낼 수 있도록 처리할 수 있게 만들어야했다.
그리고 Session에서 WriteBuffer deque을 들고있다. deque을 쓰는 이유는 pop front와 push_front를 모두 해야하기 때문.
#pragma once
#include "Types.h"
#include <vector>
class WriteBuffer
{
public:
WriteBuffer(BYTE* buffer, int dataLen);
~WriteBuffer();
BYTE* GetBuffer() { return &_buffer[_writePos]; }
int GetDataLen() { return _allocSize - _writePos; }
bool UpdateWritePos(int writeLen);
private:
int _writePos;
int _allocSize;
std::vector<BYTE> _buffer;
};
#include "WriteBuffer.h"
#include <cstring>
#include <iostream>
using namespace std;
WriteBuffer::WriteBuffer(BYTE* buffer, int dataLen) : _writePos(0), _allocSize(dataLen)
{
_buffer.resize(dataLen);
memcpy(&_buffer[0], buffer, dataLen);
}
WriteBuffer::~WriteBuffer() {}
bool WriteBuffer::UpdateWritePos(int writeLen)
{
_writePos += writeLen;
if (_allocSize <= _writePos)
return false;
return true;
}
Send
워커스레드에서 db호출을 마치고 클라이언트에게 보낼 데이터를 만든 뒤 마지막에 사용할 함수.
Send함수로 Session의 writeBuffers에 writeBuffer를 넣고 epoll에 write 이벤트를 등록한다. 클라이언트에게 실제로 보내는건 메인스레드 담당.
void Session::Send(WriteBufferRef writeBuffer)
{
if (ServiceRef service = _service.lock())
{
std::lock_guard<std::mutex> lock(m);
_writeBuffers.push_back(writeBuffer);
ModEvent(EventType::Write);
}
}
ProcessWrite
워커스레드가 보낼 데이터를 넣어두면 메인스레드의epoll이 write이벤트를 감지하여 ProcessWrite함수를 실행시킨다.
지금까지 session의 writeBuffers deque에 쌓인 모든 버퍼를 front부터 차례대로 전송시도하며, 도중에 못하면 어디까지 보냈는지 설정하고, front로 집어넣어 다음에 보낼 때 가장 먼저 보내도록 만들었다.
void Session::ProcessWrite()
{
SSL* ssl = _sslObject->GetSsl();
while (!_writeBuffers.empty())//writebuffers가 빌때까지 반복
{
WriteBufferRef writeBuffer;
BYTE* buffer;
int dataLen;
{
std::lock_guard<std::mutex> lock(m);
writeBuffer = _writeBuffers.front();
buffer = writeBuffer->GetBuffer();
dataLen = writeBuffer->GetDataLen();
_writeBuffers.pop_front();
}
size_t writeLen = 0;
int rtn = SSL_write_ex(ssl, buffer, dataLen, &writeLen);
if (rtn == 0)
{// 실패
int err = SSL_get_error(ssl, rtn);
if (err == SSL_ERROR_WANT_WRITE){//커널 버퍼 공간 부족
return;
} else if (err == SSL_ERROR_ZERO_RETURN) {
//??
ProcessDisconnect(true);
return;
} else {//SSL에 심각한 에러 발생. SSL_shutdown 금지.
ProcessDisconnect(false);
return;
}
}
else if (writeLen != dataLen)
{//일부만 write 성공 시
if (writeBuffer->UpdateWritePos(writeLen) == false)
{
ProcessDisconnect(true);
return;
}
std::lock_guard<std::mutex> lock(m);
_writeBuffers.push_front(writeBuffer);
}
ModEvent(EventType::Read);
}
}'프로젝트 > Project_Island' 카테고리의 다른 글
| 7. Protobuf, PacketHeader, PacketSession (0) | 2026.03.04 |
|---|---|
| 6. 멀티스레드 작업 (0) | 2026.03.03 |
| 4. epoll기반 서버 기초다지기 (0) | 2026.03.01 |
| 3. 인증서버 설계를 위한 공부 (0) | 2026.02.21 |
| 2. Docker 기반 Debian 개발환경 구축(2) +vscode (0) | 2026.02.20 |