You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何重构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 ClientConnection or spin up a test server without affecting global state.
  • Separation of Concerns: The server handles listening and client lifecycle, while each ClientConnection manages its own socket and callbacks.
  • Thread Safety: Uses ConcurrentBag for 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 06:58:09