1. 先把方案定下来:4G模组上网的几种路子
作为常年和嵌入式Linux打交道的工程师,我在很多项目里都碰到过同一个需求:板子放在野外机房、车上或者偏远站点,没有网线,没有WiFi,唯一能联网的办法就是插一张SIM卡,通过4G模组上公网。而这个“4G模组上网”的方案,不同人上手路子完全不一样,我见过有人直接在应用层用AT指令发HTTP POST,也有人把模组当串口用,一条条AT指令拼数据,还有人在Linux里把模组挂成PPP点对点拨号。这几个方案各有适用场景,但如果你要在Linux系统上跑socket网络通讯,最可控、最贴近传统网络编程的方式,还是把4G模组直接挂载成一张网卡,然后像操作eth0一样操作它。
这个标题里其实已经把核心说得很明白了:“将4G模组挂载为网卡,利用socket进行网络通讯”。一句话讲透就是:让Linux内核把USB接口的4G模组识别成一个标准网络接口设备,系统天然就有一条通往公网的物理链路,上层不管是爬虫、MQTT、视频推流还是自定义TCP/UDP协议,全部用socket就能跑,不需要关心什么SIM卡状态、AT指令回显、PPP拨号状态机。这对做物联网网关、车载终端、安防监控、无人机地面站、工业数采上云的工程师来说,基本上属于入职第一天就要会的基本功。
适合谁来参考这篇内容?如果你是嵌入式Linux开发者、物联网协议工程师、做车辆远程控制或无人设备通信的,又或者是在x86工控机上玩4G模组的运维老哥,这篇文章可以直接“抄作业”。我会把方案选型、驱动配置、接口参数、socket代码、排查手段全部拆开讲,尽量让一个从零开始的人也能把4G网卡跑起来,然后顺利建立起自己的socket通讯链路。
1.1 先想清楚:为什么推荐“网卡方案”而不是串口AT或PPP
我最早接触4G模块的时候,用过一段时间的串口AT方式。思路很简单:串口发AT指令(比如AT+HTTPGET),模组自己内置协议栈,直接返回HTTP内容。这个思路的好处是省事,MCU都能搞定,不用懂TCP/IP。但用起来真的很憋屈,尤其是做一个需要长连接的业务时,模组内置TCP栈一般只能维持有限几个连接,而且断开重连、心跳、超时的逻辑都写在AT脚本里,一旦上一包指令卡住,整个状态机全乱。到后面我自己都分不清是网络抖动还是AT解析出了问题,调试体验很差。
PPP拨号则是另一种经典方案。在Linux下用pppd+ 串口发AT指令拨号,内核会建立一个ppp0接口,也能跑socket。问题是PPP链路的开销相对高,带宽有损耗,而且pppd这个老家伙在嵌入式系统里的依赖比较多,动不动就要处理 modem chat 脚本、认证、XON/XOFF,出问题的时候排查链路很费劲。尤其是模组走USB接口时,PPP还要一层USB转串口的封装,速度上限也受影响。
相比之下,把模组挂成网卡(RNDIS/ECM/QMI-WWAN),相当于整个TCP/IP协议栈全部由Linux内核接管,4G模组只做一个物理层和链路层的转发。socket生态里能用的东西全都能用,select/poll/epoll、TCP keepalive、iptables、tcpdump抓包,全都无缝衔接。传输效率上,USB Hi-Speed(480Mbps)远大于串口波特率(一般115200bps,顶天几Mbps),实际吞吐性能差距非常明显,4G的带宽潜力才发挥得出来。这也是电信运营商的模块出厂固件大都会默认提供“网卡模式”的根本原因。
1.2 常见方案速览:ECM、RNDIS、QMI-WWAN 到底选哪个
4G模组与Linux主机之间的USB数据通道主要有三种模型,具体走哪种,取决于模组的基带方案和出厂固件配置。
第一种是ECM(Ethernet Control Model),这是USB-IF标准里的以太网仿真模式,Linux内核原生支持,驱动名字叫cdc_ether。优点是简单直接,模组把自己伪装成一个USB以太网卡,插上就出usb0,DHCP就能拿到地址,非常省心。缺点是功能比较简单,新版模组一般只保留ECM或者把它当备用模式。
第二种是RNDIS(Remote NDIS),微软搞的以太网仿真协议,最早是给Windows用的,Linux内核也有rndis_host驱动支持。很多国产模组(比如中兴、紫光展锐方案的)出厂默认就是RNDIS,电脑插上去自动装驱动弹网卡的就是它。Linux下同样插上就会出现ethX或者usb0,使用体验和ECM接近。
第三种是QMI-WWAN,高通方案的模组(像移远EC20、EC200T,广和通L610这些)最常用这种方式。它走的是高通私有的QMI协议,在内核里由qmi_wwan驱动注册一个wwan0网卡。QMI-WWAN的能力比ECM强很多,可以同时承载多个PDP上下文,支持IPv4/IPv6双栈,还能上报信号强度、SIM状态、基站信息等。数据通过USB的QMI通道走,配置工具一般是libqmi的qmicli,也可以用ModemManager统一管理。
还有一种非典型的方案是Gobinet(viagobinet驱动),塞拉(Sierra Wireless)模组用的,原理和QMI类似,这里不展开。
我自己的选择习惯是:能用QMI-WWAN的优先QMI,因为诊断信息丰富,可以拿qmicli -p -d /dev/cdc-wdm0 --nas-get-signal-info直接看信号值,排查“信号差、频段没锁上”这类问题非常直观。但如果现场就是一台老内核的板子,或者模组固件锁死在ECM/RNDIS,那直接用cdc_ether/rndis_host也没问题,网络功能是等价的。下面的实操我会以高通方案QMI-WWAN为主线,同时给出ECM/RNDIS的快速步骤。
| 方案 | 内核驱动 | 网卡名 | 优势 | 缺点 | 适用模组 |
|---|---|---|---|---|---|
| ECM | cdc_ether | usb0 / eth1 | 配置最简单,插上就出网卡 | 功能单一,诊断信息少 | 多数USB网卡式模组 |
| RNDIS | rndis_host | ethX / usb0 | Windows/Linux兼容好 | 协议开销略高,问题定位不方便 | 展锐、中兴等模组 |
| QMI-WWAN | qmi_wwan | wwan0 | 功能强,可查询信号/SIM/基站 | 需要qmicli或ModemManager配合 | 高通方案,移远EC20等 |
2. 硬件与内核准备:动手前先把这几件事办了
很多人拿到模组直接插USB就开始敲命令,结果dmesg里一串错误,心态直接崩了。其实4G模组当网卡这件事,硬件层面比想象中讲究。我踩过的坑包括:供电不足导致模组反复烧录失败、SIM卡弹片接触不良导致网络反复掉线、天线没接导致信号-110dBm直接无法附着网络。这些跟软件没关系,但每个都能让项目卡一整天。
2.1 4G模组选型与硬件电路注意事项
现在市面上常用的4G模组,移远EC20、EC200T、广和通L610、美格SLM750、SIMCom A7600系列都比较常见。选型的时候除了看价格,一定要确认三件事:
第一,模组的USB接口是否引出,且支持网卡模式。大多数带USB接口的4G模组都支持,但有些面向MCU的贴片模组只引出了串口,那个就得走AT方案,不在本文讨论范围。
第二,供电能力是否达标。4G模组在发射的时候瞬时电流很大,尤其是在弱信号环境下,PA(功率放大器)会加大发射功率,瞬间电流可以到2A甚至更高。如果用LDO或者小电流DC-DC供电,电压一跌落模组就直接重启了。我经手的板子上,4G模组供电部分都会选输出能力3A以上的DC-DC芯片,并且靠近模组VBAT引脚放一个大容值的储能电容(220uF以上钽电容或MLCC组合)。说实话,很多“跑着跑着网卡消失”“系统日志里模组反复枚举”的故障,最后查出来都是电源纹波太大或者供电流不够。
第三,天线接口是否预留。4G模组的ANT_MAIN天线是必须接的,不接天线或者天线虚焊,信号强度可能只有-110dBm以下,驻网都会困难,更别提网卡起来之后跑速度。有条件的话分集天线也接上,对弱信号场景的速率提升很明显。
2.2 内核驱动配置:编译内核时把这几项钩上
如果你的系统是标准发行版内核,一般驱动已经编成模块了,直接modprobe就能加载。但嵌入式板子如果自己编内核,务必检查这几项:
CONFIG_USB_NET_DRIVERS:USB网卡总开关,必选。CONFIG_USB_NET_CDCETHER:ECM支持。CONFIG_USB_NET_RNDIS_HOST:RNDIS支持,如果板子可能接Windows类网卡模组就勾上。CONFIG_USB_NET_QMI_WWAN:QMI网卡支持。CONFIG_USB_SERIAL_OPTION:大部分高通模组还需要把USB串口(AT口、日志口)驱动勾上,否则ModemManager和qmicli用不了/dev/cdc-wdm0。CONFIG_USB_SERIAL_QUALCOMM:高通串口驱动,有些模组需要。
在menuconfig里搜索这些项一个个钩上就行。编完后启动系统,插上模组,lsusb能看到对应的VID/PID。比如移远EC20通常是2c7c:0125,SIM7600系列是1e0e:9001或1e0e:9011,具体以你手头模组为准。
有些模组出厂固件默认的是“串口AT模式”,也就是插上USB只能看到/dev/ttyUSB0、/dev/ttyUSB1这些串口设备,没有网卡。这时候需要先用USB转出来的AT串口给模组发指令,切到网卡模式,然后再插拔一次或者重启模组。比如EC20,在AT串口下执行:
AT+QCFG="usbnet",2usbnet参数的含义因固件版本而异,有些版本1代表ECM,2代表QMI,3代表RNDIS。发完之后重启模组(AT+CFUN=0再AT+CFUN=1,或者直接断电上电)。再看dmesg,应该就能看到qmi_wwan/cdc_ether/rndis_host被驱动加载的信息。
2.3 插上模组后的第一步:查看USB枚举状态
在写任何脚本之前,先确认内核认不认这个设备。干净的排查顺序:
lsusb dmesg | tail -50 ls /sys/class/net/lsusb能看到设备说明USB物理链路正常;dmesg里关注有没有qmi_wwan、cdc_ether、rndis_host或者usb0/eth1/wwan0之类的字样;ls /sys/class/net/则能直接看到新网卡接口名。这一步做完,才能判断是驱动没加载、模组固件没切模式,还是USB线/供电有问题。
有一次我拿到一块新板子,插上EC20之后lsusb什么也没有,敲dmesg能看到usb 1-1: new high-speed USB device又立马device descriptor read/64, error -71。排查到最后发现是USB_D+和USB_D-两根线接反了。硬件问题怎么看日志都很明显,所以别一上来就怀疑软件。
3. 把4G模组挂载成网卡:完整实操步骤
这部分我直接按最常用的“高通QMI-WWAN”路径来写,同时兼顾ECM/RNDIS的简易替代方案。整个流程可以拆成四个阶段:模式切换、驱动加载、IP配置、路由与拨号确认。
3.1 模式切换:让模组从“AT串口设备”变为“网卡设备”
新模组上电后,如果lsusb能看到,但ls /sys/class/net/里没有新增网卡,先别急着加载驱动,多半是固件还没切到网卡模式。拿EC20举例,插上后通常会看到四个USB串口:/dev/ttyUSB0(AT口)、/dev/ttyUSB1(日志口)、/dev/ttyUSB2(AT口或数据口)、/dev/ttyUSB3(GPS口之类)。用任意一个AT口发指令查询当前USB模式:
echo -e "AT+QCFG=\"usbnet\"\r" > /dev/ttyUSB0 cat /dev/ttyUSB0 &或者直接用microcom/minicom打开AT口(波特率通常115200)查看返回。如果需要切换,就发:
echo -e "AT+QCFG=\"usbnet\",2\r" > /dev/ttyUSB0注意不同模组的切换指令差异很大。SIMCom的A7600系列有的用AT+CUSBD,有的支持自动识别。最稳妥的做法是翻对应模组的AT指令手册,搜索关键字“usbnet”或者“RNDIS/ECM/QMI”。如果模组本身出厂就是网卡模式,这一步可以跳过。
3.2 加载驱动:让内核识别出网卡接口
确认模式没问题后,加载驱动。以QMI-WWAN为例:
modprobe qmi_wwan modprobe cdc_mbim # 如果走MBIM的话,但一般qmi_wwan就够加载后看dmesg,应该能看到类似:
qmi_wwan 1-1:1.4: cdc-wdm0: USB QMI (wwan) network device qmi_wwan 1-1:1.4: wwan0: register 'qmi_wwan' at usb-...如果没有自动注册网卡,可以强制把接口绑定到驱动:
echo "2c7c 0125" > /sys/bus/usb/drivers/qmi_wwan/new_id这条命令的作用是告诉驱动“你也能管这个VID/PID的设备”。不同版本的qmi_wwan驱动匹配表不一样,如果lsusb看到设备但驱动没加载,这个方法往往能救急。但既然是救急,说明你的内核驱动表可能比较旧,生产环境建议编译新版内核或者打补丁。
如果走ECM/RNDIS,直接用:
modprobe cdc_ether modprobe rndis_host插上之后系统多半直接生成usb0或eth1,不需要额外配置。
3.3 配置APN、拨号与获取IP
网卡有了,不代表就能上网。4G上网需要先激活一个PDP上下文(数据连接),也就是说要告诉模组“用哪个APN来建立数据链路”。APN是运营商分配的接入点名称,一般是cmnet(移动)、ctnet(电信)、3gnet(联通),也有企业专用APN。
在QMI-WWAN方案下,用qmicli来拨号比较标准:
qmicli -p -d /dev/cdc-wdm0 --device-open-net='net-raw-qmi|net-no-qos-header' \ --wds-start-network="apn=cmnet,ip-type=ipv4" \ --client-no-release-cid执行后,如果返回类似Packet data handle: 0x......,说明拨号成功。然后启动DHCP客户端拿地址:
dhclient wwan0如果模组的固件支持自动下发IP(很多ECM/RNDIS模组在数据连接建立后,会自动给Linux网卡分配一个私网IP段),那就不一定需要dhclient,直接ifconfig usb0看一下有没有地址。我见过有些方案里,模组会给主机下发192.168.225.1这种地址,主机直接拿DHCP就能得到192.168.225.x,而且模组自带NAT上网。这种情况下,默认路由也可能已经自动加好了,非常省心。
如果是ECM/RNDIS手动走AT拨号,流程是:
# 串口AT口执行 AT+CGDCONT=1,"IP","cmnet" AT+CGACT=1,1 AT+COPS? # 确认是否已注册运营商网络然后主机侧再dhclient usb0或手动配地址。注意有些模组在AT拨号成功之前不会给主机分配地址,顺序不能反。
3.4 路由、DNS与开机自启:让网络链路稳定可用
拿到IP后,先看路由表:
ip route show正常情况下应该有一条default via <网关IP> dev wwan0或者类似的路由。如果没有,手动添加:
ip route add default via 192.168.225.1 dev wwan0DNS如果没配,在/etc/resolv.conf里加上:
nameserver 223.5.5.5 nameserver 114.114.114.114这里有个小坑:运营商的4G网络下,公共DNS可能被劫持或者污染,建议用国内的公共DNS,实测延迟和成功率都比运营商默认DNS好。然后就可以测试了:
ping -I wwan0 -c 4 114.114.114.114 curl -I https://www.baidu.com注意ping -I wwan0指定从哪个网卡出去,否则多网卡设备很容易出现“明明看着有IP,但就是不通”的诡异情况。
为了实现开机自启,我一般写一个/etc/network/interfaces.d/wwan0或者systemd service。用systemd的方式比较通用:
[Unit] Description=4G QMI network bring-up After=network-pre.target Before=network.target [Service] Type=oneshot RemainAfterExit=yes ExecStart=/usr/local/bin/4g-up.sh ExecStop=/usr/local/bin/4g-down.sh [Install] WantedBy=multi-user.target4g-up.sh里做的事情就是:加载驱动、qmicli拨号、dhclient、加默认路由这些步骤按顺序执行。实际项目里建议把拨号状态做成一个检测函数,如果qmicli --wds-get-packet-status查询到不在已连接状态,就自动重拨,配合网络中断自动重连。这个逻辑我在第五部分展开。
4. socket网络通讯:代码怎么写才能在大流量下稳定运行
网卡有了,IP有了,剩下的就是socket编程。这里不教你hello world级别的connect/send/recv,那是任何一本网络编程书都有的事。我重点讲几个在4G链路上特别容易翻车的点,以及对应的代码写法。
4.1 TCP客户端:设好超时,别让recv卡死一辈子
4G网络的典型特征是延迟波动大。同样一个TCP连接,信号好的时候RTT可能10ms,信号差的时候可能300ms甚至1000ms。更糟糕的是,当模组进入深度休眠或者网络侧释放了无线资源,TCP连接可能长时间没有任何数据到达,这时候如果你的recv是一直阻塞的,一个线程就彻底卡死了。
我的建议是给socket设置接收超时和发送超时,同时开TCP keepalive。代码写起来很简单:
struct timeval timeout = {0}; timeout.tv_sec = 10; timeout.tv_usec = 0; setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); int keepalive = 1; socklen_t len = sizeof(keepalive); setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &keepalive, len); int keepidle = 30; int keepintvl = 10; int keepcnt = 3; setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle, sizeof(keepidle)); setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, &keepintvl, sizeof(keepintvl)); setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPCNT, &keepcnt, sizeof(keepcnt));SO_RCVTIMEO设成10秒的意义是:哪怕接收不到数据,recv最多阻塞10秒就会返回-1且errno为EAGAIN/EWOULDBLOCK。此时你可以发一个应用层心跳包,或者重新connect。keepalive则是让内核在空闲30秒(TCP_KEEPIDLE)后开始探测对端是否存活,防止运营商NAT把空闲连接静默回收。
另外,4G链路的MTU问题值得注意。运营商4G网络的默认MTU一般是1500,但加上隧道封装(比如CAPWAP、GRE)后会变小。如果发现大包不通而小包通,八成是MTU不匹配。可以在网卡上手动调整:
ip link set dev wwan0 mtu 1400做了这个调整之后,TCP层的MSS也会跟着降下来,很多莫名其妙的“能Ping通但打不开网页”“上传大文件到一半卡死”问题都会消失。
4.2 TCP服务端:多客户端接入别只靠一个accept循环
如果你是用4G模组做设备端,那大概率是设备主动连接云服务器,属于TCP客户端场景。但也有场景是设备作为服务端,比如车机被手机App直连调试,或者一对多数据采集。这时写TCP服务端要注意,别在单个线程里同步处理多个连接。
最简单的方案是“accepted一发,线程一收”:
while (1) { client_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &addr_len); if (client_fd < 0) { perror("accept"); continue; } pthread_t tid; pthread_create(&tid, NULL, client_handler, (void *)(intptr_t)client_fd); pthread_detach(tid); }每个连接开一个线程,代码直观,但并发高时线程开销不可忽视。生产级建议用epoll。不过如果只是4G网络下的几个并发连接,pthread方案完全够了,不必过度设计。关键点在于处理函数里同样要设超时,毕竟4G链路断开时对端不会立刻感知,你不设超时,一个recv会一直挂着,资源泄漏很快就会把板子拖垮。
此外,如果设备上同时有以太网和4G网卡,bind 的时候一定要指明监听地址。默认0.0.0.0会监听所有网卡,如果只想让4G网卡提供服务,就bindwwan0上的IP地址。这个细节能避免安全问题,比如4G侧暴露了不该暴露的调试端口。
4.3 UDP场景:处理好丢包与乱序,别把“能收到”当成“能用”
有些业务(比如设备GPS轨迹上报、音视频通话)会走UDP。4G网络对UDP的容忍度其实一般,尤其在弱信号环境和网络拥塞时,丢包率会明显上升。UDP报文通常没有ACK重传机制,应用层要么接受丢包,要么在业务报文里增加序列号、时间戳和超时重发。
一个实用做法是:应用层自定义一个轻量包头,包含magic、seq、timestamp和payload长度。接收端根据seq判断乱序和丢包,如果丢了就向发送端请求重传,重传用TCP或者UDP都行。这个“消息确认+重传”机制看着简单,但比直接裸UDP可靠得多。
如果对实时性要求很高(比如视频流),那UDP裸发也没问题,但接收端的socket缓冲区要调大:
int rcvbuf = 4 * 1024 * 1024; setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)); int sndbuf = 4 * 1024 * 1024; setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf));默认的socket缓冲在4G高速率下很容易丢包,尤其是接收端处理不过来的时候,内核socket缓冲区一满,新到的UDP包直接被丢弃。调大之后,抗短暂抖动能力好很多。
下面给一个完整的TCP客户端示例,包含了初始化、超时、断线重连的逻辑框架:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/tcp.h> #include <arpa/inet.h> #define SERVER_IP "120.xxx.xxx.xxx" #define SERVER_PORT 8080 static int connect_server(void) { int sockfd; struct sockaddr_in server_addr; struct timeval timeout = {10, 0}; sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { perror("socket"); return -1; } memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERVER_PORT); inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr); if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { perror("connect"); close(sockfd); return -1; } setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); int keepalive = 1; setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive)); return sockfd; } static void send_heartbeat(int fd) { const char *hb = "{\"type\":\"heartbeat\"}"; send(fd, hb, strlen(hb), 0); } int main(void) { int sockfd; char buf[1024]; while (1) { sockfd = connect_server(); if (sockfd < 0) { sleep(5); continue; } while (1) { ssize_t n = recv(sockfd, buf, sizeof(buf), 0); if (n > 0) { // 正常处理业务数据 printf("recv %zd bytes\n", n); } else if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { // 超过10秒没数据,发心跳探测 send_heartbeat(sockfd); } else { // 连接断开 break; } } close(sockfd); sleep(3); } return 0; }这段代码的核心逻辑就一个循环:连接断了就重连,连接活着但没数据就发心跳。在4G模组上实际跑的时候,这条主循环足够稳定,我试过挂在车上颠簸一天,网络切换好几轮,程序一直没挂。
5. 常见问题与排查技巧实录
这部分把我实际项目中碰到过的故障和排查方法整理成一个速查表,按“现象–命令–原因–解法”的结构来写,都是能直接搜索记忆的干货。
| 现象 | 排查命令 | 常见原因 | 解决办法 |
|---|---|---|---|
| lsusb看不到模组 | dmesg | grep -i usb; lsusb | USB线松、供电不足、模组没上电 | 检查硬件供电和USB枚举,测VBAT电压 |
| 看到USB设备但没有网卡 | dmesg | tail; ls /sys/class/net/ | 模组固件处于AT串口模式;驱动没加载 | 发AT指令切usbnet模式;modprobe对应驱动 |
| 网卡已出现但没有IP | dmesg | grep -i qmi; qmicli ... | APN没配好、SIM卡未注册、PDP激活失败 | 用AT+CGDCONT设置APN,确认AT+CREG?返回1或5 |
| 能Ping通IP但不能解析域名 | cat /etc/resolv.conf; nslookup | DNS配置错误或运营商DNS异常 | 改为223.5.5.5 / 114.114.114.114 |
| 小包能通大包不通 | ping -s 1472; ip link show | MTU不匹配 | 调低MTU为1400,关闭TCP分段卸载 |
| 运行一段时间网卡消失 | dmesg | tail; lsusb | 供电跌落,模组重启或USB断开 | 加强供电,检查DC-DC纹波,加储能电容 |
| socket连接经常超时 | tcpdump -i wwan0; netstat -an | NAT超时回收空闲连接 | 应用层心跳间隔小于运营商NAT超时,一般30~60秒 |
| 网速很慢 | qmicli --nas-get-signal-info | 信号弱或天线没接好 | 换天线位置,锁频段,检查RSRP/RSRQ |
5.1 网卡消失/模块反复重启:八成是供电问题
这个故障我在车载项目上踩得最深。板子在实验室跑得好好的,装到车上跑一段时间,4G网卡就消失了,日志里能看到模组重新枚举的过程。一开始怀疑是驱动崩溃,各种升级内核、加重启守护脚本都没用。后来用示波器抓VBAT电压,发现车辆启动瞬间电压跌落到了3.1V,而模组的正常工作电压下限是3.3V。问题根源是车载电瓶在启动瞬间电压波动极大,DC-DC输入又不稳,输出直接从3.8V掉到3.1V。
解决办法是选了一颗宽压输入、输出3.8V的DC-DC,并且在输出端加了470uF电容阵列;更关键的是在DC-DC的反馈电阻上做了一点“预加重”,让它在负载瞬变时能快速响应。改完之后同样的路试,网卡再也没消失过。
所以遇到“网卡跑着跑着没了”这种问题,不要先怀疑软件,优先查硬件供电,用示波器看VBAT在收发数据瞬间的跌落幅度,比你在代码里加一百行守护逻辑都管用。
5.2 信号满格但网速慢:别只看RSRP,还有SINR和频段
有次客户反馈4G设备上传视频卡顿,我远程一查,模组上报的RSRP(参考信号接收功率)是-75dBm,按说信号非常好。但点开SINR(信噪比)一看,只有2dB,这对LTE来说属于底噪恶劣环境,实际速率根本起不来。后来定位到是设备装在金属配电箱内,天线虽然收到了很强的基站信号,但也收到了大量杂散干扰。
所以在调试网速的时候,别只看信号格数。用qmicli查--nas-get-signal-info,里面能看到RSRP、RSRQ、SINR。RSRP大于-90dBm、SINR大于10才算正常。如果SINR差,优先考虑把天线引到箱子外面,或者换高增益天线,比在软件里调TCP窗口参数有效得多。
另外一个隐藏因素是频段锁定。有些模组默认会搜全频段,如果某个频段信号虚高但拥塞,模组会一直停在那个频段上。你可以用AT指令锁频段,比如把EC20锁定到B3频段:
AT+QCFG="band",F,1A0,1A0锁完再测网速,通常会有惊喜。
5.3 socket调不通:学会用tcpdump代替猜测
做socket调试时,最忌讳的就是在代码里加了一堆printf,然后看着日志猜。我建议把tcpdump用起来,在4G网卡上抓包,一看便知:
tcpdump -i wwan0 -nn -vv port 8080如果看到SYN发出去但没有SYN-ACK回来,说明数据包根本没到达服务器,或者被运营商NAT丢了,先排查APN、路由、防火墙;如果SYN-ACK回来了但连接还是建立不了,那可能是本机iptables规则把入包丢了。抓包能精确区分“是本机没发出去”还是“对端没回应”,比你盯着errno猜效率高一倍不止。
如果设备上没有tcpdump,可以先装一个,ARM板的rootfs里一般都能通过opkg/apt安装。实在装不上,也可以用cat /proc/net/tcp看socket状态,但体验差太多,还是建议直接上tcpdump。
6. 总结一下我的实际感受吧
4G模组挂网卡这件事,技术本身不算高深,但它是很多物联网项目的承重墙。把这条链路捋顺了,后面无论是做远程运维、数据上云还是视频传输,都只是socket层的编码问题。反过来,如果链路层没做好,应用层再炫酷都会在弱网环境下崩溃。
我最想强调的还是那句话:硬件供电、天线、SIM卡注册状态这些“非编程”的问题,在工作中占了实际排查量的一大半。代码层面的socket超时、心跳、重连这些套路是通用的,但真正决定一个4G设备稳不稳的,往往是开机自动拨号、掉线自愈、供电纹波这些容易被忽视的地方。希望这篇文章能帮你少走一点我当年走过的弯路。
最后再分享一个实用小习惯:不管用哪个模组,我都会在系统里留一个简单的shell脚本,手动拨号、查信号、重启模组、清路由,分分钟就能在野外现场快速排障。这个脚本写一次,后面每个项目几乎都能复用,强烈建议你也照着维护一个。