C#短信猫群发实战:从AT指令到PDU编码的完整指南

发布时间:2026/10/16 3:17:50
C#短信猫群发实战:从AT指令到PDU编码的完整指南
简介这是一份基于短信猫硬件的C#短信群发完整工程源码面向具备一定C#基础的开发者可直接编译运行用于批量短信发送的企业通知、营销及客户维护场景。源码核心逻辑涵盖GSM/AT指令串口通信、多线程并发发送、短信模板与号码库的数据库管理以及WinForms界面和多用户权限控制体现了从硬件控制到业务管理的完整链路。压缩包为RAR格式共114个文件大小约721KB以38个cs主程序文件为核心辅以resx/resources资源文件、dll运行库、可执行文件和数据库文件并包含项目工程与说明文档文件分类清晰、便于检索。已有120人学习下载。从内容预览来看主窗体、发送短信、客户导入、用户管理等模块划分明确适合希望研究C#串口编程、GSM短信收发或快速搭建企业级群发系统的开发者参考借鉴。1. 短信群发这个需求C#加短信猫为什么仍然能打短信群发这件事放在今天看确实有点“复古”但做过内部系统的人都知道它依然是不少业务场景里的硬需求。C#短信群发源码基于短信猫是一套把 AT 指令直接封装成 C# 类库的本地短信发送服务核心思路是让普通 SIM 卡插在 GSM 模块上通过串口下发指令完成批量发送。它解决的是三类问题不想按条付费给第三方 API、短信内容涉及内部数据不能出内网、以及发件人需要一个固定号码而不是随机号码。适合有 .NET 基础、要搭内部通知或告警通道的团队跑通它不需要额外购买云服务一台工控机加一个短信猫就能开工。2. 短信猫选型与 AT 指令原理先搞懂串口链路再谈发送2.1 短信猫的本质与选型三要素短信猫并不是一个神秘设备。拆开看它就是一块工业级 GSM 通信模块加上 SIM 卡座、天线接口、电源管理电路和串口电平转换芯片。模块负责射频收发和协议栈串口是控制通道SIM 卡提供号码和鉴权数据。买来的成品短信猫通常是一个带 RS232 或 USB 转串口的小盒子插入 SIM 卡接上电源和天线就构成了一个独立的短信发送单元。从软件角度看短信猫就是一个串口设备使用标准 AT 指令集控制。选型时我会关注三个硬指标第一模块支持的频段是否覆盖当前网络制式很多旧模块只支持 2G而现在不少地区 2G 退网后这类模块根本注册不上网络第二模块是否支持 PDU 模式少数简化版只支持 Text 模式而 Text 模式处理中文短信非常糟糕第三供电方式工业级模块需要 5V/2A 左右的稳定电源USB 供电容易在批量发送时掉线。这三个指标直接决定了代码能不能跑通而不是看它外观像不像路由器。选择 C# 而不是其他语言理由很实际System.IO.Ports.SerialPort 是 .NET 内置的串口通信类支持事件驱动和数据缓冲写 AT 指令的应答等待逻辑非常顺手而且不少内部管理系统本来就是 .NET 技术栈把短信服务集成到现有后台不需要引入额外运行时。如果你团队是 Java 或 Python 栈用同样的思路也能实现但这套源码省掉的是你重新踩一遍 AT 指令、PDU 编码、状态机的时间。2.2 AT 指令与 PDU 模式两条核心命令撑起整个发送流程短信猫的指令集很庞大但日常发送短信只需要核心的十几条。AT 是握手测试ATCMGF 切换文本模式和 PDU 模式ATCMGS 发起短信发送ATCMEE 打开错误码详情ATCREG 查询网络注册状态。主流的做法是先发 AT 确认设备在线然后 ATCMGF0 切到 PDU 模式再按 PDU 编码规则组装内容最后用 ATCMGS 带长度下发。为什么要坚持 PDU 模式Text 模式本质上是模块直接收 ASCII 内容中文短信需要模块厂商自己实现编码不同模块的兼容性差异很大。PDU 模式则是 GSM 协议栈的标准数据格式所有模块都必须支持你把短信内容编码成 UCS2 十六进制放进 PDU 包里模块只负责发射不参与编码。这意味着同一套编码代码换了不同批次甚至不同厂家的短信猫行为完全一致。到目前为止我没有遇到过 PDU 模式兼容性翻车的情况但 Text 模式下的乱码问题见过太多次。对比维度PDU 模式Text 模式中文支持UCS2 标准编码兼容性好依赖模块私有编码经常乱码长短信可手动分片模块支持不稳定跨品牌一致性高低适用场景生产环境英文测试或调试下面是一段精简的 PDU 编码函数参数是目标手机号和短信内容返回值是可直接发送的十六进制字符串private static string PduEncode(string phone, string content) { // 1. 目标地址TP-DA号码前加86国际前缀然后奇数位补F并按两两倒序排列 string daPhone 86 phone; if (daPhone.Length % 2 1) daPhone F; string da ; for (int i 0; i daPhone.Length; i 2) { da da daPhone[i 1] daPhone[i]; } string tpDa 91 da.Length.ToString(X2) da; // 2. 用户数据TP-UD用UCS2大端编码短信内容 byte[] udBytes Encoding.BigEndianUnicode.GetBytes(content); string ud BitConverter.ToString(udBytes).Replace(-, ); // 3. 组装TPDU01是MTI00是MR08表示未启用UDHI且使用UCS2编码最后是UD长度 string tpdu 0100 tpDa 0008 ud.Length.ToString(X2) ud; return 00 tpdu; // 00表示使用SIM卡默认短信中心 }逻辑说明手机号倒序这一步最容易出错PDU 规范要求地址按半字节 BCD 码排列所以号码会被重排成看似乱序的字符串。短信内容按 UCS2 编码每个汉字占两个字节70 个汉字正好是 140 字节恰好等于单条短信的容量上限。如果内容超过 70 个汉字就必须做长短信分片后面的避坑章会专门讲。参数说明tpDa 前面的 91 表示目标号码类型是国际格式如果你的短信猫只在本地使用号码没有 86 前缀则需要自己判断是否补前缀否则部分运营商会拒绝投递。smsc 字段这里固定为 00意思是让模块从 SIM 卡读取短信中心号一般不需要手动配置但如果你的 SIM 卡里短信中心号损坏就得用 ATCSCA? 查询后手动填入。返回的字符串长度除以 2 就是 ATCMGS 需要带的 PDU 字节数这个数值不能算错多一位或少一位都会让模块返回 ERROR。2.3 短信猫与 HTTP API 的选型边界很多人会问现在短信 API 这么便宜为什么还要自建短信猫。答案要看场景。第三方 API 的计费是按条算的量大了是一笔持续成本而且短信内容要经过第三方服务器内部系统的验证码、告警信息、客户资料都不适合送出去。短信猫的成本是一次性硬件投入加 SIM 卡月租数据全程不出内网发件号码是固定的适合日均几百到几千条、对发送率要求不极端的内部场景。但边界也很清楚短信猫不适合千万级营销轰炸单猫每秒最多能发两三条多猫池也撑不起大促节点它也不适合需要全国覆盖的短信验证码业务因为短信猫依赖本地信号放在机房或地下室信号不好时发送率会很难看。简单说内部通知用短信猫对外业务用 API两者不是替代关系是互补关系。这套源码解决的是前者能把内部通知通道的成本压到最低。3. 源码结构拆解从串口打开发短信的完整链路3.1 工程结构与核心类职责拿到源码包后第一步不是看代码而是先看目录结构。这套资源的工程结构不复杂但类划分很清晰串口通信封装在 SmsModem 类里消息队列和重试逻辑在 SmsQueueService配置读取集中在 SerialPortConfig入口是一个简单的控制台宿主程序。这种分层的目的是把硬件操作和业务逻辑解耦以后要把它改造成 Windows 服务或者 Web API只需要替换入口层串口和队列的逻辑原封不动。文件职责关键接口SmsModem.cs串口封装、AT指令发送与应答解析Open() / SendSms() / Close()PduEncoder.cs短信PDU编码支持UCS2中文Encode(phone, content)SmsQueueService.cs发送队列、重试、失败日志Enqueue() / Start() / Stop()SerialPortConfig.cs串口参数与短信中心号配置Load()Program.cs演示入口加载配置并启动服务Main()核心设计逻辑在 SmsModem 里它的内部状态决定了整个发送流程。我打开源码时最关注的是它的应答等待方式是同步轮询读串口还是用 DataReceived 事件拼字符串。这套源码用的是同步轮询加超时机制虽然看起来没那么“优雅”但胜在流程可控出错时能精确知道卡在哪一步。你如果打算改成事件驱动要注意串口缓冲区的粘包和半包问题AT 指令的应答不是一次到位的必须自己做累积拼接。3.2 串口初始化与 AT 握手所有短信功能的前提是串口能稳定通信。初始化串口的参数基本固定波特率 9600、数据位 8、校验位 None、停止位 1几乎所有的 GSM 模块出厂默认都是这一组参数。如果你的模块之前被改过波特率就需要先用其他工具恢复出厂设置这属于硬件层面的问题代码层面能做的就是提供一个可配置的波特率字段。public bool Open(string portName, int baudRate) { _port new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One); _port.ReadTimeout 3000; _port.WriteTimeout 3000; try { _port.Open(); _port.WriteLine(AT\r); string resp _port.ReadTo(OK); if (!resp.Contains(OK)) { _port.Close(); return false; } _port.WriteLine(ATCMGF0\r); _port.ReadTo(OK); return true; } catch (TimeoutException) { return false; } }逻辑说明这里用 ReadTo 方法直接读到 OK 为止比手动拼字节流省事很多但要注意 ReadTo 是阻塞的超时时间设成 3000 毫秒足够如果模块没响应抛出的 TimeoutException 会被捕获并返回 false。ATCMGF0 是把模块切换到 PDU 模式这一步必须在每次打开后执行因为模块不会记忆上次的运行模式掉电重启后默认回到 Text 模式。如果你看到日志里一直卡在发送失败先检查是不是忘了在连接后切模式。参数说明波特率可以从配置文件读取但建议第一优先用 9600除非你确认模块支持更高速率并已用 ATIPR 改过。ReadTimeout 和 WriteTimeout 这两个值也不要设太大3 秒对于 AT 应答已经够用设成 30 秒反而会让故障时日志死等排查问题效率极低。3.3 单条短信发送的状态机发送单条短信是这套源码最核心的方法也是新手最容易写崩的地方。完整流程有四步第一步发 ATCMGS长度第二步等模块返回 提示符第三步填入 PDU 数据和结束符 0x1A第四步等模块返回 OK 或 CMGS 序号。任何一步超时都要走错误处理不要认为模块一定会按预期响应。public bool SendSms(string phone, string content) { if (!_port.IsOpen) return false; // 1. 编码并计算PDU字节长度 string pdu PduEncoder.Encode(phone, content); int byteLen pdu.Length / 2; // 2. 发起发送指令等模块给出“”提示 _port.Write(ATCMGS byteLen \r); if (!WaitFor(, 5000)) return false; // 3. 发送PDU数据以0x1A结束 _port.Write(pdu); _port.Write(new byte[] { 0x1A }, 0, 1); // 4. 等待模块返回最终结果 return WaitFor(OK, 10000) || WaitFor(CMGS, 10000); }逻辑说明WaitFor 方法内部循环读取串口缓冲匹配到目标字符串返回 true超时返回 false这一步的细节决定了整个发送的稳定性。发送结束符 0x1A 是 ASCII 码的 CTRLZ表示 PDU 数据输入完毕这个字节必须单独写不能转成字符串跟在 PDU 后面否则会破坏二进制数据流。最后一步有些模块返回 OK有些返回 CMGS: 序号所以两个都等只要其中一个命中就算成功。参数说明第 2 步的 5 秒超时和第 4 步的 10 秒超时是经过实践调出来的。模块返回 提示符通常很快5 秒完全够用但发送后等待网络侧确认可能需要更久在下车库、电梯井这类信号差的地方10 秒偶尔都不够。如果你把第 4 步超时设到 3 秒那么信号弱时就会出现大量“超时但实际发送成功”的重复短信这一点特别坑避坑章还会展开。3.4 队列、重试与失败落盘单条发送稳定后批量发送要考虑的就不是编码问题而是节奏和容错。SmsQueueService 做的事情很朴素接收消息放入内存队列后台线程按固定间隔逐条取出并调用 SmsModem 发送失败的重试几次最终仍失败的把消息写入本地日志文件。很多新手会直接开多线程并发发短信这在短信猫场景里是大忌因为模块本身是单工设备并发写入串口会让 AT 指令互相交错反而降低成功率。public void Enqueue(SmsMessage msg) { lock (_queue) { _queue.Enqueue(msg); } _signal.Set(); }逻辑说明队列用锁保护入队后通过信号量通知后台线程立即处理。发送线程每次只取一条发送完成后休眠 SendInterval 指定的毫秒数再取下一条。这个设计让发送节奏完全可控不会因为消费者速度波动导致模块过载。重试逻辑放在发送线程内部失败时先记录到失败日志再尝试下一次重试重试周期可以用指数退避比如第一次等 5 秒第二次等 10 秒避免连续失败时疯狂打串口。参数说明MaxRetry 设为 3 是保守值如果你的 SIM 卡偶尔信号不好3 次重试能覆盖大部分瞬态故障。失败日志建议包含消息内容、目标号码、失败阶段、错误码和时间戳这些字段决定了后续排查时你能快速定位是编码问题、串口问题还是网络问题。这套源码的队列部分没有引入第三方消息组件纯粹用内置集合实现好处是部署简单坏处是进程重启会丢失内存中未发送的消息对可靠性要求高的场景可以考虑在入队时同步落库。4. 跑通第一批发短信参数配置与验证步骤先单条后批量4.1 硬件连接与环境体检开工前先做五个检查短信猫这东西软件问题都好修硬件问题最折腾。我在部署前会固定做五个检查第一天线是否拧紧GSM 模块如果没有天线信号强度会直接掉到不可用第二SIM 卡是否插到位很多模块的卡座需要用力推到底并卡住没卡到位时 AT 指令能通但注册不上网络第三电源适配器是否满足电流需求USB 供电的模块发个三五条没问题发到几十条就可能过热重启第四串口线是否是好的交叉线RS232 串口线分直连和交叉接错直接无响应第五设备管理器里能不能看到 COM 口号USB 转串口的驱动是否安装成功。这五个检查做完后先用一个最简单的串口调试工具做 AT 指令体检顺手测三条指令AT 看能否返回 OKATCMEE1 打开详细错误码ATCREG? 看网络注册状态。注册状态返回 0 表示未注册、1 表示已注册本地网络、5 表示已注册漫游网络只有看到 1 或 5 才能进入下一步。这一步不需要写代码但它把硬件链路和软件链路分成两个独立问题排查时能少走很多弯路。指令预期响应说明ATOK模块在线ATCMEE1OK打开详细错误码ATCREG?CREG: 11 或 5 表示已注册网络ATCSQCSQ: 15,0第一个值大于 10 才可靠4.2 核心配置参数串口号、短信中心号与发送节奏源码的配置都集中在一个配置文件里这是最舒服的设计。使用短信猫的场景各不相同有人用 COM3 物理串口有人用 USB 转串口的 COM5有人用四口短信猫池配置集中后改一个文件就能切换硬件。参数示例值说明PortNameCOM3串口号设备管理器中查看BaudRate9600与模块出厂默认一致SmsCenter留空留空则使用 SIM 卡内置短信中心号SendInterval500两条短信之间的最短间隔单位毫秒MaxRetry3单条消息最大重试次数TimeoutPrompt5000等待 提示符的超时TimeoutResult10000等待发送结果的超时配置文件的读取逻辑在这里public static SmsConfig Load(string filePath) { var doc XDocument.Load(filePath); var root doc.Root.Elements().ToDictionary(e e.Name.LocalName, e e.Value); return new SmsConfig { PortName root[PortName], BaudRate int.Parse(root[BaudRate]), SendInterval int.Parse(root[SendInterval]), MaxRetry int.Parse(root[MaxRetry]), SmsCenter root.ContainsKey(SmsCenter) ? root[SmsCenter] : }; }逻辑说明这里用一个字典把 XML 节点转成配置对象好处是缺字段时会直接抛异常不会让程序带着默认值悄悄跑起来。SmsCenter 留空时PDU 编码器会在第一字节用 00让模块从 SIM 卡读取短信中心号这个设计对多卡场景很重要因为不同运营商的短信中心号格式完全不同写死反而容易翻车。SendInterval 控制发送节奏500 毫秒是安全下限如果你用的猫散热不好、电源又不够500 毫秒间隔会频繁触发模块自动开关机建议调成 1000 到 1500 毫秒。4.3 验证链路发一条测试短信看日志和手机两端配置完成后不要直接跑批量发送。先发一条测试短信到自己的手机然后对比三样东西手机收到的短信内容是否正确、串口日志里是否出现 OK 或 CMGS 响应、发送耗时是否符合预期。这一步能把编码问题和硬件问题一次性暴露出来。[10:12:01] ATCMGF0 - OK [10:12:01] ATCMGS32 - [10:12:01] PDU: 010091683110...00085265... [10:12:02] CMGS: 198 - OK [10:12:02] 发送成功耗时 1.2 秒日志的第 3 行是 PDU 内容如果你发现 PDU 里手机号是正序而不是倒序排列或者汉字编码变成了 ASCII 码那一定是 PduEncoder 出了问题直接拿十六进制对照规范就能定位。第 4 行出现的 CMGS: 198 中的 198 是模块上报的消息序号不是手机号尾号这个序号可以用来做消息去重或对账。整条链路从发出到返回 OK 只用了约 1.2 秒属于正常水平如果超过 5 秒大概率是信号问题先别怀疑代码。发完测试短信后再看一眼手机收件箱确认没有出现乱码、重复或截断。这一步听起来很原始但它是最可靠的验收标准。短信猫是个黑匣子AT 指令返回 OK 不代表运营商会真的投递只有手机端收到内容才是硬证据。花两分钟做这个验证能省掉后面排查“为什么用户说没收到短信”的两个小时。5. 短信猫避坑指南串口占用、乱码、假成功五个常见坑5.1 串口打开报 IOException被调试工具占用了现象程序启动时提示 Access to the port is denied 或 IOException串口根本打不开日志里报错后进程退出。原因最常见的情况是之前在用的串口调试工具没有关闭或者安全管理软件把 COM 口当成了可移动设备锁定。另一个常见原因是模块本身被其他进程占用比如你同时开了两个测试程序。解决先打开设备管理器确认端口是否还在再关掉所有可能占用串口的工具然后重新插拔 USB 转串口线让系统重新枚举端口。如果重新插拔后 COM 口号变了程序会继续报错因为配置里还是旧的 PortName。这就是为什么配置文件里写 COM 号的地方要多加一行注释提醒防止别人接手时一脸茫然。从那以后我每次部署前都会确认串口没被占用再启动程序避免白忙一场。5.2 ATCMGS 返回 ERRORSIM 卡或信号层面的问题现象发送流程已经在等 提示符PDU 数据也发送了但模块最终返回 ERROR 而不是 OK发送失败。日志里看不到更详细的错误信息。原因模块只能返回通用的 ERROR具体是 SIM 卡欠费、PIN 码没解除、还是信号弱导致的网络拒绝它不会主动告诉你。很多新手在这里卡半天一直怀疑代码实际上问题出在卡上比如插了一张已被限制的物联网卡或者卡槽接触不良导致模块读不到卡。解决发送前先做两步排查——用 ATCMEE1 打开详细错误码然后逐条执行 ATCPIN? 查询 SIM 卡状态、ATCSQ 查询信号强度、ATCREG? 查询网络注册。CMEE 打开后再遇到 ERROR 时模块会返回类似 CME ERROR: 302 这样的具体编号查一下错误码表就知道是卡问题还是网络问题。ATCSQ 返回的第一个数值在 10 以下属于信号很差发送失败很正常这时候优化天线位置比改代码更有效。5.3 中文短信变成乱码PDU 编码字节序搞错现象手机能收到短信但内容是一串乱码或者空的英文和数字正常汉字全部不对。或者收到的短信是一半正常一半乱码。原因绝大多数情况是 UCS2 编码时高低字节顺序写反了或者整个字符串被错误地按 ASCII 逐字节处理。比如“测试”两字的 UCS2 编码是 D5 E2 CA D4如果按小端序写就成了 E2 D5 D4 CA模块按规范解析后自然是乱码。另一个可能性是模块被切到了 Text 模式Text 模式下中文依赖模块厂商的私有编码换模块就变脸。解决确认发送前执行了 ATCMGF0代码里使用 Encoding.BigEndianUnicode 而不是 Encoding.Unicode因为后者在 Windows 上是小端序。如果你已经改了代码还乱码用串口调试工具手动发送一条 PDU 数据直接排除程序的问题。手动发送无误说明模块和代码没问题问题出在程序里的编码分支手动发送也乱码那就是模块硬件对 UCS2 的支持有缺陷只能换模块。长短信乱码则多半是分片时用户数据头算错了每一片的长度和序号必须严格递增。5.4 ATCMGS 返回 OK 但手机收不到网络注册假象现象日志显示发送成功AT 指令返回了 OK但手机始终收不到短信。批量发送时这种情况尤其致命看起来所有消息都发出去了实际上用户一条都没收到。原因ATCMGS 返回 OK 只代表模块把数据包交给了网络侧不代表运营商完成投递。网络注册状态不对时模块甚至会返回 OK但数据包根本没有进入正常的短信中心。还有一种情况是模块返回 OK 时已经超时代码把超时当成失败并重试结果短信重复发送最终用户收到好几十条重复的。解决发送前先执行 ATCREG?确认状态是 1 或 5 再继续发送后超过 10 秒没收到 CMGS 响应不要立刻重发先查询模块状态因为可能短信已经发出去了重试只会造成重复。如果业务要求绝对不重复就得在业务层做消息幂等比如生成一个全局唯一的业务 ID用户侧靠业务 ID 过滤重复短信这不是短信猫能解决的问题是应用层必须设计的。5.5 批量发送时模块突然不响应供电不足与散热现象连续发几十条后程序卡在 WaitFor 超时模块完全无响应或者模块自动重启COM 口短暂消失后重新出现。日志里没有报错就是一直没有响应。原因这是短信猫批量发送场景最典型的翻车点。许多 USB 口的供电能力只有 500mA而 GSM 模块在发射瞬间电流会冲到 2A 以上供电不足直接导致模块重启。另一个原因是发送间隔太短模块射频部分持续工作热量积聚后触发硬件保护。这两个问题叠加时就会出现“发十来条正常发到四五十条就开始抽风”的玄学现象。解决换一个 5V/2A 以上的独立电源适配器给短信猫供电不要依赖 USB 口。软件上把 SendInterval 从 500 毫秒调到 1000 毫秒以上给模块留出发射间隙。如果是多口短信猫池还要确认每个口的供电单元是独立的而不是共用一条电源线。我见过最夸张的一次是某个项目把四口短信猫池接到同一个 USB Hub 上发到第二轮四个口全部掉线换独立电源后问题消失。从那以后我遇到批量发送不稳第一反应就是先量电源而不是改代码。6. 进阶玩法短信猫池调度与状态报告回执验证单只短信猫的发送速度上限大约在每分钟 20 到 30 条批量需求再大就得上多口短信猫池。常见的调度方式是用轮询分发每只猫维护各自的队列调度器每取一条消息就切换到下一只猫的队列避免同一只猫连续发送导致过热。实现上只需要一个计数器加一个锁核心代码如下public SmsModem Next() { lock (_modems) { var modem _modems[_cursor]; _cursor (_cursor 1) % _modems.Count; return modem; } }逻辑说明这里用互斥锁保证多线程环境下计数器不会错乱取模运算让负载均匀分布到各个猫上。如果你的猫池混合了不同型号和发送速度的猫轮询不是最优方案可以改成“按当前队列长度取最小”的算法把选择逻辑从取模改成按待发送数排序取第一个即可。参数说明队列长度需要在每次取走后更新锁的范围要覆盖选择和更新两步否则多线程下可能出现两只线程拿到同一只猫。发送状态回执这边一个实用的技巧是设置状态报告字节。在 PDU 的 TP-SRR 位设为 1让短信中心在目标手机收到短信后返回状态报告模块通过 ATCNMI 指令把报告推给电脑。这样不仅知道模块发送成功还能知道用户手机真正收到了。验证时在代码里记录每一条消息的发送时间戳收到状态报告后再记录回执时间戳两者相减就是短信端到端投递延迟。这个数据很有价值它能告诉你当前信号环境下 10 秒超时是否合理也能在运营商网络拥堵时给你第一手告警。如果你手边就是这份源码照着前面的验证顺序走一遍通常半小时内就能看到手机收到第一条短信。从那以后我每次部署短信服务都强制走一遍查串口、看注册、发测试、对比日志这四步先单条后批量先短信猫池后多猫池确认所有环节才交给业务方使用。希望这些经验能帮你在短信群发这条路上少踩几个坑。本文还有配套的精品资源点击获取