在SSL包装的本地用户连接之后或之前建立远程SSL连接

本文关键字:SSL 连接 建立 之后 用户 包装 | 更新日期: 2023-09-27 18:20:34

我正试图在C#中制作一个stunnel克隆,只是为了好玩。主循环是这样的(忽略捕获所有东西,什么都不做,暂时尝试捕获)

ServicePointManager.ServerCertificateValidationCallback = Validator;
            TcpListener a = new TcpListener (9999);
            a.Start ();
            while (true) {
                Console.Error.WriteLine ("Spinning...");
                try {
                    TcpClient remote = new TcpClient ("XXX.XX.XXX.XXX", 2376);
                    SslStream ssl = new SslStream(remote.GetStream(), false, new RemoteCertificateValidationCallback(Validator));
                    ssl.AuthenticateAsClient("mirai.ca");
                    TcpClient user = a.AcceptTcpClient ();
                    new Thread (new ThreadStart(() => {
                        Thread.CurrentThread.IsBackground = true;
                        try{
                            forward(user.GetStream(), ssl); //forward is a blocking function I wrote
                        }catch{}
                    })).Start ();
                } catch {
                    Thread.Sleep (1000);
                }
            }

我发现,如果我像以前一样在等待用户之前进行远程SSL连接,那么当用户连接时,SSL已经设置好了(这是为了隧道传输HTTP,所以延迟非常重要)。另一方面,我的服务器会关闭长时间不活动的连接,所以如果在5分钟内没有新的连接发生,一切都会锁定。

最好的方法是什么?

此外,我观察到我的程序生成了多达200个线程,这当然意味着上下文切换开销相当大,有时会导致整个程序阻塞数秒,即使只有一个用户通过程序进行隧道传输。我的前向函数,在一个要点上,像

    new Thread(new ThreadStart(()=>in.CopyTo(out))).Start();
    out.CopyTo(in);

当然,还有大量的错误处理,以防止断开的连接永远保持不变。不过,这似乎拖延了很多。我不知道如何使用像BeginRead这样的异步方法,根据谷歌的说法,这应该会有所帮助。

在SSL包装的本地用户连接之后或之前建立远程SSL连接

对于任何类型的代理服务器(包括stunnel克隆),在接受前端连接后打开后端连接显然要简单得多。

如果您预先打开后端连接以预期接收到前端连接,那么您当然可以保存RTT(这有利于延迟),但您必须处理您暗示的问题:后端将关闭空闲连接。在您收到前端连接的任何时候,您都会面临这样的风险:您将要与此前端连接关联并且在一段时间前打开的后端连接太旧,无法使用,可能会被后端关闭。您必须管理一个当前打开的后端连接池,并在它们空闲太久时定期关闭和刷新它们。甚至还有一种竞争条件,如果后端认为连接空闲时间过长,并决定关闭它,但代理服务器同时接收到新的前端连接,则前端可能会决定通过后端连接转发请求,而后端正在关闭此连接。这意味着,在后端关闭后端连接之前,您必须能够先验地知道后端连接可以空闲多长时间(您必须知道后端上配置的超时值设置为什么),这样您就可以在后端决定它们太旧之前放弃它们。

总之:与仅按需打开后端连接相比,预打开后端连接将节省RTT,但这是一项艰巨的工作,包括微妙的连接池管理,很难实现无错误。由你来判断额外的复杂性是否值得。

顺便说一句,关于你关于处理几百个同时连接的评论,我建议将这样一个I/O绑定程序实现为基于事件循环而不是基于线程的代理服务器。基本上,您使用非阻塞套接字并在单个线程中处理事件(例如,"此套接字有新数据等待转发到另一端"),而不是为每个连接生成一个线程(这可能在线程创建和上下文切换方面都很昂贵)。为了将这种基于事件的模型扩展到多个CPU核心,您可以启动少量并行进程线程(每个CPU核心或多或少一个),每个线程处理数百(或数千)个同时连接。