c# - 使用异步套接字的 TCP 端口转发

标签 c# .net sockets networking tcp

我正在尝试在 C# 中实现 TCP 转发器。具体应用:

  1. 监听一个TCP端口并等待客户端,
  2. 当客户端连接时,连接到远程主机,
  3. 在两个连接上等待传入数据并在两个端点之间交换数据(充当代理),
  4. 在另一个连接被端点关闭时关闭一个连接。

我已经改编了Simple TCP Forwader (加西亚)转发一系列端口,以便

TCPForwarder.exe 10.1.1.1 192.168.1.100 1000 1100 2000

将在 10.1.1.1 端口 1000-1100 上接收到的任何数据包转发到远程主机 192.168.1.100 端口 2000-2100。我已经使用它来公开 NAT 后面的 FTP 服务器。

通过运行上述命令,客户端能够连接到 FTP 服务器,并在预期的控制台中输出以下模式(请参阅代码):

0 StartReceive: BeginReceive
1 StartReceive: BeginReceive
1 OnDataReceive: EndReceive
1 OnDataReceive: BeginReceive
1 OnDataReceive: EndReceive
1 OnDataReceive: Close (0 read)
0 OnDataReceive: EndReceive
0 OnDataReceive: Close (exception)

但是在多次成功连接后(在 Filezilla 中按 F5),没有从 TCPForwarder(和 FTP 服务器)收到进一步的响应。

我的实现似乎有两个无法调试的问题:

  1. 在这种情况下,StartReceive 方法中的BeginReceive 被调用,但没有从 FTP 服务器接收到数据。我不认为这可能是 FTP 服务器问题(它是 ProFTPD 服务器),因为它是众所周知的 FTP 服务器。

  2. 每次建立和关闭连接时,线程数都会增加 1。我认为垃圾回收无法解决这个问题。线程数一直在增加,强制垃圾收集器运行也不会减少。我认为我的代码中存在一些泄漏,这也导致了问题 #1。

编辑:

  • 重新启动 FTP 服务器并没有解决问题,因此 TCPForwarder 中肯定存在错误。

  • @jgauffin 指出的一些问题已在以下代码中修复。

完整代码如下:

using System;
using System.Net;
using System.Net.Sockets;
using System.Collections.Generic;
using System.Threading;

namespace TCPForwarder
{
    class Program
    {
        private class State
        {
            public int ID { get; private set; } // for debugging purposes
            public Socket SourceSocket { get; private set; }
            public Socket DestinationSocket { get; private set; }
            public byte[] Buffer { get; private set; }
            public State(int id, Socket source, Socket destination)
            {
                ID = id;
                SourceSocket = source;
                DestinationSocket = destination;
                Buffer = new byte[8192];
            }
        }

        public class TcpForwarder
        {
            public void Start(IPEndPoint local, IPEndPoint remote)
            {
                Socket MainSocket;
                try
                {
                    MainSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                    MainSocket.Bind(local);
                    MainSocket.Listen(10);
                }
                catch (Exception exp)
                {
                    Console.WriteLine("Error on listening to " + local.Port + ": " + exp.Message);
                    return;
                }

                while (true)
                {
                    // Accept a new client
                    var socketSrc = MainSocket.Accept();
                    var socketDest = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                    try
                    {
                        // Connect to the endpoint
                        socketDest.Connect(remote);
                    }
                    catch
                    {
                        socketSrc.Shutdown(SocketShutdown.Both);
                        socketSrc.Close();
                        Console.WriteLine("Exception in connecting to remote host");
                        continue;
                    }

                    // Wait for data sent from client and forward it to the endpoint
                    StartReceive(0, socketSrc, socketDest);

                    // Also, wait for data sent from endpoint and forward it to the client
                    StartReceive(1, socketDest, socketSrc);
                }
            }

            private static void StartReceive(int id, Socket src, Socket dest)
            {
                var state = new State(id, src, dest);

                Console.WriteLine("{0} StartReceive: BeginReceive", id);
                try
                {
                    src.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, OnDataReceive, state);
                }
                catch
                {
                    Console.WriteLine("{0} Exception in StartReceive: BeginReceive", id);
                }
            }

            private static void OnDataReceive(IAsyncResult result)
            {
                State state = null;
                try
                {
                    state = (State)result.AsyncState;

                    Console.WriteLine("{0} OnDataReceive: EndReceive", state.ID);
                    var bytesRead = state.SourceSocket.EndReceive(result);
                    if (bytesRead > 0)
                    {
                        state.DestinationSocket.Send(state.Buffer, bytesRead, SocketFlags.None);

                        Console.WriteLine("{0} OnDataReceive: BeginReceive", state.ID);
                        state.SourceSocket.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, OnDataReceive, state);
                    }
                    else
                    {
                        Console.WriteLine("{0} OnDataReceive: Close (0 read)", state.ID);
                        state.SourceSocket.Shutdown(SocketShutdown.Both);
                        state.DestinationSocket.Shutdown(SocketShutdown.Both);
                        state.DestinationSocket.Close();
                        state.SourceSocket.Close();
                    }
                }
                catch
                {
                    if (state!=null)
                    {
                        Console.WriteLine("{0} OnDataReceive: Close (exception)", state.ID);
                        state.SourceSocket.Shutdown(SocketShutdown.Both);
                        state.DestinationSocket.Shutdown(SocketShutdown.Both);
                        state.DestinationSocket.Close();
                        state.SourceSocket.Close();
                    }
                }
            }
        }

        static void Main(string[] args)
        {
            List<Socket> sockets = new List<Socket>();

            int srcPortStart = int.Parse(args[2]);
            int srcPortEnd = int.Parse(args[3]);
            int destPortStart = int.Parse(args[4]);

            List<Thread> threads = new List<Thread>();
            for (int i = 0; i < srcPortEnd - srcPortStart + 1; i++)
            {
                int srcPort = srcPortStart + i;
                int destPort = destPortStart + i;

                TcpForwarder tcpForwarder = new TcpForwarder();

                Thread t = new Thread(new ThreadStart(() => tcpForwarder.Start(
                    new IPEndPoint(IPAddress.Parse(args[0]), srcPort),
                    new IPEndPoint(IPAddress.Parse(args[1]), destPort))));
                t.Start();

                threads.Add(t);
            }

            foreach (var t in threads)
            {
                t.Join();
            }
            Console.WriteLine("All threads are closed");
        }
    }
}

最佳答案

第一个问题是代码将在目标套接字(接受循环)上的连接失败时继续。在 try/catch 中使用 continue;。当您调用第一个 BeginReceive 时,也无法保证套接字仍然可用。这些调用也需要包装。

始终将回调方法包装在 try/catch 中,否则您的应用程序可能会失败(在本例中为 OnDataRecieve)。

解决这个问题并开始写出异常。他们肯定会提示您哪里出了问题。

关于c# - 使用异步套接字的 TCP 端口转发,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32347257/

相关文章:

c# - 需要帮助确定实际使用的设计模式吗? WP7

c# - 如何在 C# 中动态创建字符串?

c# - 什么是 TimeZoneInfo.ConvertTime 更快的替代方案?

.net - Visual Studio Community 2019 不显示设计器 View

java.net.SocketException : Connection reset by peer: socket write error When serving a file 异常

c# - C# 中的 "Abstract"接口(interface)

c# - 如果我调用 C# 事件处理程序并调用它,会发生什么情况?

当队列中没有连接时,C 套接字 accept() 失败

java - 更改客户端-服务器应用程序中圆圈的颜色

c# - foreach 循环无法识别对象类型