如何重构TCP Server静态类中的代码重复问题?
Got it, refactoring static classes that handle stateful connections like TCP/WebSocket clients is always tricky—they tend to balloon into unmanageable "god classes" over time, with tangled global state and hard-to-test logic. Let’s walk through a practical, step-by-step refactoring plan that’s proven to work for similar server implementations.
First, Let’s Break Down the Core Problems with the Static Approach
Static classes here introduce:
- Global state coupling: All client connections and callbacks live in shared static collections, making it impossible to run multiple server instances or isolate tests.
- Hard-to-maintain logic: Per-client behavior is mixed with server listener logic, leading to messy, nested code.
- Thread safety headaches: Static collections require careful locking, and debugging race conditions becomes a nightmare.
Refactoring Step-by-Step
1. Extract Per-Client State into a ClientConnection Class
First, move all logic tied to a single client socket into its own non-static class. This encapsulates socket management, callback registrations, and data handling for one client.
public class ClientConnection : IDisposable { private readonly Socket _clientSocket; private readonly List<Action<byte[]>> _onReceiveCallbacks = new(); private readonly List<Action> _onDisconnectCallbacks = new(); private bool _isConnected = true; public ClientConnection(Socket socket) { _clientSocket = socket; _ = StartListeningForData(); // Fire-and-forget async listen loop } // Allow multiple subscribers instead of a single callback public void RegisterOnReceive(Action<byte[]> callback) { lock (_onReceiveCallbacks) { _onReceiveCallbacks.Add(callback); } } public void RegisterOnDisconnect(Action callback) { lock (_onDisconnectCallbacks) { _onDisconnectCallbacks.Add(callback); } } private async Task StartListeningForData() { var buffer = new byte[4096]; try { while (_isConnected) { int bytesRead = await _clientSocket.ReceiveAsync(buffer, SocketFlags.None); if (bytesRead == 0) break; // Client closed connection var receivedData = buffer.Take(bytesRead).ToArray(); // Notify all receive subscribers lock (_onReceiveCallbacks) { foreach (var callback in _onReceiveCallbacks) { callback.Invoke(receivedData); } } } } catch (SocketException) { // Handle unexpected disconnect } finally { NotifyDisconnect(); Dispose(); } } private void NotifyDisconnect() { lock (_onDisconnectCallbacks) { foreach (var callback in _onDisconnectCallbacks) { callback.Invoke(); } } } public void Dispose() { _isConnected = false; _clientSocket.Shutdown(SocketShutdown.Both); _clientSocket.Close(); } }
2. Replace the Static Class with a Server Instance
Create a non-static TcpWebSocketServer class that manages the listener, client connections, and top-level lifecycle. This lets you create multiple server instances, mock dependencies for testing, and isolate state.
public class TcpWebSocketServer : IDisposable { private readonly TcpListener _listener; private readonly ConcurrentBag<ClientConnection> _activeClients = new(); private bool _isRunning = false; // Expose events for external subscribers (instead of static callbacks) public event EventHandler<ClientDataReceivedEventArgs>? ClientDataReceived; public event EventHandler<ClientDisconnectedEventArgs>? ClientDisconnected; public void Start(int port) { _listener = new TcpListener(IPAddress.Any, port); _listener.Start(); _isRunning = true; _ = AcceptClientsLoop(); } private async Task AcceptClientsLoop() { while (_isRunning) { try { Socket clientSocket = await _listener.AcceptSocketAsync(); var clientConnection = new ClientConnection(clientSocket); // Wire up client events to server events clientConnection.RegisterOnReceive(data => ClientDataReceived?.Invoke(this, new ClientDataReceivedEventArgs(clientConnection, data))); clientConnection.RegisterOnDisconnect(() => ClientDisconnected?.Invoke(this, new ClientDisconnectedEventArgs(clientConnection))); _activeClients.Add(clientConnection); } catch (InvalidOperationException) { // Listener was stopped, exit loop break; } } } public void Dispose() { _isRunning = false; _listener.Stop(); // Clean up all active clients foreach (var client in _activeClients) { client.Dispose(); } _activeClients.Clear(); } } // Helper event args classes for type safety public class ClientDataReceivedEventArgs : EventArgs { public ClientConnection Client { get; } public byte[] Data { get; } public ClientDataReceivedEventArgs(ClientConnection client, byte[] data) { Client = client; Data = data; } } public class ClientDisconnectedEventArgs : EventArgs { public ClientConnection Client { get; } public ClientDisconnectedEventArgs(ClientConnection client) => Client = client; }
3. How to Use the Refactored Code
Instead of calling static methods, you now create a server instance and subscribe to its events:
var server = new TcpWebSocketServer(); server.ClientDataReceived += (sender, args) => { Console.WriteLine($"Received data from client: {Encoding.UTF8.GetString(args.Data)}"); }; server.ClientDisconnected += (sender, args) => { Console.WriteLine("Client disconnected"); }; server.Start(8080);
Key Benefits of This Refactor
- Testability: You can mock
ClientConnectionor spin up a test server without affecting global state. - Separation of Concerns: The server handles listening and client lifecycle, while each
ClientConnectionmanages its own socket and callbacks. - Thread Safety: Uses
ConcurrentBagfor client collections and locks per-client callback lists to avoid race conditions. - Scalability: Easily add features like per-client authentication, connection limits, or multiple server instances.
内容的提问来源于stack exchange,提问作者Igneous01

