实现设备自动重连机制

在上位机、工业控制、智能硬件等项目中,设备连接并不会一直保持稳定。

例如使用 TCP、串口或其他通信方式连接设备时,都可能遇到:

网络短暂中断

设备重启

设备掉电

网线松动

Wi-Fi切换

服务端主动断开

如果程序只是简单地提示“设备已断开”,然后等待用户手动重新连接,实际使用体验会比较差。

特别是一些需要长期运行的上位机软件,设备短暂掉线后,通常希望程序能够自动恢复连接。

因此,可以在通信模块外增加一个自动重连机制。

整体思路可以理解为:

正常连接

↓

检测设备断开

↓

进入重连状态

↓

等待一定时间

↓

尝试重新连接

↓

连接成功后恢复通信

下面以 TCP 设备为例,介绍一个比较简单的 C# 自动重连实现方式。

一、先把设备连接状态管理清楚

自动重连最容易出现的问题,并不是 ConnectAsync 怎么写,而是连接状态比较混乱。

例如程序可能同时出现:

正在连接

已经连接

设备掉线

正在重连

用户主动关闭

如果只是使用一个 bool:

bool isConnected;

后面很容易出现判断不清楚的情况。

因此可以先定义一个简单的连接状态:

public enum DeviceConnectionState

{

Disconnected,

Connecting,

Connected,

Reconnecting,

Stopped

}

其中:

Disconnected

表示当前没有连接设备。

Connecting

表示第一次连接设备。

Connected

表示设备已经正常连接。

Reconnecting

表示设备发生异常,目前正在尝试重新连接。

Stopped

表示用户主动停止连接,不再自动重连。

然后在设备客户端中保存:

private DeviceConnectionState connectionState

= DeviceConnectionState.Disconnected;

public DeviceConnectionState ConnectionState

{

get

{

return connectionState;

}

}

这样整个连接生命周期会比较清楚:

Disconnected

↓

Connecting

↓

Connected

↓

Reconnecting

↓

Connected

如果用户主动停止:

Connected

↓

Stopped

进入 Stopped 以后,就不应该继续触发自动重连。

二、封装一个基础连接方法

首先实现正常的设备连接。

例如:

public class DeviceClient

{

private TcpClient tcpClient;

private NetworkStream networkStream;

public async Task ConnectAsync(

    string ip,

    int port,

    CancellationToken cancellationToken)

{

    tcpClient = new TcpClient();

    await tcpClient.ConnectAsync(

        ip,

        port,

        cancellationToken

    );

    networkStream =

        tcpClient.GetStream();

    Console.WriteLine(

        $"连接设备成功:{ip}:{port}"

    );

}

}

第一版连接时:

connectionState =

DeviceConnectionState.Connecting;

try

{

await ConnectAsync(

ip,

port,

cancellationToken

);

connectionState =

    DeviceConnectionState.Connected;

}

catch(Exception ex)

{

connectionState =

DeviceConnectionState.Disconnected;

Console.WriteLine(

    $"连接失败:{ex.Message}"

);

}

这里建议把“连接设备”和“重连策略”分开。

也就是说:

ConnectAsync只负责:

连接一次设备

不要直接在 ConnectAsync 内部写:

连接失败

↓

继续连接

↓

再失败

↓

继续连接

否则后面很难管理取消、退出和重连次数。

三、实现自动重连循环

设备断开以后,可以启动一个独立的重连流程。

例如:

private async Task ReconnectAsync(

CancellationToken cancellationToken)

{

connectionState =

DeviceConnectionState.Reconnecting;

int retryCount = 0;

while(!cancellationToken.IsCancellationRequested)

{

    retryCount++;

    try

    {

        Console.WriteLine(

            $"第 {retryCount} 次尝试重新连接设备"

        );

        await ConnectAsync(

            deviceIp,

            devicePort,

            cancellationToken

        );

        connectionState =

            DeviceConnectionState.Connected;

        Console.WriteLine(

            "设备重新连接成功"

        );

        return;

    }

    catch(OperationCanceledException)

    {

        return;

    }

    catch(Exception ex)

    {

        Console.WriteLine(

            $"重新连接失败:{ex.Message}"

        );

    }

    await Task.Delay(

        3000,

        cancellationToken

    );

}

}

这段逻辑就是一个基础的自动重连机制。

例如设备掉线:

10:20:01

设备断开

10:20:04

第一次重连失败

10:20:07

第二次重连失败

10:20:10

第三次重连成功

对于很多上位机项目来说,这种简单策略已经可以满足基础需求。

不过需要特别注意:

不能每次检测到断线都重新启动一个 ReconnectAsync。

否则可能出现:

Reconnect Task 1

Reconnect Task 2

Reconnect Task 3

三个任务同时尝试连接同一个设备。

因此通常需要增加一个标记:

private bool isReconnecting;

例如:

private async Task StartReconnectAsync(

CancellationToken cancellationToken)

{

if(isReconnecting)

{

return;

}

isReconnecting = true;

try

{

    await ReconnectAsync(

        cancellationToken

    );

}

finally

{

    isReconnecting = false;

}

}

这样可以保证同一时间只有一个重连流程。

四、重连间隔不要固定得太激进

最简单的做法是:

每3秒尝试一次。

例如:

await Task.Delay(

3000,

cancellationToken

);

对于局域网设备来说,这种方式通常没有问题。

但是如果设备长期离线:

程序可能一直:

连接

失败

等待3秒

连接

失败

等待3秒

如果设备可能长时间断电,可以适当增加重连间隔。

例如:

第一次:

1秒

第二次:

2秒

第三次:

5秒

之后:

10秒

可以简单实现:

private TimeSpan GetRetryDelay(

int retryCount)

{

if(retryCount <= 1)

return TimeSpan.FromSeconds(1);

if(retryCount <= 3)

    return TimeSpan.FromSeconds(3);

return TimeSpan.FromSeconds(10);

}

调用:

TimeSpan delay =

GetRetryDelay(retryCount);

await Task.Delay(

delay,

cancellationToken

);

这样在设备刚刚掉线时:

能够快速恢复。

如果设备已经长时间离线:

又不会频繁发起连接。

实际项目中还可以设置最大间隔,例如:

MaximumRetryDelay = 30秒

一般没有必要不断增加到几分钟甚至更长。

五、设备掉线以后需要做哪些处理

自动重连并不是检测到异常以后直接调用 ConnectAsync 就结束了。

原来的连接资源也需要先处理。

例如:

private void CloseConnection()

{

try

{

networkStream?.Dispose();

}

catch

{

}

try

{

    tcpClient?.Dispose();

}

catch

{

}

networkStream = null;

tcpClient = null;

}

当接收任务发现:

ReadAsync 返回 0

或者出现:

IOException

SocketException

可以执行:

CloseConnection();

connectionState =

DeviceConnectionState.Disconnected;

await StartReconnectAsync(

cancellationToken

);

完整过程:

设备正常通信

↓

ReadAsync异常

↓

关闭旧连接

↓

修改连接状态

↓

启动重连

↓

建立新的TcpClient

↓

重新启动接收任务

这里还有一个容易忽略的问题:

重连成功以后,要重新启动和连接相关的任务。

例如:

ReceiveLoop

Heartbeat

DeviceStatusPolling

因为之前的任务通常已经随着旧连接结束。

因此重连成功后可能需要:

await ConnectAsync(...);

StartReceiveLoop();

StartHeartbeat();

StartDevicePolling();

而不是只重新创建 TcpClient。

六、实际项目中需要特别注意的几个问题

自动重连代码本身并不复杂,但真正使用时有几个地方比较容易出现问题。

用户主动断开时不要自动重连

例如用户点击:

断开设备

此时应该:

Stopped

而不是:

Disconnected

因为:

Disconnected

可能代表异常断开,需要重连。

Stopped

代表用户主动停止,不应该重连。

可以增加:

private bool manualStop;

用户主动停止:

manualStop = true;

if(!manualStop)

{

await StartReconnectAsync(

cancellationToken

);

}

重连之前释放旧Socket

不要在旧 TcpClient 还存在的情况下不断 new 新对象。

建议流程:

旧连接异常

↓

停止读写

↓

Dispose

↓

重新建立连接

否则时间长以后可能产生比较多无效资源。

CancellationToken要贯穿整个重连流程

例如程序退出:

cancellationToken.Cancel();

此时:

正在连接

重连等待

心跳任务

接收任务

都应该结束。

不要出现:

界面已经关闭

但后台仍然每3秒连接设备

这种情况。

重连成功不代表业务已经恢复

例如某些设备连接成功后还需要:

登录

身份验证

读取设备信息

同步参数

订阅状态

因此更完整的流程可能是:

TCP重新连接

↓

设备握手

↓

读取设备信息

↓

恢复订阅

↓

开始正常工作

只有这些步骤全部成功以后,才能真正认为设备恢复正常。

和心跳机制结合

有些设备网络异常以后:

Socket并不会立即报错。

因此通常结合心跳:

Connected

↓

定时Heartbeat

↓

连续多次无响应

↓

认为设备离线

↓

CloseConnection

↓

Reconnect

这样比单纯等待 Socket 报错更加主动。

记录重连过程

实际调试设备问题时,重连日志非常有用。

例如:

09:31:12 设备断开

09:31:15 第1次重连失败

09:31:18 第2次重连失败

09:31:21 重连成功

09:31:22 设备信息同步完成

这样以后出现现场问题时,可以比较容易判断:

到底是设备掉线

网络异常

还是重连逻辑本身出现问题。

一个比较完整的设备连接模块,最终可以形成:

DeviceService

↓

ConnectionManager

↓

TcpTransport

其中 ConnectionManager 负责:

连接状态

断线检测

自动重连

停止连接

TcpTransport 只负责:

建立Socket

收数据

发数据

业务层不需要关心设备掉线以后具体重连了多少次,只需要关注:

Connected

Disconnected

Reconnecting

这些状态即可。

对于需要长期运行的 C# 上位机软件来说,自动重连属于一个很基础但很重要的功能。真正需要注意的不是简单地重复调用 ConnectAsync,而是把连接状态、资源释放、取消、重连任务和业务恢复这几个环节处理清楚。

最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容