前言
最近在写一个透传项目,需要实现一个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。感谢各位网友,你们都很强大!
版权归原作者 羅漢果茶 所有, 如有侵权,请联系我们删除。