在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
这样的异步方法,根据谷歌的说法,这应该会有所帮助。
对于任何类型的代理服务器(包括stunnel
克隆),在接受前端连接后打开后端连接显然要简单得多。
如果您预先打开后端连接以预期接收到前端连接,那么您当然可以保存RTT(这有利于延迟),但您必须处理您暗示的问题:后端将关闭空闲连接。在您收到前端连接的任何时候,您都会面临这样的风险:您将要与此前端连接关联并且在一段时间前打开的后端连接太旧,无法使用,可能会被后端关闭。您必须管理一个当前打开的后端连接池,并在它们空闲太久时定期关闭和刷新它们。甚至还有一种竞争条件,如果后端认为连接空闲时间过长,并决定关闭它,但代理服务器同时接收到新的前端连接,则前端可能会决定通过后端连接转发请求,而后端正在关闭此连接。这意味着,在后端关闭后端连接之前,您必须能够先验地知道后端连接可以空闲多长时间(您必须知道后端上配置的超时值设置为什么),这样您就可以在后端决定它们太旧之前放弃它们。
总之:与仅按需打开后端连接相比,预打开后端连接将节省RTT,但这是一项艰巨的工作,包括微妙的连接池管理,很难实现无错误。由你来判断额外的复杂性是否值得。
顺便说一句,关于你关于处理几百个同时连接的评论,我建议将这样一个I/O绑定程序实现为基于事件循环而不是基于线程的代理服务器。基本上,您使用非阻塞套接字并在单个线程中处理事件(例如,"此套接字有新数据等待转发到另一端"),而不是为每个连接生成一个线程(这可能在线程创建和上下文切换方面都很昂贵)。为了将这种基于事件的模型扩展到多个CPU核心,您可以启动少量并行进程线程(每个CPU核心或多或少一个),每个线程处理数百(或数千)个同时连接。