高可用的端口转发

在windows下, 利用netsh可以实现端口转发功能, 能满足大部分日常需求, 但在某些情况下, 在后端节点不可用的时候, 需要将数据转发至另一节点, 这时就只能自行编码实现了。

本文将使用C#做一个简单的实现: 完整代码

解决思路

  • 因为需要转发数据, 所以程序肯定是要监听端口的
  • 为了避免数据混乱和复杂性, 这里每个端口只接收一个连接, 所以接收到新连接后, 关闭端口监听
  • 若长时间没有数据通讯, 则主动关闭连接, 释放资源
  • 开启两个工作线程, 一个叫代理线程, 一个叫转发线程

代理线程工作流程

代理线程负责接收外来连接, 并确保同一时间只有一个外来连接, 接收到连接后读取数据, 读取到数据后, 将数据转发到指定目标地址。

graph LR
    listen[监听]
    accept[接受连接]
    read[读取数据]
    forwarding[转发数据]
    listen-->accept
    accept-->read
    read-->forwarding
    forwarding-->read

主要代码:

server = new TcpListener(listen);
server.Start(1);
client = server.AcceptTcpClient();
server.Stop(); //停止监听, 同一时间只接收一个连接
server = null;
listenStream = client.GetStream();
listenStream.ReadTimeout = READ_TIMEOUT;
while (true)
{
int len = listenStream.Read(buffer, 0, BUFF_SIZE);
if (len > 0)
{
byte[] tmp = new byte[len];
Array.Copy(buffer, tmp, len);
Forwarding(tmp); //转发数据
}
else
{
//.. 处理连接中断
}
}

转发线程工作流程

转发线程负责连接目标地址, 一个连不上再去连接下一个, 当连上后, 等待接收数据, 接收到数据后, 将数据转发到代理连接上去。

主要代码:

client = new TcpClient();
var result = client.BeginConnect(currentEndPoint.Address, currentEndPoint.Port, null, null);
var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(CONNECT_TIMEOUT));
if (!success) //超时处理
{
Console.WriteLine("connect {0} timeout", currentEndPoint);
continue;
}
client.EndConnect(result);
forwardingStream = client.GetStream();
forwardingStream.ReadTimeout = READ_TIMEOUT;
while (true)
{
int len = forwardingStream.Read(buffer, 0, BUFF_SIZE);
if (len > 0)
{
byte[] tmp = new byte[len];
Array.Copy(buffer, tmp, len);
Response(tmp);
}
else
{
//.. 处理连接中断
}
}