구조체로 책임 나누기
왜 나누는데요?
- 현재까지 코드는 진짜 쌩 버퍼 주고 함수를 일일히 거쳐서 내용을 직접 해석함
- 문제는 그러면 중간 중간에 내가 어디까지 파싱했는지 알 수가 없음
- 그리고 함수로만 존재하다보니 워크플로우 파악이 너무 복잡함
- 그래서 구조체를 만들어서 상태를 표현하고 책임을 분리할 것임
어떻게 나누나요?
- PacketReader : 바이트를 받아서 패킷 단위로 잘라주는 구조체
- PacketWriter : 패킷을 받아서 바이트로 바꿔주는 구조체
- Packet : “내용이 있는” 구조체 (메시지 타입과 페이로드)
- Connection : 클라이언트 하나를 표현하는 구조체 - 연결과 reader/writer를 가짐
- Dispatcher : Packet의 메시지 타입을 보고 Handler를 호출하는 구조체
- Handler : Dispatcher에 의해 호출됨. 게임 로직을 실행하는 구조체
- Session : Connection에 대한 정보를 포함하는 구조체 (ID, Player)
전체 구조도 (확정은 아님)

- 서버 디렉토리 구조 -
Server/
├─ go.mod
├─ main.go
│
├─ protocol/
│ └─ game.proto
│ └─ game.pb.go
│
├─ packet/
│ ├─ reader.go
│ └─ writer.go
│
├─ network/
│ ├─ connection.go
│ └─ server.go
│
├─ dispatch/
│ └─ dispatcher.go
│
└─ handler/
└─ ping.go
PacketReader
- 바이트를 받아서 패킷 단위로 잘라주는 구조체
- 구조체 내부에 바이트 배열을 가지고 있어서, 완성되지 못한 패킷을 가지고 다음 스트림을 받을 수 있음
type PacketReader struct {
buffer []byte // 참고 : 변수명이 대문자로 시작하면 public, 아니면 private임
}
// PacketReader 초기화. 빈 버퍼를 포함함
func NewPacketReader() *PacketReader {
return &PacketReader{buffer: make([]byte, 0)}
}
// PacketReader에 남은 버퍼에 새 데이터를 붙여서 패킷화 시도
func (r *PacketReader) Read(data []byte) ([][]byte, error) {
frames, remain := ParseFrames(append(r.buffer, data...))
r.buffer = remain
return frames, nil
}
PacketWriter
- 패킷을 받아서 바이트 배열로 바꿔주는 구조체
- 얘는 구조체 안에 딱히 뭐가 있지는 않음
type PacketWriter struct{}
// PacketWriter 초기화
func NewPacketWriter() *PacketWriter {
return &PacketWriter{}
}
// 직렬화 된 패킷 반환
func (w *PacketWriter) Write(pkt *protocol.Packet) ([]byte, error) {
return SerializePacket(pkt)
}
// 패킷 구조체를 바이트 배열로 변경
func SerializePacket(pkt *protocol.Packet) ([]byte, error) {
// 1. 내부에서 돌던 패킷을 Protobuf에 규정한 패킷으로 바꿈
protoPkt := &protocol.Packet{
Type: pkt.Type,
Payload: pkt.Payload,
}
// 2. 패킷을 바이트 배열로 변경
packetBytes, err := proto.Marshal(protoPkt)
if err != nil {
return nil, err
}
// 3. 빈 바이트 배열을 만듬
// 이때 빈 바이트 배열의 길이는 4 + 바이트 배열로 변경한 패킷의 길이
frame := make([]byte, 4+len(packetBytes))
// 4. 빈 바이트 배열의 앞 4바이트를 바이트 배열로 변경한 패킷 길이로 정함
binary.BigEndian.PutUint32(frame[:4], uint32(len(packetBytes)))
// 5. 뒤에 바이트 배열로 변경한 패킷을 붙임
copy(frame[4:], packetBytes)
return frame, nil
}
다음에 할 것
- Packet + codec 폴더에 있어야 하는 건 다 만들었음
- 그러니 이번에는 dispatch와 handler를 만들어 볼 예정
- 이에 더해 시간 되면 connection 구조체까지 만들어서 간단하게 핑퐁 테스트 할 예정