前言


最近在写一个透传项目,需要实现一个TCPClient模式的透传。在没有连接上时会去不断发起连接直至连接成功, 还有断连后又会不断发起请求连接,直至再次连接成功。 作为小白,第一反应就是去百度,结果百度搜索出来的,全是CSDN,而且清一色都是上来贴一大堆代码,令人头晕,还一大堆重复的,越看越烦而且搜索无果。

既然没有路,那就由我自己来开辟!


连接成功前进行不断发起请求连接

其实这个功能思路非常简单,无非就是 尝试连接=>连接失败=>重连(连接成功就跳出)。

用代码写出来:

//创建一个新的Socket对象
Socket client=new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp)
try
{
    client.Connect(IPAddress.Parse(IP地址), 端口号);//尝试连接
}
catch
{
    client.Close();//先关闭
    /*使用新的客户端资源覆盖,上一个已经废弃。如果继续使用以前的资源进行连接,即使参数正确,
    服务器全部打开也会无法连接*/
    client=new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp);
    client.Connect(IPAddress.Parse(IP地址), 端口号);//尝试连接
}

复制代码

而这上面仅是进行一次失败重连,如果再失败怎么办?所以我们要不断重复这个步骤。加一层while循环让它不断进行重连。 代码如下:

//创建一个新的Socket对象
Socket client=new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp)
while(true)//无限循环
{
    try
    {
        client.Connect(IPAddress.Parse(IP地址), 端口号);//尝试连接,失败则会跳去catch
        break;//在此处加上break,成功就跳出循环,避免死循环
    }
    catch
    {
        client.Close();//先关闭
        /*使用新的客户端资源覆盖,上一个已经废弃。如果继续使用以前的资源进行连接,即使参数正确,
        服务器全部打开也会无法连接*/
        client=new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp);
    }
}

复制代码

其实这么写,就已经实现了我们初步的功能,客户端会进行不断地连接,直至成功连上服务器。但是,这里有个很严重的问题,如果一直没连上,一直在执行这一步重连,程序会卡死在这里。 所以我们需要额外多开个子线程去执行这一步操作。

//创建一个新的Socket对象
Socket client=new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp)
//将方法写进线程中
Thread thread=new Thread(() =>
{
    while(true)//无限循环
    {
        try
        {
            client.Connect(IPAddress.Parse(IP地址), 端口号);//尝试连接,失败则会跳去catch
            break;//在此处加上break,成功就跳出循环,避免死循环
        }
        catch
        {
            client.Close();//先关闭
            /*使用新的客户端资源覆盖,上一个已经废弃。如果继续使用以前的资源进行连接,
            即使参数正确, 服务器全部打开也会无法连接*/
            client=new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp);
            Thread.Sleep(1000);//等待1s再去重连
        }
    }
});
thread.IsBackground = true;//设置为后台线程,在程序退出时自己会自动释放
thread.Start();//开始执行线程
复制代码

即使一直连不上也不会使程序卡死,会一直进行重连直到连上服务器。但是我们的问题还没解决。那就是 循环结束问题。这部分尝试不断连接,while循环的条件是true,无限循环,就会导致即使连上后,虽然break,但是线程没有结束,还是会继续去进行无限循环。我们需要重新设置循环中止条件。

代码如下:

//创建一个新的Socket对象
Socket client=new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp)
public static bool IsConnet=true;//判断是否成功连接,设置为全局变量,方便随时控制
//将方法写进线程中
Thread thread=new Thread(() =>
{
    while(IsConnet)//循环
    {
        try
        {
            client.Connect(IPAddress.Parse(IP地址), 端口号);//尝试连接,失败则会跳去catch
            IsConnet=false;//成功连接后修改bool值为false,这样下一步循环就不再执行。
            break;//在此处加上break,成功就跳出循环,避免死循环
        }
        catch
        {
            client.Close();//先关闭
            /*使用新的客户端资源覆盖,上一个已经废弃。如果继续使用以前的资源进行连接,
            即使参数正确, 服务器全部打开也会无法连接*/
            client=new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp);
            Thread.Sleep(1000);//等待1s再去重连
        }
    }
});
thread.IsBackground = true;//设置为后台线程,在程序退出时自己会自动释放
thread.Start();//开始执行线程
复制代码

这样就不会再有无限循环的问题了。至此,我们已经完成了功能的一半,这部分已经实现了连接前的不断请求连接,那么连接后又断开呢?很显然,如果在连接后再断开,我们无法进行重连。
另一半就要实现 断线重连。


断线重连

这个思路同样很简单,就是服务器断开->调用连接方法->不断连接
连接方法就是我们上一步写过的功能,我们已经实现不断连接了,我们要将上一步的功能封装成一个方法体去调用就可以了。
连接上服务器后,就是个不断接收的过程,所以也需要多开一个线程去不断接收消息。 代码如下:

//注意,这里的开始部分还是上一步的代码,只不过嵌进了方法体
public void Connet(string Iptxt,int Port)//接收参数是目标ip地址和目标端口号。客户端无须关心本地端口号
{
    //创建一个新的Socket对象
    Socket client=new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp)
    IsConnet=true;//注意,此处是全局变量,将其设置为true
    //将方法写进线程中
    Thread thread=new Thread(() =>
    {
        while(IsConnet)//循环
        {
            try
            {
                client.Connect(IPAddress.Parse(Iptxt), Port);//尝试连接,失败则会跳去catch
                IsConnet=false;//成功连接后修改bool值为false,这样下一步循环就不再执行。
                break;//在此处加上break,成功就跳出循环,避免死循环
            }
            catch
            {
                client.Close();//先关闭
                /*使用新的客户端资源覆盖,上一个已经废弃。如果继续使用以前的资源进行连接,
                即使参数正确, 服务器全部打开也会无法连接*/
                client=new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp);
                Thread.Sleep(1000);//等待1s再去重连
            }
        }
        /*这里不一样就是放接收线程,在连接上后break出来,执行。
        因为需要带参数,所以要用到特别的ParameterizedThreadStart,
        然后开始线程。↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓*/
        Thread thread2 = new Thread(new ParameterizedThreadStart(ClientReceiveData));//接收线程方法
        thread2.IsBackground = true;//该值指示某个线程是否为后台线程。
        thread2.Start(client);//参数是用我们自建的Socket对象,就是上面的Socket client=new……
        
    });
    thread.IsBackground = true;//设置为后台线程,在程序退出时自己会自动释放
    thread.Start();//开始执行线程
}
复制代码

这样就是在连接后就会进入不断接收的子线程,接下来写接收程序的代码,与网络上的大同小异,只不过我们会稍作改动,在异常断开和正常退出时都去重新进行连接。Ip地址和端口号可以设置成全局变量,方便进行获取,代码如下:

 public void ClientReceiveData(object socket)//TCPClient消息的方法
{
    var ProxSocket = socket as Socket;//处理上一步传过来的Socket函数
    byte[] data = new byte[1024 * 1024];//接收消息的缓冲区
    while (!IsConnet)//同样循环中止的条件
    {
        int len = 0;//记录消息长度,以及判断是否连接
        try
        {
            //连接函数Receive会将数据放入data,从0开始放,之后返回数据长度。
            len = ProxSocket.Receive(data, 0, data.Length, SocketFlags.None);
        }
        catch (Exception)
        {
            //异常退出
            ProxSocet.ShutDown(SocketShutdown.Both);//中止传输
            ProxSocet.Close();//关闭
            Connet(ip地址,端口号);//重新尝试去连接
            IsConnet=false;//注意,此处是全局变量,将其设置为false,防止循环
            return;//让方法结束,终结当前接收服务端数据的异步线程
        }
        if (len <= 0)
        {
            //如果小于0,证明无连接,服务端正常退出
            ProxSocet.ShutDown(SocketShutdown.Both);//中止传输
            ProxSocet.Close();//关闭
            Connet(ip地址,端口号);//重新尝试去连接
            IsConnet=false;//注意,此处是全局变量,将其设置为false,防止循环
            return;//让方法结束,终结当前接收服务端数据的异步线程
        }
        //这里做你想要对消息做的处理
        //string str = Encoding.Default.GetString(data, 0, len);//二进制数组转换成字符串……
    }
}
复制代码

到这里就已经全部实现了!!接下来看看效果吧!!(以本人做的项目做例子)。

本次分享结束,有什么不足的地方,希望大家可以指出,或者不懂的可以留言问我,我们可以多交流!如果想实现图里动态刷新连接状态,可以看我上一篇文章C#LINQ实现动态刷新

2022.11/25 BUG反映


有些小伙伴会反映有以下BUG:

ProxSocet.Shutdown(SocketShutdown.Both);
---------------------
System.Net.Sockets.SocketException:“由于套接字没有连接并且(当使用一个 sendto 调用发送数据报套接字时)没有提供地址,发送或接收数据的请求没有被接受。”

按照这位朋友说的改正就好了!

 

之前写文章的时候,是直接根据自己已经成功实现的思路去白板徒手写了代码去复现,没有做测试,所以有了意想不到的BUG。感谢各位网友,你们都很强大!

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐