一分钟了解长连接 、短连接、心跳机制与断线重连

短连接

概念

client与server通过三次握手建立连接,client发送请求消息,server返回响应,一次连接就完成了。

这时候双方任意都可以发起close操作,不过一般都是client先发起close操作。由于短连接一般只会在 client/server 间传递一次请求操作,因此短连接的特点是连接生命周期短暂,连接建立和断开的开销较大,适用于单次请求响应的场景。

短连接的优缺点

管理起来比较简单,存在的连接都是有用的连接,不需要额外的控制手段。

使用场景

通常情况下,当浏览器访问服务器时,采用的是短连接的方式。

对于服务端而言,长连接会消耗大量的资源,而且用户使用浏览器对服务端的访问频率相对较低。如果同时存在几十万甚至上百万的连接,则服务端的压力将非常巨大,甚至可能导致崩溃。

因此,针对并发量高但请求频率低的情况,建议使用短连接。

为了优化这种情况,可以考虑以下方法:

1. 进行连接池管理:使用连接池来管理与服务端的连接,避免每次请求都建立和关闭连接,减少资源的消耗。

2. 使用缓存机制:将一些不经常变动且占用资源较多的数据进行缓存,减少对服务端的请求,提高性能。

3. 引入负载均衡:通过负载均衡技术将请求分发到多个服务器上,均衡服务器的压力,提高整体的处理能力。

4. 优化服务端架构:对服务端进行优化,如增加服务器的处理能力、调整服务器配置等,以提高服务端的并发处理能力。

长连接

什么是长连接

客户端向服务器发起连接,服务器接受客户端连接并建立双方连接。

客户端和服务器完成一次读写后,它们之间的连接不会主动关闭,并可以继续使用该连接进行后续的读写操作。

长连接的生命周期

在正常情况下,一条TCP长连接建立后,只要双方不提出关闭请求并且不出现异常情况,这条连接会一直存在。操作系统不会主动关闭它,即使在经过物理网络拓扑的改变之后仍然可以使用。因此,一条连接可以保持几天、几个月、几年甚至更长时间,只要没有异常情况或用户(应用层)主动关闭。

客户端和服务端可以一直使用该连接进行数据通信。

长连接的优点

使用长连接可以减少TCP建立和关闭操作,从而减少网络阻塞。即使发生错误,也不需要关闭连接就能进行提示,这样可以减少CPU和内存的使用,因为不需要频繁地建立和关闭连接。

长连接的缺点

连接数过多时,影响服务端的性能和并发数量。

使用场景

数据库的连接就是采用TCP长连接.

RPC,远程服务调用,在服务器,一个服务进程频繁调用另一个服务进程,可使用长连接,减少连接花费的时间。

总结

1.对于长连接和短连接的使用是需要根据应用场景来判断的

2.长连接并不是万能的,也是需要维护的,

长连接的实现

心跳机制

应用层协议通常会使用心跳机制来保持客户端与服务器的连接,并确保客户端仍然在线。典型的心跳协议如IM协议(例如QQ、MSN、飞信)会定期发送数据包给服务器,同时传输一些可能必要的数据。

在TCP协议中,也有一个心跳机制,即TCP选项中的SO_KEEPALIVE。系统默认设置为2小时发送一次心跳包。但是这个机制无法检测机器断电、网线拔出或防火墙等导致的断线情况。此外,逻辑层处理断线情况也可能不够完善。通常情况下,如果只是用于保活目的,SO_KEEPALIVE机制仍然是可以接受的。

请注意以下优化建议:

1. 调整心跳频率:根据实际情况,可以根据应用需求调整心跳频率。太频繁的心跳包可能造成额外的网络负担,而太不频繁则可能延迟检测到断线情况。

2. 使用应用层心跳机制:考虑使用应用层心跳机制,而不仅仅依赖于TCP的SO_KEEPALIVE。应用层心跳机制能够更灵活地处理不同情况下的断线问题,并能够传递更多的必要数据。

3. 完善断线处理逻辑:在应用层实现断线处理逻辑,包括重新连接、重发未成功的数据等。确保断线后客户端能够尽快恢复连接,并保持数据的完整性和一致性。

4. 测试和监控:定期测试心跳机制的有效性,并监控断线情况以及处理效果。及时发现并解决可能存在的问题。

为什么需要心跳机制?

由于网络的不可靠性,TCP长连接可能会在某些突发情况下断开,例如网线被拔出或突然掉电。在这种情况下,如果服务器和客户端之间没有交互,它们不能立即发现对方已掉线。为解决这个问题,可以引入心跳机制。

TCP协议的KeepAlive机制

默认KeepAlive状态是不打开的。

需要将setsockopt将SOL_SOCKET.SO_KEEPALIVE设置为1才是打开KeepAlive状态,

并且可以设置三个参数:

tcp_keepalive_time ,tcp_keepalive_probes , tcp_keepalive_intvl

分别表示:连接闲置多久开始发keepalive的ack包、发几个ack包不回复才当对方已断线、两个ack包之间的间隔。

很多网络设备,尤其是NAT路由器,由于其硬件的限制(例如内存、CPU处理能力),无法保持其上的所有连接,因此在必要的时候,会在连接池中选择一些不活跃的连接踢掉。

典型做法是LRU,把最久没有数据的连接给T掉。

通过使用TCP的KeepAlive机制(修改那个time参数),可以让连接每隔一小段时间就产生一些ack包,以降低被踢掉的风险,当然,这样的代价是额外的网络和CPU负担。

如何实现心跳机制?

两种方式实现心跳机制:

  • 使用 TCP 协议层面的 keepalive 机制.
  • 在应用层上实现自定义的心跳机制.

虽然在 TCP 协议层面上, 提供了 keepalive 保活机制, 但是使用它有几个缺点:

  1. 它不是 TCP 的标准协议, 并且是默认关闭的.
  2. TCP keepalive 机制依赖于操作系统的实现, 默认的 keepalive 心跳时间是 两个小时, 并且对 keepalive 的修改需要系统调用(或者修改系统配置), 灵活性不够.
  3. TCP keepalive 与 TCP 协议绑定, 因此如果需要更换为 UDP 协议时, keepalive 机制就失效了.

使用 TCP 层面的 keepalive 机制比自定义的应用层心跳机制节省流量,

本文的主要介绍应用层方面实现心跳机制,使用netty实现心跳和断线重连。

netty实现心跳机制

netty对心跳机制提供了机制,实现的关键是IdleStateHandler先来看一下他的构造函数

public IdleStateHandler(
            long readerIdleTime, long writerIdleTime, long allIdleTime,
            TimeUnit unit) {
        this(false, readerIdleTime, writerIdleTime, allIdleTime, unit);
    }

Copy

实例化一个 IdleStateHandler 需要提供三个参数:

  • readerIdleTimeSeconds, 读超时. 即当在指定的时间间隔内没有从 Channel 读取到数据时, 会触发一个 READER_IDLE 的 IdleStateEvent 事件.
  • writerIdleTimeSeconds, 写超时. 即当在指定的时间间隔内没有数据写入到 Channel 时, 会触发一个 WRITER_IDLE 的 IdleStateEvent 事件.
  • allIdleTimeSeconds, 读和写都超时. 即当在指定的时间间隔内没有读并且写操作时, 会触发一个 ALL_IDLE 的 IdleStateEvent 事件.

netty心跳流程

3f0d2202310071633484377.png

1. 客户端成功连接服务端。

2.在客户端中的ChannelPipeline中加入IdleStateHandler,设置写事件触发事件为5s.

3.客户端超过5s未写数据,触发写事件,向服务端发送心跳包,

4.同样,服务端要对心跳包做出响应,其实给客户端最好的回复就是“不回复”,减轻服务端的压力

5.超过三次,1过0s服务端都会收到来自客户端的心跳信息,服务端可以认为客户端挂了,可以close链路。

6.客户端恢复正常,发现链路已断,重新连接服务端。

代码实现

服务端handler:

package com.heartbreak.server;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;

import java.util.Random;

/\*\*
 \* @author janti
 \* @date 2018/6/10 12:21
 \*/
public class HeartbeatServerHandler extends SimpleChannelInboundHandler<String> {
    // 失败计数器:未收到client端发送的ping请求
    private int unRecPingTimes = 0;

    // 定义服务端没有收到心跳消息的最大次数
    private static final int MAX\_UN\_REC\_PING\_TIMES = 3;

    private Random random = new Random(System.currentTimeMillis());

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        if (msg!=null && msg.equals("Heartbeat")){
            System.out.println("客户端"+ctx.channel().remoteAddress()+"--心跳信息--");
        }else {
            System.out.println("客户端----请求消息----:"+msg);
            String resp \= "商品的价格是:"+random.nextInt(1000);
            ctx.writeAndFlush(resp);
        }
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event \= (IdleStateEvent) evt;
            if (event.state()==IdleState.READER\_IDLE){
                System.out.println("===服务端===(READER\_IDLE 读超时)");
                // 失败计数器次数大于等于3次的时候,关闭链接,等待client重连
                if (unRecPingTimes >= MAX\_UN\_REC\_PING\_TIMES) {
                    System.out.println("===服务端===(读超时,关闭chanel)");
                    // 连续超过N次未收到client的ping消息,那么关闭该通道,等待client重连
                    ctx.close();
                } else {
                    // 失败计数器加1
                    unRecPingTimes++;
                }
            }else {
                super.userEventTriggered(ctx,evt);
            }
        }
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        super.channelActive(ctx);
        System.out.println("一个客户端已连接");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        super.channelInactive(ctx);
        System.out.println("一个客户端已断开连接");
    }
}

Copy

服务端server:

package com.heartbreak.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.\*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;

import java.util.concurrent.TimeUnit;

/\*\*
 \* @author tangj
 \* @date 2018/6/10 10:46
 \*/
public class HeartBeatServer {
    private static int port = 9817;

    public HeartBeatServer(int port) {
        this.port = port;
    }

    ServerBootstrap bootstrap \= null;
    ChannelFuture f;

    // 检测chanel是否接受过心跳数据时间间隔(单位秒)
    private static final int READ\_WAIT\_SECONDS = 10;

    public static void main(String args\[\]) {
        HeartBeatServer heartBeatServer \= new HeartBeatServer(port);
        heartBeatServer.startServer();
    }

    public void startServer() {
        EventLoopGroup bossgroup \= new NioEventLoopGroup();
        EventLoopGroup workergroup \= new NioEventLoopGroup();
        try {
            bootstrap \= new ServerBootstrap();
            bootstrap.group(bossgroup, workergroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new HeartBeatServerInitializer());
            // 服务器绑定端口监听
            f = bootstrap.bind(port).sync();
            System.out.println("server start ,port: "+port);
            // 监听服务器关闭监听,此方法会阻塞
            f.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            bossgroup.shutdownGracefully();
            workergroup.shutdownGracefully();
        }
    }


    private class HeartBeatServerInitializer extends ChannelInitializer<SocketChannel> {

        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            ChannelPipeline pipeline \= ch.pipeline();
            // 监听读操作,读超时时间为5秒,超过5秒关闭channel;
            pipeline.addLast("ping", new IdleStateHandler(READ\_WAIT\_SECONDS, 0, 0, TimeUnit.SECONDS));
            pipeline.addLast("decoder", new StringDecoder());
            pipeline.addLast("encoder", new StringEncoder());

            pipeline.addLast("handler", new HeartbeatServerHandler());
        }
    }

}

Copy

客户端handler

package com.heartbreak.client;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.EventLoop;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/\*\*
 \* @author tangj
 \* @date 2018/6/11 22:55
 \*/
public class HeartBeatClientHandler extends SimpleChannelInboundHandler<String>{
    private HeartBeatClient client;

    private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:dd");

    private static final ByteBuf HEARTBEAT\_SEQUENCE = Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("Heartbeat",
            CharsetUtil.UTF\_8));

    public HeartBeatClientHandler(HeartBeatClient client) {
        this.client = client;
    }
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println("收到服务端回复:"+msg);
        if (msg.equals("Heartbeat")) {
            ctx.write("has read message from server");
            ctx.flush();
        }
        ReferenceCountUtil.release(msg);
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleState state \= ((IdleStateEvent) evt).state();
            if (state == IdleState.WRITER\_IDLE) {
                ctx.writeAndFlush(HEARTBEAT\_SEQUENCE.duplicate());
            }
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        super.channelInactive(ctx);
        System.err.println("客户端与服务端断开连接,断开的时间为:"+format.format(new Date()));
        // 定时线程 断线重连
        final EventLoop eventLoop = ctx.channel().eventLoop();
        eventLoop.schedule(new Runnable() {
            @Override
            public void run() {
                client.doConncet();
            }
        }, 10, TimeUnit.SECONDS);
    }


}

Copy

客户端启动:

package com.heartbreak.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.\*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/\*\*
 \* @author tangj
 \* @date 2018/6/10 16:18
 \*/
public class HeartBeatClient {

    private Random random = new Random();
    public Channel channel;
    public Bootstrap bootstrap;

    protected String host = "127.0.0.1";
    protected int port = 9817;

    public static void main(String args\[\]) throws Exception {
        HeartBeatClient client \= new HeartBeatClient();
        client.run();
        client.sendData();

    }

    public void run() throws Exception {
        EventLoopGroup group \= new NioEventLoopGroup();
        try {
            bootstrap \= new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new SimpleClientInitializer(HeartBeatClient.this));
            doConncet();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /\*\*
     \* 发送数据
     \* @throws Exception
     \*/
    public void sendData() throws Exception {
        BufferedReader in \= new BufferedReader(new InputStreamReader(System.in));
        while (true){
            String cmd \= in.readLine();
            switch (cmd){
                case "close" :
                    channel.close();
                    break;
                default:
                channel.writeAndFlush(in.readLine());
                    break;
            }
        }
    }

    /\*\*
     \* 连接服务端
     \*/
    public void doConncet() {
        if (channel != null && channel.isActive()) {
            return;
        }
        ChannelFuture channelFuture \= bootstrap.connect(host, port);
        channelFuture.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture futureListener) throws Exception {
                if (channelFuture.isSuccess()) {
                    channel \= futureListener.channel();
                    System.out.println("connect server successfully");
                } else {
                    System.out.println("Failed to connect to server, try connect after 10s");
                    futureListener.channel().eventLoop().schedule(new Runnable() {
                        @Override
                        public void run() {
                            doConncet();
                        }
                    }, 10, TimeUnit.SECONDS);
                }
            }
        });

    }


    private class SimpleClientInitializer extends ChannelInitializer<SocketChannel> {

        private HeartBeatClient client;

        public SimpleClientInitializer(HeartBeatClient client) {
            this.client = client;
        }

        @Override
        protected void initChannel(SocketChannel socketChannel) throws Exception {
            ChannelPipeline pipeline \= socketChannel.pipeline();
            pipeline.addLast(new IdleStateHandler(0, 5, 0));
            pipeline.addLast("encoder", new StringEncoder());
            pipeline.addLast("decoder", new StringDecoder());
            pipeline.addLast("handler", new HeartBeatClientHandler(client));
        }
    }


}

Copy

运行结果:

1.客户端长时间未发送心跳包,服务端关闭连接

server start ,port: 9817
一个客户端已连接
\===服务端===(READER\_IDLE 读超时)
\===服务端===(READER\_IDLE 读超时)
\===服务端===(READER\_IDLE 读超时)
\===服务端===(READER\_IDLE 读超时)
\===服务端===(读超时,关闭chanel)
一个客户端已断开连接

Copy

2.客户端发送心跳包,服务端和客户端保持心跳信息

一个客户端已连接
客户端/127.0.0.1:55436--心跳信息--
客户端/127.0.0.1:55436--心跳信息--
客户端/127.0.0.1:55436--心跳信息--
客户端/127.0.0.1:55436--心跳信息--

Copy

3.服务单宕机,断开连接,客户端进行重连

客户端与服务端断开连接,断开的时间为:2018-06-12 23:47:12
Failed to connect to server, try connect after 10s
Failed to connect to server, try connect after 10s
Failed to connect to server, try connect after 10s
connect server successfully
<think>我们正在讨论Air780EPM模块的TCP长连接断线自动连实现。根据引用[3]和引用[4]中提到的经验,我们知道在物联网应用中,由于网络不稳定、休眠唤醒等原因,TCP连接可能意外断开。因此,实现一个健壮的自动机制至关要。 实现思路: 1. 监控连接状态:通过心跳机制或定期检查连接状态来检测连接是否断开。 2. 断线连策略:一旦检测到断开,按照一定的策略(如立即连、延时连)尝试连接。 3. 错误处理和恢复:在多次连失败后,可能需要采取更激进的恢复措施,如复位模块(引用[4])。 在LuatOS的Lua环境中,我们可以通过以下步骤实现: 步骤1:建立TCP连接并设置状态标志 步骤2:在发送数据或心跳时检查连接状态,如果发现断开则触发连 步骤3:实现连函数,包含试逻辑和延时 步骤4:订阅网络事件(如断网、唤醒)来触发连 下面是一个示例代码框架: ```lua -- 定义TCP连接参数 local HOST = "example.com" local PORT = 1234 local sock = nil local isConnected = false local retryCount = 0 local MAX_RETRY = 5 -- 创建TCP连接函数 local function connectTCP() sock = socket.tcp() sock:host(HOST) sock:port(PORT) -- 设置超时 sock:setopt("connect_timeout", 10000) -- 尝试连接 if sock:connect() then isConnected = true retryCount = 0 -- 试计数 log.info("TCP", "连接成功") else isConnected = false log.error("TCP", "连接失败") -- 连逻辑 sys.timerStart(reconnect, 5000) -- 5秒后试 end end -- 连函数 function reconnect() if retryCount >= MAX_RETRY then log.warn("TCP", "超过最大试次数,启模块") sys.restart() -- 或者采用其他恢复措施 return end retryCount = retryCount + 1 log.info("TCP", string.format("尝试第%d次连", retryCount)) connectTCP() end -- 检测连接状态并发送心跳 function checkConnection() if not isConnected then reconnect() return end -- 发送心跳包 local ok = sock:send("PING") if not ok then log.error("TCP", "发送心跳失败,连接可能已断开") isConnected = false reconnect() else -- 等待心跳响应(这里简化处理,实际需要根据协议处理) sys.timerStart(checkConnection, 60000) -- 1分钟后再检查 end end -- 网络事件处理 sys.subscribe("NET_STATE_CHANGED", function(state) if state == "IP_READY" then -- 网络就绪,尝试连接 connectTCP() elseif state == "DISCONNECTED" then isConnected = false -- 网络断开,标记连接失效,等待恢复后连 end end) -- 初始化 sys.taskInit(function() net.switchFly(false) -- 确保关闭飞行模式 net.waitUntil("IP_READY") -- 等待网络就绪 connectTCP() sys.timerStart(checkConnection, 60000) -- 启动心跳检测 end) ``` 关键点说明: 1. 使用`sys.subscribe`订阅网络状态变化事件,当网络恢复时自动连。 2. 通过定时器循环发送心跳包来检测连接状态。 3. 连采用递增延时策略(示例中固定5秒,实际可改为指数退避)。 4. 达到最大试次数后,采取启模块等极端措施(引用[4])。 此外,根据引用[3]的经验,在休眠场景下,TCP连接可能会被底层关闭,因此需要在唤醒后主动连: ```lua sys.subscribe("WAKEUP", function() -- 唤醒后检查连接状态 isConnected = false sys.timerStart(reconnect, 1000) -- 1秒后连 end) ``` 注意:实际应用中,需要根据具体场景调整心跳间隔、连间隔和最大试次数。同时,为了避免频繁启,可以结合网络状态(如信号强度)来调整连策略。 引用[5]提到,在物联网应用中,建议使用Qos0来减少网络负担,但TCP连接本身是可靠的,所以上述机制可以保证在断线后尽快恢复连接。 最后,关于引用[2]中提到的充电状态判断,虽然TCP连无直接关系,但在设计低功耗应用时,可以通过GPIO中断唤醒模块,从而在唤醒后触发连。 总结:自动机制的核心是状态检测和连策略,结合LuatOS提供的事件订阅和定时器功能,可以高效实现。 §§相关问题§§ 1. 如何优化Air780EPM在TCP长连接中的心跳机制? 2. Air780EPM模块在弱信号环境下的TCP连策略有哪些? 3. 如何避免Air780EPM模块因频繁连导致功耗增加?</think>### Air780EPM TCP长连接断线自动连实现方法 在物联网应用中,TCP长连接的稳定性至关要。基于Air780EPM模块的特性(引用[3][4])和LuatOS开发框架,以下是完整的自动连实现方案: #### 一、核心实现原理 1. **状态监控**:持续检测TCP连接状态 2. **断线检测**:通过心跳机制+底层事件双判断 3. **智能连**:采用指数退避算法避免网络风暴 4. **故障恢复**:在多次失败后触发硬件复位(引用[4]) #### 二、完整实现代码 ```lua -- TCP连接配置 local TCP_HOST = "your_server.com" local TCP_PORT = 8080 local HEARTBEAT_INTERVAL = 30000 -- 30秒心跳 local MAX_RETRY = 5 -- 最大试次数 local retry_count = 0 -- 创建TCP连接函数 function create_connection() local sock = socket.tcp() sock:host(TCP_HOST) sock:port(TTP_PORT) sock:setopt("keepalive", 60) -- 启用TCP保活[^3] -- 设置连接超时 if sock:asyncSelect(10000) == 1 then -- 10秒超时 if sock:connect() then retry_count = 0 log.info("TCP", "连接成功") return sock end end sock:close() return nil end -- 心跳任务 sys.taskInit(function() while true do if tcp_sock then local ok = tcp_sock:send("PING") if not ok then log.warn("TCP", "心跳失败,触发连") sys.publish("TCP_DISCONNECTED") end end sys.wait(HEARTBEAT_INTERVAL) end end) -- 主连接任务 sys.taskInit(function() while true do -- 等待网络就绪 net.waitUntil("IP_READY", 30000) -- 创建连接 tcp_sock = create_connection() if tcp_sock then -- 连接成功,等待断开事件 sys.waitUntil("TCP_DISCONNECTED", 180000) -- 3分钟超时 -- 处理断开 tcp_sock:close() tcp_sock = nil log.warn("TCP", "连接断开") end -- 指数退避连 retry_count = retry_count + 1 local wait_time = (2^retry_count) * 1000 -- 2^N秒 wait_time = math.min(wait_time, 60000) -- 最大60秒 if retry_count >= MAX_RETRY then log.error("TCP", "超过试上限,复位模块") sys.restart("TCP_RECON_FAIL") -- 软件复位[^4] -- 或使用硬件复位: gpio.set(pio.RESET, 0) end log.info("TCP", "等待连:"..wait_time.."ms") sys.wait(wait_time) end end) -- 注册底层事件 sys.subscribe("NET_DISCONNECTED", function() sys.publish("TCP_DISCONNECTED") end) sys.subscribe("WAKEUP", function() -- 休眠唤醒事件 if tcp_sock then sys.publish("TCP_DISCONNECTED") end end) ``` #### 三、关键优化措施 1. **双保险检测机制**: - 应用层:定时心跳包检测(30秒) - 底层:订阅`NET_DISCONNECTED`网络断开事件 - 休眠唤醒后强制连(引用[2]充电状态判断类似原理) 2. **智能连策略**: ```mermaid graph LR A[断开检测] --> B{试次数<5?} B -->|是| C[等待2^N秒] B -->|否| D[模块复位] C --> E[连接] E --> F{成功?} F -->|是| G[置计数器] F -->|否| B ``` 3. **功耗优化**: ```lua -- 空闲时进入轻休眠 sys.taskInit(function() while true do if tcp_sock and (sys.runtime() - last_activity > 120000) then pm.request(pm.LIGHT) -- 进入轻休眠模式[^3] end sys.wait(10000) end end) ``` 4. **内存保护**: ```lua sock:setopt("recv_buf_size", 2048) -- 限制接收缓冲区 sock:setopt("send_buf_size", 2048) -- 限制发送缓冲区 ``` #### 四、异常处理增强 1. **DNS故障处理**: ```lua local ip = socket.dns(TCP_HOST) if not ip then log.error("DNS", "解析失败,使用备用IP") ip = "192.168.1.100" -- 备用IP end sock:host(ip) ``` 2. **数据发送容错**: ```lua function safe_send(data) local sent = 0 while sent < #data do local chunk = data:sub(sent+1, sent+512) local ok, written = pcall(sock.send, sock, chunk) if not ok or not written then break end sent = sent + written end return sent end ``` 3. **服务器不可达处理**: ```lua sock:setopt("connect_timeout", 15000) -- 15秒连接超时 ``` #### 五、调试建议 1. 使用日志分级: ```lua log.setLevel(log.INFO) -- 生产环境 log.setLevel(log.DEBUG) -- 调试环境 ``` 2. 关键事件记录: ```lua sys.subscribe("NET_STATE_CHANGED", function(state) log.info("NET", "网络状态变更:", state) end) ``` > **提示**:当遇到极端网络故障时,建议实现双阶段恢复机制:前5次软连,失败后硬件复位(引用[4])。同时确保SIM卡状态正常,避免无效试[^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值