Implementación de Servidor de Juegos con Golang: Sistema de Login y Comunicación de Paquetes

Serie de Desarrollo de Servidor de Juegos

Esta serie presenta el desarrollo completo de un servidor de juegos en Golang utilizando las mejores prácticas de DevOps/GitOps y Kubernetes en la nube.

El objetivo es proporcionar una guía práctica para el desarrollo backend de serviodres de juegos, permitiendo comprender los principios fundamentales de Golang: "Share memory by communication" (compartir memoria mediante comunicación).

Además, este proyecto incluye técnicas de optimización de rendimiento en Linux (herramientas relacionadas con BPF) y prácticas de SRE (Site Reliability Engineering).

Arquitectura del Servidor

  • Comprensión del negocio mediante arquitectura monolítica
  • Transformación a arquitectura distribuida y microservicios
  • Proyecto demo: go-mahjong-server

Conexión del Cliente al Servidor de Juegos

Activación del Modo Debug

Para habilitar logs detallados en el servidor, utilizamos nano.WithDebugMode():

// internal/game/game.go
nano.Listen(addr,
  nano.WithPipeline(pip),
  nano.WithHeartbeatInterval(time.Duration(heartbeat)*time.Second),
  nano.WithLogger(log.WithField("component", "nano")),
  nano.WithSerializer(json.NewSerializer()),
  nano.WithComponents(comps),
  nano.WithDebugMode(),
)

Análisis de Logs de Conexión

Al iniciar el servidor en modo debug, podemos observar el siguiente flujo de conexión:

...
time="2021-02-18T23:01:16+08:00" level=info msg="Nueva sesión establecida: Remoto=192.168.31.125:62569, UltimoTiempo=1613660476" component=nano
time="2021-02-18T23:01:16+08:00" level=info msg="Handshake de sesión Id=2, Remoto=192.168.31.125:62569" component=nano
time="2021-02-18T23:01:16+08:00" level=info msg="Recibido handshake ACK Id=2, Remoto=192.168.31.125:62569" component=nano
time="2021-02-18T23:01:16+08:00" level=info msg="UID=0, Mensaje={Solicitud Manager.Login (195bytes)}, Datos=&{Nombre:J1 Uid:1 UrlAvatar:http://ejemplo.com/avatar Sexo:1 Tarjetas:10 IP:192.168.31.125}" component=nano
time="2021-02-18T23:01:16+08:00" level=info msg="Jugador: 1 conectado: &{Nombre:J1 Uid:1 UrlAvatar:http://ejemplo.com/avatar Sexo:1 Tarjetas:10 IP:192.168.31.125}"
time="2021-02-18T23:01:16+08:00" level=info msg="Jugador: 1 no está en línea, creando nuevo jugador"
time="2021-02-18T23:01:16+08:00" level=info msg="Agregar sesión al grupo _SYSTEM_MESSAGE_BROADCAST, ID=2, UID=1" component=nano
time="2021-02-18T23:01:16+08:00" level=info msg="Tipo=Respuesta, ID=2, UID=1, MID=1, Datos=&{Uid:1 Nickname:J1 UrlAvatar:http://ejemplo.com/avatar Sexo:1 Tarjetas:10}" component=nano
time="2021-02-18T23:01:16+08:00" level=info msg="[SQL] SELECT `id`, `algo`, `hash`, `salt`, `role`, `status`, `is_online`, `last_login_at`, `priv_key`, `pub_key`, `coin`, `register_at`, `first_recharge_at`, `debug` FROM `user` WHERE `id`=? LIMIT 1 []interface {}{1}" component=model orm=xorm
time="2021-02-18T23:01:16+08:00" level=info msg="Tipo=Push, ID=2, UID=1, Route=onCoinChange, Datos=&{Monedas:10}" component=nano
time="2021-02-18T23:01:16+08:00" level=info msg="Recibido handshake ACK Id=2, Remoto=192.168.31.125:62569" component=nano
time="2021-02-18T23:01:16+08:00" level=info msg="UID=1, Mensaje={Solicitud DeskManager.MesasIncompletas (2bytes)}, Datos=[91 93]" component=nano
time="2021-02-18T23:01:16+08:00" level=debug msg="DeskManager.MesasIncompletas: El jugador no está en una sala" player=1
time="2021-02-18T23:01:16+08:00" level=info msg="Tipo=Respuesta, ID=2, UID=1, MID=2, Datos=&{Existe:false InfoMesa:{NumMesa: CreadoEn:0 Creador:0 Titulo: Desc: Estado:creado Rondas:0 Modo:0}}" component=nano
...

Interpretación del Proceso de Conexión

El flujo de conexión muestra claramente tres eventos importantes:

  1. Nueva sesión establecida: El servidor acepta una nueva conexión entrante
  2. Handshake de sesión: El cliente inicia el proceso de handshake con el servidor
  3. Handshake ACK: Confirmación exitosa del handshake entre cliente y servidor

Este es el proceso de handshake entre el cliente de juegos y el servidor utilizando el framework Nano.

Una vez establecida la conexión subyacente, el cliente envía una solicitud de handshake al servidor con los datos necesarios. El servidor valida estos datos y responde. Si el handshake es exitoso, el cliente envía una confirmación (ACK) al servidor, finalizando así la fase de handshake.

Mensaje de Login

Después del handshake, observamos el siguiente mensaje:

UID=0, Mensaje={Solicitud GestorAuth.IniciarSesion (195bytes)}, Datos=&{Nombre:J1 Uid:1 UrlAvatar:http://ejemplo.com/avatar Sexo:1 Tarjetas:10 IP:192.168.31.125}

Esto indica que el cliente de juegos envía un paquete de tipo Solicitud a la ruta GestorAuth.IniciarSesion del servidor.

Implementación del Handler de Login

Análisis de la Lógica de Negocio

El proceso de login incluye los siguientes pasos:

  1. Asociar el UID del jugador a la sesión actual
  2. Consultar la lista de jugadores
  3. Si el jugador no está en línea:
    • Inicializar los datos del jugador
    • Asociar la sesión al jugador
    • Sincronizar tarjetas de forma asíncrona desde la base de datos
    • Agregar el jugador a la lista de gestión
  4. Si el jugador ya está en línea:
    • Restablecer la sesión anterior
    • Asociar la nueva sesión
  5. Agregar al canal de broadcast
  6. Enviar respuesta al cliente

Protocolo de Comunicación

Definición de las estructuras de datos para el login:

// protocol/login.go

type SolicitudLoginServidor struct {
    Nombre    string `json:"nombre"`
    Uid       int64  `json:"uid"`
    UrlAvatar string `json:"urlAvatar"`
    Sexo      int    `json:"sexo"`       // [0]desconocido [1]masculino [2]femenino
    Tarjetas  int    `json:"tarjetas"`
    IP        string `json:"ip"`
}

type RespuestaLoginServidor struct {
    Uid       int64  `json:"uid"`
    Apodo     string `json:"apodo"`
    UrlAvatar string `json:"urlAvatar"`
    Sexo      int    `json:"sexo"`
    Tarjetas  int    `json:"tarjetas"`
}

Resumen del Proceso

El flujo de comunicación entre el cliente y el servidor de juegos sigue un patrón establecido:

  1. Establecimiento de conexión TCP
  2. Proceso de handshake
  3. Envío de credenciales del jugador
  4. Validación y creación/recuperación de sesión
  5. Sincronización de estado del jugador
  6. Confirmación al cliente

Este patrón garantiza una comunicación confiable y segura entre el cliente y el servidor, permitiendo que los jugadores puedan unirse a partidas de mahjong de manera eficiente.

Etiquetas: golang nano-framework game-server DevOps Kubernetes

Publicado el 7-11 07:59