Event System¶
Socket.IO is based on a bidirectional event system. This document explains all available events and how to use them.
Table of Contents¶
Lifecycle Events¶
These events are automatically emitted during the connection lifecycle.
OnConnect¶
Executed when the connection is successfully established.
client.OnConnect(func() {
fmt.Println("Connected to server")
// You can emit initial events here
client.Emit("join-room", "room-123")
})
OnDisconnect¶
Executed when the connection closes. Receives the disconnection reason.
client.OnDisconnect(func(reason string) {
fmt.Printf("Disconnected: %s\n", reason)
// Common reasons:
// - "io server disconnect" - Server closed the connection
// - "io client disconnect" - Client closed the connection
// - "ping timeout" - Server did not respond to ping
// - "transport close" - Underlying transport closed
// - "transport error" - Transport error
})
OnError¶
Executed when a connection error occurs.
client.OnError(func(err error) {
fmt.Printf("Connection error: %v\n", err)
// You can implement recovery logic here
if err.Error() == "authentication failed" {
fmt.Println("Check your credentials")
}
})
Reconnection Events¶
These events allow you to monitor and control the automatic reconnection process.
OnReconnectAttempt¶
Executed before each reconnection attempt.
client.OnReconnectAttempt(func(attempt int) {
fmt.Printf("Attempting to reconnect... (attempt %d)\n", attempt)
})
OnReconnect¶
Executed when reconnection is successful.
client.OnReconnect(func(attempt int) {
fmt.Printf("Successfully reconnected after %d attempts\n", attempt)
// Re-subscribe to events or update state
client.Emit("resume-session", lastSessionId)
})
OnReconnectError¶
Executed when a reconnection attempt fails.
OnReconnectFailed¶
Executed when all reconnection attempts fail.
client.OnReconnectFailed(func() {
fmt.Println("Reconnection failed after all attempts")
// Notify user or implement fallback
notifyUser("Could not re-establish connection")
})
Complete Reconnection Example¶
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("⏳ Reconnection attempt %d...\n", attempt)
})
client.OnReconnect(func(attempt int) {
fmt.Printf("✅ Reconnected after %d attempts\n", attempt)
reconnectCount = 0
})
client.OnReconnectError(func(err error) {
fmt.Printf("❌ Error on attempt %d: %v\n", reconnectCount, err)
})
client.OnReconnectFailed(func() {
fmt.Println("💥 Reconnection failed permanently")
})
Custom Events¶
You can listen to any custom event emitted by the server.
Listening to Events¶
client.On("message", func(data ...interface{}) {
fmt.Printf("Message received: %v\n", data[0])
})
client.On("user-joined", func(data ...interface{}) {
username := data[0].(string)
fmt.Printf("%s joined the room\n", username)
})
client.On("notification", func(data ...interface{}) {
notification := data[0].(map[string]interface{})
fmt.Printf("Notification: %s\n", notification["message"])
})
Multiple Arguments¶
Events can receive multiple arguments:
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¶
Events can be listened to on specific namespaces:
// Default namespace
client.On("global-event", func(data ...interface{}) {
fmt.Println("Global event:", data[0])
})
// Custom namespace
chat := client.Of("/chat")
chat.On("message", func(data ...interface{}) {
fmt.Println("Chat message:", data[0])
})
admin := client.Of("/admin")
admin.On("alert", func(data ...interface{}) {
fmt.Println("Admin alert:", data[0])
})
Event Handlers¶
Handler Signature¶
Event handlers have the following signature:
The data parameter contains all arguments sent with the event.
Type Assertions¶
You must perform type assertions to use the received data:
client.On("user-data", func(data ...interface{}) {
if len(data) == 0 {
return
}
// Type assertion to map
if userData, ok := data[0].(map[string]interface{}); ok {
name := userData["name"].(string)
age := userData["age"].(float64)
fmt.Printf("User: %s, Age: %.0f\n", name, age)
}
})
Error Handling in Handlers¶
client.On("data", func(data ...interface{}) {
defer func() {
if r := recover(); r != nil {
fmt.Printf("Handler error: %v\n", r)
}
}()
// Process data
processData(data[0])
})
Acknowledgments¶
Acknowledgments enable bidirectional responses.
Emitting with Acknowledgment¶
client.EmitWithAck("get-user", func(response ...interface{}) {
user := response[0].(map[string]interface{})
fmt.Printf("User retrieved: %v\n", user)
}, "user-123")
Responding to Acknowledgments¶
client.On("save-data", func(data ...interface{}) {
// Last argument is the acknowledgment callback
if len(data) < 2 {
return
}
payload := data[0]
if ack, ok := data[len(data)-1].(func(...interface{})); ok {
// Process data
success := saveToDatabase(payload)
// Respond to server
if success {
ack(map[string]interface{}{
"status": "ok",
"message": "Data saved successfully",
})
} else {
ack(map[string]interface{}{
"status": "error",
"message": "Error saving data",
})
}
}
})
Acknowledgment Timeout¶
You can configure a global timeout for acknowledgments:
client := socketio.New("ws://localhost:3000", socketio.Options{
AckTimeout: 5 * time.Second, // 5 second timeout
})
client.EmitWithAck("slow-operation", func(response ...interface{}) {
if response == nil {
fmt.Println("Timeout: server did not respond in time")
return
}
fmt.Println("Response:", response[0])
}, "data")
Binary Events¶
To send binary data (images, files, etc.):
// Read file
imageData, err := os.ReadFile("photo.jpg")
if err != nil {
log.Fatal(err)
}
// Emit binary event
client.Of("/").EmitBinary("upload", imageData, map[string]interface{}{
"filename": "photo.jpg",
"size": len(imageData),
})
Receiving Binary Data¶
client.On("file", func(data ...interface{}) {
// First argument is the binary buffer
if binaryData, ok := data[0].([]byte); ok {
fmt.Printf("File received: %d bytes\n", len(binaryData))
// Save file
err := os.WriteFile("downloaded.dat", binaryData, 0644)
if err != nil {
fmt.Println("Error saving file:", err)
}
}
// Additional arguments
if len(data) > 1 {
metadata := data[1].(map[string]interface{})
fmt.Printf("Metadata: %v\n", metadata)
}
})
Best Practices¶
1. Validate Received Data¶
Always validate data before using it:
client.On("update", func(data ...interface{}) {
if len(data) == 0 {
fmt.Println("Event with no data")
return
}
payload, ok := data[0].(map[string]interface{})
if !ok {
fmt.Println("Invalid data format")
return
}
// Use validated data
processUpdate(payload)
})
2. Use Goroutines for Long Operations¶
Don't block the event handler with long operations:
client.On("process", func(data ...interface{}) {
go func() {
// Long operation
result := heavyComputation(data[0])
// Emit result
client.Emit("result", result)
}()
})
3. Clean Up Resources¶
Unregister handlers when you no longer need them:
// Client doesn't provide Off() directly
// Instead, use flags or channels to control execution
done := make(chan struct{})
client.On("temp-event", func(data ...interface{}) {
select {
case <-done:
return // Ignore event
default:
processEvent(data)
}
})
// When finished
close(done)
4. Structured Logging¶
Implement consistent logging:
func logEvent(event string, data ...interface{}) {
fmt.Printf("[%s] Event: %s, Data: %v\n",
time.Now().Format("15:04:05"),
event,
data)
}
client.On("important-event", func(data ...interface{}) {
logEvent("important-event", data...)
processEvent(data)
})