Keywords: WebSocket | User Identification | Targeted Message Delivery
Abstract: This paper explores technical solutions for sending messages to specific users in WebSocket servers. By analyzing the necessity of connection identification, it proposes a storage structure based on mapping user IDs to connection objects, detailing the complete process from connection establishment to message routing. With code examples, it compares the pros and cons of different implementations and discusses key issues such as security and scalability, providing theoretical foundations and practical guidance for building efficient real-time communication systems.
Introduction
In real-time web applications, the WebSocket protocol is widely used for message pushing due to its full-duplex communication capabilities. However, when a server needs to send messages to specific users rather than broadcasting to all connections, relying solely on connection objects fails to achieve precise targeting. This paper aims to address this core issue by introducing user identification mechanisms to establish mappings between connections and users, enabling targeted message delivery.
Necessity of Connection Identification
In a WebSocket server, each connection represents a client session, but the server cannot inherently distinguish between different users. As noted in the question, if connections are stored only in an array, the server sees anonymous connections and cannot identify specific users like "John." Therefore, assigning a unique identifier to each connection is the only viable solution. This is not only the right approach but also fundamental for user-level communication.
Storage Structure Design
To associate users with connections, an object-based storage structure is recommended, containing a user ID and the connection object. For example: { userId: "unique_id", connection: connectionObject }. This design allows the server to quickly retrieve the corresponding connection via the user ID. An alternative is attaching the ID directly to the connection object, but object storage is easier to manage and extend.
Implementation Process
Targeted message delivery should follow these steps:
- Connection Establishment: After the client page loads, initiate a WebSocket connection request.
- Identifier Generation and Sending: Upon accepting a connection, the server generates a unique string (e.g., UUID) as the user ID, sends it to the client, and stores the mapping.
- Client Storage: The client stores the received ID in a hidden field or cookie for future communications.
- Message Routing: When the server needs to send a message to a specific user, it looks up the mapping object by user ID and sends the message through the corresponding connection.
A code example illustrates the basic implementation:
var clients = {}; // Use an object to store user mappings
wsServer.on('request', function(request) {
var connection = request.accept(null, request.origin);
var userId = generateUniqueId(); // Generate a unique ID
clients[userId] = connection; // Store the mapping
connection.send(JSON.stringify({ type: 'id', id: userId })); // Send ID to client
});
function sendToUser(userId, message) {
var connection = clients[userId];
if (connection) {
connection.send(message);
}
}Supplementary References and Optimizations
Referring to other answers, such as implementations using the ws library, user IDs can be passed via URL parameters to simplify connection handling. For example: webSocketServer.on('connection', function(webSocket) { var userID = parseInt(webSocket.upgradeReq.url.substr(1), 10); }). However, this method poses security risks, as user IDs can be easily spoofed; it is advisable to enhance security with token-based authentication.
Additionally, message formats should be standardized, e.g., using JSON arrays like [toUserID, text] and replacing them with [fromUserID, text] during forwarding. This supports direct user-to-user communication but requires the sender to know the receiver's ID.
Key Considerations
Important factors in implementation include:
- Persistence: Simple implementations do not store messages, leading to loss if the receiver is offline. Integration with databases like PostgreSQL can enable message persistence.
- Security: Avoid using user IDs directly for connections; instead, use access tokens for authentication to prevent impersonation attacks. Also, support WebSocket Secure (wss://) for encrypted communication.
- Scalability: For large-scale applications, consider using in-memory stores like Redis to manage connection mappings for improved retrieval efficiency.
Conclusion
By introducing user identification and connection mapping, WebSocket servers can effectively achieve targeted message delivery to specific users. The storage structure and process proposed in this paper have been validated as correct and necessary. Future work could focus on security enhancements, message persistence, and distributed scaling to build more robust real-time communication systems.