Saltar a contenido

Sistema de Eventos

Socket.IO se basa en un sistema de eventos bidireccional. Este documento explica todos los eventos disponibles y cómo usarlos.

Tabla de Contenidos

Eventos del Ciclo de Vida

Estos eventos se emiten automáticamente durante el ciclo de vida de la conexión.

OnConnect

Se ejecuta cuando la conexión se establece exitosamente.

client.OnConnect(func() {
  fmt.Println("Conectado al servidor")

  // Aquí puedes emitir eventos iniciales
  client.Emit("join-room", "sala-123")
})

OnDisconnect

Se ejecuta cuando la conexión se cierra. Recibe la razón de la desconexión.

client.OnDisconnect(func(reason string) {
  fmt.Printf("Desconectado: %s\n", reason)

  // Razones comunes:
  // - "io server disconnect" - El servidor cerró la conexión
  // - "io client disconnect" - El cliente cerró la conexión
  // - "ping timeout" - El servidor no respondió al ping
  // - "transport close" - El transporte subyacente se cerró
  // - "transport error" - Error en el transporte
})

OnError

Se ejecuta cuando ocurre un error en la conexión.

client.OnError(func(err error) {
  fmt.Printf("Error de conexión: %v\n", err)

  // Puedes implementar lógica de recuperación aquí
  if err.Error() == "authentication failed" {
    fmt.Println("Verifica tus credenciales")
  }
})

Eventos de Reconexión

Estos eventos te permiten monitorear y controlar el proceso de reconexión automática.

OnReconnectAttempt

Se ejecuta antes de cada intento de reconexión.

client.OnReconnectAttempt(func(attempt int) {
  fmt.Printf("Intentando reconectar... (intento %d)\n", attempt)
})

OnReconnect

Se ejecuta cuando la reconexión es exitosa.

client.OnReconnect(func(attempt int) {
  fmt.Printf("Reconectado exitosamente después de %d intentos\n", attempt)

  // Re-subscribirse a eventos o actualizar estado
  client.Emit("resume-session", lastSessionId)
})

OnReconnectError

Se ejecuta cuando un intento de reconexión falla.

client.OnReconnectError(func(err error) {
  fmt.Printf("Error en reconexión: %v\n", err)
})

OnReconnectFailed

Se ejecuta cuando todos los intentos de reconexión fallan.

client.OnReconnectFailed(func() {
  fmt.Println("Reconexión falló después de todos los intentos")

  // Notificar al usuario o implementar fallback
  notifyUser("No se pudo restablecer la conexión")
})

Ejemplo Completo de Reconexión

client := socketio.New("ws://localhost:3000", socketio.Options{
  ReconnectAttempts: 5,
  ReconnectDelay:    time.Second,
  ReconnectDelayMax: 5 * time.Second,
})

var reconnectCount int

client.OnReconnectAttempt(func(attempt int) {
  reconnectCount = attempt
  fmt.Printf("⏳ Intento %d de reconexión...\n", attempt)
})

client.OnReconnect(func(attempt int) {
  fmt.Printf("✅ Reconectado después de %d intentos\n", attempt)
  reconnectCount = 0
})

client.OnReconnectError(func(err error) {
  fmt.Printf("❌ Error en intento %d: %v\n", reconnectCount, err)
})

client.OnReconnectFailed(func() {
  fmt.Println("💥 Reconexión falló permanentemente")
})

Eventos Personalizados

Puedes escuchar cualquier evento personalizado emitido por el servidor.

Escuchar Eventos

client.On("message", func(data ...interface{}) {
  fmt.Printf("Mensaje recibido: %v\n", data[0])
})

client.On("user-joined", func(data ...interface{}) {
  username := data[0].(string)
  fmt.Printf("%s se unió a la sala\n", username)
})

client.On("notification", func(data ...interface{}) {
  notification := data[0].(map[string]interface{})
  fmt.Printf("Notificación: %s\n", notification["message"])
})

Múltiples Argumentos

Los eventos pueden recibir múltiples argumentos:

client.On("chat-message", func(data ...interface{}) {
  username := data[0].(string)
  message := data[1].(string)
  timestamp := data[2].(float64)

  fmt.Printf("[%s] %s: %s\n",
    time.Unix(int64(timestamp), 0).Format("15:04"),
    username,
    message)
})

Namespaces

Los eventos se pueden escuchar en namespaces específicos:

// Namespace por defecto
client.On("global-event", func(data ...interface{}) {
  fmt.Println("Evento global:", data[0])
})

// Namespace personalizado
chat := client.Of("/chat")
chat.On("message", func(data ...interface{}) {
  fmt.Println("Mensaje de chat:", data[0])
})

admin := client.Of("/admin")
admin.On("alert", func(data ...interface{}) {
  fmt.Println("Alerta de admin:", data[0])
})

Event Handlers

Firma del Handler

Los event handlers tienen la siguiente firma:

type EventHandler func(data ...interface{})

El parámetro data contiene todos los argumentos enviados con el evento.

Type Assertions

Debes hacer type assertions para usar los datos recibidos:

client.On("user-data", func(data ...interface{}) {
  if len(data) == 0 {
    return
  }

  // Type assertion a map
  if userData, ok := data[0].(map[string]interface{}); ok {
    name := userData["name"].(string)
    age := userData["age"].(float64)
    fmt.Printf("Usuario: %s, Edad: %.0f\n", name, age)
  }
})

Manejo de Errores en Handlers

client.On("data", func(data ...interface{}) {
  defer func() {
    if r := recover(); r != nil {
      fmt.Printf("Error en handler: %v\n", r)
    }
  }()

  // Procesar datos
  processData(data[0])
})

Acknowledgments

Los acknowledgments permiten respuestas bidireccionales.

Emitir con Acknowledgment

client.EmitWithAck("get-user", func(response ...interface{}) {
  user := response[0].(map[string]interface{})
  fmt.Printf("Usuario obtenido: %v\n", user)
}, "user-123")

Responder a Acknowledgments

client.On("save-data", func(data ...interface{}) {
  // Último argumento es el callback de acknowledgment
  if len(data) < 2 {
    return
  }

  payload := data[0]

  if ack, ok := data[len(data)-1].(func(...interface{})); ok {
    // Procesar datos
    success := saveToDatabase(payload)

    // Responder al servidor
    if success {
      ack(map[string]interface{}{
        "status": "ok",
        "message": "Datos guardados exitosamente",
      })
    } else {
      ack(map[string]interface{}{
        "status": "error",
        "message": "Error al guardar datos",
      })
    }
  }
})

Timeout de Acknowledgments

Puedes configurar un timeout global para acknowledgments:

client := socketio.New("ws://localhost:3000", socketio.Options{
  AckTimeout: 5 * time.Second, // Timeout de 5 segundos
})

client.EmitWithAck("slow-operation", func(response ...interface{}) {
  if response == nil {
    fmt.Println("Timeout: el servidor no respondió a tiempo")
    return
  }
  fmt.Println("Respuesta:", response[0])
}, "datos")

Eventos Binarios

Para enviar datos binarios (imágenes, archivos, etc.):

// Leer archivo
imageData, err := os.ReadFile("photo.jpg")
if err != nil {
  log.Fatal(err)
}

// Emitir evento binario
client.Of("/").EmitBinary("upload", imageData, map[string]interface{}{
  "filename": "photo.jpg",
  "size": len(imageData),
})

Recibir Datos Binarios

client.On("file", func(data ...interface{}) {
  // Primer argumento es el buffer binario
  if binaryData, ok := data[0].([]byte); ok {
    fmt.Printf("Archivo recibido: %d bytes\n", len(binaryData))

    // Guardar archivo
    err := os.WriteFile("downloaded.dat", binaryData, 0644)
    if err != nil {
      fmt.Println("Error al guardar archivo:", err)
    }
  }

  // Argumentos adicionales
  if len(data) > 1 {
    metadata := data[1].(map[string]interface{})
    fmt.Printf("Metadata: %v\n", metadata)
  }
})

Mejores Prácticas

1. Validar Datos Recibidos

Siempre valida los datos antes de usarlos:

client.On("update", func(data ...interface{}) {
  if len(data) == 0 {
    fmt.Println("Evento sin datos")
    return
  }

  payload, ok := data[0].(map[string]interface{})
  if !ok {
    fmt.Println("Formato de datos inválido")
    return
  }

  // Usar datos validados
  processUpdate(payload)
})

2. Usar Goroutines para Operaciones Largas

No bloquees el event handler con operaciones largas:

client.On("process", func(data ...interface{}) {
  go func() {
    // Operación larga
    result := heavyComputation(data[0])

    // Emitir resultado
    client.Emit("result", result)
  }()
})

3. Limpiar Recursos

Desregistra handlers cuando ya no los necesites:

// El cliente no proporciona Off() directamente
// En su lugar, usa flags o canales para controlar la ejecución

done := make(chan struct{})

client.On("temp-event", func(data ...interface{}) {
  select {
  case <-done:
    return // Ignorar evento
  default:
    processEvent(data)
  }
})

// Cuando termines
close(done)

4. Logging Estructurado

Implementa logging consistente:

func logEvent(event string, data ...interface{}) {
  fmt.Printf("[%s] Evento: %s, Datos: %v\n",
    time.Now().Format("15:04:05"),
    event,
    data)
}

client.On("important-event", func(data ...interface{}) {
  logEvent("important-event", data...)
  processEvent(data)
})

Ver También