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