限制TcpListener的连接数量和时间。如何正确关闭/停止TcpListener和TcpClient?

3

我有一些代码:

    protected TcpListener ClientListener;
    protected TcpClient ClientSocket;
    protected Thread th1;
    public static string server_ip = string.Empty;
    public static string server_port = string.Empty;

    internal void start_potok()
    {
        Protection.Ban.ban_del();
        th1 = new Thread(start_listener);
        th1.IsBackground = true;
        th1.Start();
    }

    internal void stop_potok()
    {
        th1.Abort();
        if (ClientSocket != null)
        {
            ClientSocket.Close();
        }
        ClientListener.Stop();
    }

    private void start_listener() 
    {
        try
        {
            ClientListener = new TcpListener(IPAddress.Parse(server_ip), Convert.ToInt32(server_port));
            ClientListener.Start();
            ClientSocket = default(TcpClient);
            while (true)
            {
                ClientSocket = ClientListener.AcceptTcpClient();
                client_connected(ClientSocket);
            }
        catch (ThreadAbortException ex)
        {
            //not catch this exception
        }
        catch (Exception ex)
        {
            MessageBox.Show("Error: " + ex.Message);
        }
    }

    private void client_connected(TcpClient client)
    {
        //check bans and other
        LoginClientProc lcp = new LoginClientProc(client);
    }

听取的类:

class LoginClientProc
{
    internal EndPoint address;
    internal TcpClient client;
    internal NetworkStream stream;
    private byte[] buffer;

    internal LoginClientProc(TcpClient Client)
    {
        address = Client.Client.RemoteEndPoint;
        client = Client;
        stream = Client.GetStream();

        new System.Threading.Thread(read).Start();
    }

    void read()
    {
        try
        {
            buffer = new byte[2];
            stream.BeginRead(buffer, 0, 2, new AsyncCallback(OnReceiveCallbackStatic), null);
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

    private void OnReceiveCallbackStatic(IAsyncResult result)
    {
        int rs = 0;
        try
        {
            rs = stream.EndRead(result);

            if (rs > 0)
            {
                short Length = BitConverter.ToInt16(buffer, 0);
                buffer = new byte[Length - 2];
                stream.BeginRead(buffer, 0, Length - 2, new AsyncCallback(OnReceiveCallback), result.AsyncState);
            }
            else
                //0 = client close connection
        }
        catch (Exception s)
        {
        }
    }

    private void OnReceiveCallback(IAsyncResult result)
    {
        stream.EndRead(result);

        byte[] buff = new byte[buffer.Length];
        buffer.CopyTo(buff, 0);
        if (!verify_packet)
        {
            //Exception
        }
        else
        {
            handle(buff);
            new System.Threading.Thread(read).Start();
        }
    }

    private void handle(byte[] buff)
    {
        byte id = buff[0];
        switch (id)
        {
            //cases for headers packets
            default:
                //unknown packet
                //Here need correct Close connection
                break;
        }
       //response for packet
}

所以,我有一些问题:

  1. 如何限制客户端连接数量?(例如不超过10个用户)
  2. 如何限制客户端连接的时间(生命周期)?(例如不超过30秒)
  3. 如何正确停止监听线程(TcpListener、TcpClient),当应用程序关闭时?
  4. 当服务器收到未知数据包时,如何正确关闭TcpClient?

感谢您的回答!

2个回答

3
您可能需要使用队列。这里是可能的服务器端解决方案草图:
class Program
{
    Queue<Connection> queue = new Queue<Connection>();

    TcpListener listener;

    ManualResetEvent reset = new ManualResetEvent(true);

    static void Main(string[] args)
    {
        new Program().Start();
    }

    void Start()
    {
        new Thread(new ThreadStart(HandleConnection)).Start();

        while (true)
        {
            while (queue.Any())
            {
                var connection = queue.Dequeue();

                connection.DoStuff();

                if(!connection.Finished)
                    queue.Enqueue(connection);
            }

            reset.WaitOne();
        }
    }

    static readonly byte[] CONNECTION_REFUSED = Encoding.ASCII.GetBytes("Cannot accept a connection right now.");
    static readonly int CONNECTION_REFUSED_LENGTH = CONNECTION_REFUSED.Length;

    void HandleConnection()
    {
        listener.Start();

        while (true)
        {
            var client = listener.AcceptTcpClient();

            if (queue.Count <= 10)
            {
                queue.Enqueue(new Connection(client));
                reset.Set();
            }
            else
            {
                client.GetStream().Write(CONNECTION_REFUSED, 0, CONNECTION_REFUSED_LENGTH);
                client.Close();
            }
        }
    }

}

public sealed class Connection
{
    readonly TcpClient client;
    readonly Stopwatch stopwatch = new Stopwatch();

    public Connection(TcpClient client)
    {
        this.client = client;
        stopwatch.Start();
    }

    public void DoStuff()
    {

    }

    public void Abort()
    {
        //You can write out an abort message to the client if you like.
        client.Close();
        completed = true;
    }

    bool completed;

    public bool Finished
    {
        get
        {
            return stopwatch.ElapsedMilliseconds > 30 * 1000 || completed;
        }
    }
}

基本思想是使用一个线程将传入的连接添加到队列中,然后使用另一个线程遍历队列并处理每个连接。当连接被标记为“完成”时,它将从队列中移除(或者不会重新加入队列)。
一个健壮的解决方案可能需要使用lock语句或ConcurrentQueue类。在我的示例中,为了简洁起见,我使用了TCPClient类的阻塞方法,但是当然你应该使用非阻塞的方法。

1

限制客户端连接数量:

TcpListener.Start 方法 (Int32)

如何设置超时时间:

tcpListener.Server.ReceiveTimeout = 1000;
tcpListener.Server.SendTimeout = 1000;

停止监听的正确方式:

正确停止TcpListener的方法

当服务器收到未知数据包时关闭TCPClient的正确方式

这有点复杂,具体取决于您想要实现的协议。

  • 最简单的方法是只关闭传入的连接。
  • 如果您想要更智能的处理方式,需要考虑如何让客户端知道存在意外数据包,并重新发送或重新开始。基本上需要设计一个错误处理机制(有很多可供选择)。

2
我认为 TCPListener.Start(Int32) 只是限制了挂起连接的数量,而不是活动连接的数量。请参阅 http://msdn.microsoft.com/en-us/library/5kh8wf6s(v=vs.110).aspx - Rodrick Chapman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接