基于Django认证的Tornado实现Unity/UE客户端实时聊天方案咨询
Great question! Your approach is totally solid—using Django for authentication and Tornado for real-time TCP chat makes perfect sense, since each tool plays to its strengths. Let's walk through how to implement this with concrete code examples and address any potential pitfalls.
First off, your core idea doesn't need major adjustments. Sharing Django's session state with Tornado to validate TCP clients is a standard pattern for integrating auth-heavy Django apps with real-time services. The key is making sure Tornado can read and validate Django's session data, then managing online clients to handle message routing.
Since Django and Tornado are separate services, they need access to the same database (which you already have set up) and Tornado needs to initialize Django's environment to use its session system.
Configure Tornado to Use Django's Environment
Add this at the top of your Tornado server file to hook into Django's settings and ORM:
# tornado_chat_server.py import os import django from django.utils import timezone from django.contrib.sessions.models import Session from django.contrib.auth.models import User # Point to your Django project's settings os.environ.setdefault("DJANGO_SETTINGS_MODULE", "your_django_project.settings") django.setup()
Session Validation Function
Write a helper to check if the client's session cookie is valid and retrieve the associated user:
def validate_django_session(session_key): try: # Fetch the session from Django's database session = Session.objects.get(session_key=session_key) # Check if the session is expired if session.expire_date < timezone.now(): return None # Get the authenticated user ID from the session data user_id = session.get_decoded().get("_auth_user_id") if not user_id: return None # Return the user object if everything checks out return User.objects.get(id=user_id) except (Session.DoesNotExist, User.DoesNotExist): return None
Use Tornado's TCPServer to handle raw TCP connections. We'll maintain a registry of online clients and handle authentication, message routing, and cleanup.
Full Tornado Server Code
import tornado.ioloop import tornado.tcpserver import tornado.gen import json # Registry to track online clients: {user_id: [stream1, stream2, ...]} online_clients = {} class ChatTCPServer(tornado.tcpserver.TCPServer): @tornado.gen.coroutine def handle_stream(self, stream, address): print(f"New connection from {address}") authenticated_user = None try: # 1. First, receive authentication data from the client # Client sends JSON like: {"session_key": "your_django_session_cookie_value"} auth_raw = yield stream.read_until(b"\n") auth_data = json.loads(auth_raw.decode("utf-8").strip()) session_key = auth_data.get("session_key") if not session_key: yield stream.write(b"ERROR: No session key provided\n") stream.close() return # Validate the session against Django authenticated_user = validate_django_session(session_key) if not authenticated_user: yield stream.write(b"ERROR: Invalid or expired session\n") stream.close() return # 2. Register the client as online user_id = authenticated_user.id if user_id not in online_clients: online_clients[user_id] = [] online_clients[user_id].append(stream) yield stream.write(b"SUCCESS: Authenticated\n") # 3. Handle incoming chat messages while True: message_raw = yield stream.read_until(b"\n") message_data = json.loads(message_raw.decode("utf-8").strip()) # Assume messages include target_user_id and content target_user_id = message_data.get("target_user_id") content = message_data.get("content") if not target_user_id or not content: yield stream.write(b"ERROR: Invalid message format\n") continue # 4. Route the message to all online clients of the target user if target_user_id in online_clients: for target_stream in online_clients[target_user_id]: try: response = json.dumps({ "from_user_id": user_id, "username": authenticated_user.username, "content": content }) + "\n" yield target_stream.write(response.encode("utf-8")) except Exception as e: print(f"Failed to send message to client: {e}") # Clean up dead connections online_clients[target_user_id].remove(target_stream) if not online_clients[target_user_id]: del online_clients[target_user_id] except Exception as e: print(f"Connection error from {address}: {e}") finally: # Clean up when the client disconnects if authenticated_user and authenticated_user.id in online_clients: if stream in online_clients[authenticated_user.id]: online_clients[authenticated_user.id].remove(stream) if not online_clients[authenticated_user.id]: del online_clients[authenticated_user.id] stream.close() print(f"Connection closed from {address}") if __name__ == "__main__": server = ChatTCPServer() server.listen(8666) print("Tornado TCP chat server running on port 8666") tornado.ioloop.IOLoop.current().start()
Unity needs to first get the Django session cookie (via a login request to your Django app), then connect to the Tornado TCP server and authenticate.
C# Client Code Snippet
using System; using System.Net.Sockets; using System.Text; using UnityEngine; public class TCPChatClient : MonoBehaviour { private TcpClient _client; private NetworkStream _stream; private string _djangoSessionKey; // Retrieved from Django login response void Start() { // Assume you've already logged into Django and stored the session key here _djangoSessionKey = "your_django_session_key_here"; ConnectToChatServer(); } async void ConnectToChatServer() { _client = new TcpClient(); try { await _client.ConnectAsync("your-server-ip", 8666); _stream = _client.GetStream(); // Send authentication data var authPayload = JsonUtility.ToJson(new { session_key = _djangoSessionKey }) + "\n"; byte[] authBytes = Encoding.UTF8.GetBytes(authPayload); await _stream.WriteAsync(authBytes, 0, authBytes.Length); // Read authentication response byte[] buffer = new byte[1024]; int bytesRead = await _stream.ReadAsync(buffer, 0, buffer.Length); string response = Encoding.UTF8.GetString(buffer, 0, bytesRead).Trim(); if (response != "SUCCESS: Authenticated") { Debug.LogError($"Auth failed: {response}"); Cleanup(); return; } Debug.Log("Authenticated to chat server"); StartListeningForMessages(); // Example: Send a message to user ID 5 SendChatMessage(5, "Hello from Unity!"); } catch (Exception e) { Debug.LogError($"Connection error: {e.Message}"); Cleanup(); } } async void StartListeningForMessages() { byte[] buffer = new byte[1024]; while (_client.Connected) { try { int bytesRead = await _stream.ReadAsync(buffer, 0, buffer.Length); if (bytesRead == 0) break; string message = Encoding.UTF8.GetString(buffer, 0, bytesRead).Trim(); Debug.Log($"Received message: {message}"); // Parse the JSON message and update your UI here } catch (Exception e) { Debug.LogError($"Listening error: {e.Message}"); break; } } Debug.Log("Disconnected from chat server"); Cleanup(); } async void SendChatMessage(int targetUserId, string content) { if (!_client.Connected) return; var messagePayload = JsonUtility.ToJson(new { target_user_id = targetUserId, content = content }) + "\n"; byte[] messageBytes = Encoding.UTF8.GetBytes(messagePayload); await _stream.WriteAsync(messageBytes, 0, messageBytes.Length); } void Cleanup() { _stream?.Close(); _client?.Close(); } void OnDestroy() { Cleanup(); } }
- Security: For production, use SSL/TLS to encrypt TCP traffic (Tornado supports SSL configuration, and Unity can connect via SSL streams). Plain TCP will expose session cookies to eavesdropping.
- Message Framing: Using
\nas a message delimiter works for simple cases, but if your messages can include newlines, switch to length-prefixed messages (e.g., send the message length first, then the content). - Thread Safety: Tornado runs on a single I/O loop, so the
online_clientsdictionary is safe without locks—just avoid modifying it from external threads. - Alternative to TCP: If you want to simplify auth (since WebSocket automatically passes cookies), you could use Tornado's WebSocket support instead of raw TCP. Unity/UE have great WebSocket libraries, and this would eliminate manual session cookie handling. Your raw TCP approach is valid for low-latency use cases, though.
内容的提问来源于stack exchange,提问作者Olivier Pons




