BC26 支持使用 Socket 进行 TCP 和 UDP 协议通信, 这两个协议也是 BC26 支持的众多通信协议的基础. 本文讲解如何使用这两个协议与服务器端进行通信. 在学习这篇文章前, 请首先使用 AT+CPSMS=0 指令将节电模式 (PSM) 关闭. 否则每隔十来秒, MCU 就进入休眠状态, 让你不得不重启评估板, 相当扰人, 学习期间, 评估板一直插在 USB 口上, 供电无忧, 无所谓节电模式. 进行 Socket 通信的所有 AT 指令都可以在 AT 指令助手的指令集合面板中通过选择[BC26 Socket 命令] 项获取, 并查看手册.
命令介绍
我们知道, TCP 是面向连接的协议, TCP 发送信息必须确保对方能够收到, 即使对方无法收到信息本方也可以知晓. 而 UDP 是面向无连接的协议, 只管将信息发送给对方, 至于对方能否收到, 本方就不关心了. 接下来首先介绍本文在使用 Socket 通信时所使用到的命令.
AT+QSOC=<domain>,<type>,<protocol>
创建一个 TCP 或 UDP Socket.
- <domain>
- : 表示使用的是 IPv4 还是 IPv6, 其中 1 表示 IPv4.
- <type>
- : 表示协议类型, 其中 1 表示 TCP;2 表示 UDP.
- <protocol>
- : 表示协议类型, 其中 1 表示 IP;2 表示 ICMP.
例如: AT+QSOC=1,1,1 表示创建一个使用 IPv4 的, 使用 TCP/IP 的 Socket.
AT+QSOCON=<socket_id>,<remote_port>,<remote_address>
使用 Socket 进行远程连接.
- <socket_id>
- :BC26 一共支持同时使用 5 个 Socket 进行通信, 编辑为 0~4, 此参数指定其中一个 Socket.
- <remote_port>
- : 通信端口, 0~65535.
- <remote_address>
- : 远端 IP 地址.
例如: AT+QSOCON=0,5000,"193.112.19.116" 表示将 0 号 Socket 向地址为 193.112.19.116 的远端服务器的 5000 端口发起连接.
AT+QSOSEND=<socket_id>,<data_len>,<data>
向远端发送数据.
- <socket_id>
- :Socket 的编号, 范围 0~4.
- <data_len>
- : 数据的长度, 以字节为单位. 对于 ASCII 码来说, 一个字符的长度为 1.
- <data>
- : 发送的数据, 使用 16 进制数字表示. 记住, 无论你发送的是整数, 浮点数, 字符串, 还是其他的数据类型, 这里统统要以字节的 16
- 进行数字表示. 假设你要发送一个字符 H, 查 ASCII 表 http://www.iotxfd.cn/demo/ascii.html ,
- 找到 H 的编码为 0x48, 则发送 48 即可. 如果要发送的是 Hello, 则发送内容为: 48656C6C6F.
例如: AT+QSOSEND=0,5,48656C6C6F 表示让 0 号 Socket 发送 5 个字节长度的数据[0x48,0x65,0x6C,0x6C,0x6F].
+QSONMI=<socket_id>,<data_len>
此为非请求结果码(URC), 即服务器端主动发送过来的数据, 而非客户端请求的数据. 表示收到服务器端发来的数据.
- <socket_id>
- : 表示是第几号 Socket 收到的数据.
- <data_len>
- : 表示收到的数据的长度.
例如:+QSONMI=0,5 表示 0 号 Socket 收到远端发送过来的 5 个字节的数据.
+QSORF=<socket_id>,<req_length>
从 Socket 接收数据. 此命令配合上一条命令使用.
- <socket_id>
- : 指示接收数据的 Socket 编号.
- <req_length>
- : 接收多长的数据.
例如: AT+QSORF=0,5 表示从 0 号 Socket 的接收数据缓冲中读取 5 个字节的数据.
AT+QSODIS=<socket_id>
断开 Socket 连接.
- <socket_id>
- 指示要断开的 Socket 的编号. AT+QSOCL=
- <socket_id>
关闭 Socket.
<socket_id > 指示要断开的 Socket 的编号.
NB-IOT 的使用场景, 必定是每日少量数据的传送, 所以 Socket 打开后, 传完数据就应当立即断开连接并关闭.
使用 TCP 协议进行通信
本节演示如何使用 AT 指令跟远程服务器进行 TCP 通信, 条件是必须要有一台具有公网 IP 的服务器, 没有的话, 无法进行实验, 不过问题也不大. 后面主要还是使用更高层级的协议跟电信, 华为, 阿里的专用物联网云通信的.
服务器端
服务器端, 具体开发环境的搭建请参考上一篇文章 http://article.iotxfd.cn/WSN/Remote-Development .
新建一个 TCPSocket 文件夹, 进入后, 使用命令 dotnet new console 创建一个新控制台项目, 代码如下:
- using System;
- using System.NET;
- using System.NET.Sockets;
- using System.Threading.Tasks;
- using System.Text;
- namespace TCPSocket
- {
- class Program
- {
- static void Main(string[] args)
- { // 设置服务器 IP, 如果是腾讯云, 必须使用内网地址, 而不是公网 IP.
- IPAddress ip = IPAddress.Parse("172.16.0.11");
- IPEndPoint point = new IPEndPoint(ip, 5000); // 端口指定为 5000
- Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
- try
- {
- s.Bind(point);
- s.Listen(5);
- Console.WriteLine("服务器开始侦听...");
- Socket subSocket = s.Accept(); // 等待新连接, 本程序仅能接受一个客户端的连接
- Console.WriteLine("获取一个来自 {0} 的连接", subSocket.RemoteEndPoint.ToString());
- // 创建线程接收客户端的消息
- Task.Factory.StartNew(() => ReceiveMessage(subSocket), TaskCreationOptions.LongRunning);
- // 发送消息
- while (true)
- {
- string sendStr = Console.ReadLine();
- if(sendStr=="") return; // 如果在控制台不输入任何字符直接按回车, 则退出程序
- byte[] sendBuff = Encoding.ASCII.GetBytes(sendStr);
- subSocket.Send(sendBuff, sendBuff.Length, SocketFlags.None);
- }
- }
- catch (Exception e)
- {
- Console.WriteLine(e.Message);
- }
- finally
- {
- s.Close();
- }
- }
- // 监听客户端连接的线程方法
- static void ReceiveMessage(Socket subSocket)
- {
- byte[] buff = new byte[1024]; // 创建一个接收缓冲区
- try
- {
- while (true)
- {
- int count = subSocket.Receive(buff, buff.Length, SocketFlags.None);
- // 下面这个判断是非常必要的, 否则有可能导致不停地接收到长度为 0 的数据, 导致 CPU 占用率 100%
- if (count == 0)
- {
- subSocket.Close();
- return;
- }
- // 将接收到的数据转化为 ASCII 字符
- string recvStr = Encoding.ASCII.GetString(buff, 0, count);
- Console.WriteLine($"接收到数据:{recvStr}");
- }
- }
- catch (Exception e)
- {
- Console.WriteLine(e.Message);
- }
- finally
- {
- subSocket.Close();// 客户端关闭时会引发异常, 此时关闭此连接
- Console.WriteLine("客户端已退出连接.");
- }
- }
- }
- }
本程序仅用于测试 AT 指令, 所以写得比较简单, 实现如下功能:
仅可以接收一个连接, 如果需要再次接收, 请重新运行程序. 当程序接收到一个新的连接时, 会打印客户端 IP 地址.
当收到消息时, 会将消息转化为 ASCII 码字符串打印.
在控制台输入字符按回车, 可将字符串转化为字节数组发给客户端, 注意, 与客户端连接后方可实现此功能.
不输入任何内容按回车即可退出程序.
运行程序
服务器端使用 dotnet run 运行程序启动服务, 显示 "服务器开始侦听...".
打开 AT 指令助手, 载入[TCP Socket] 脚本.
发送指令: AT+QSOC=1,1,1, 创建 Socket.
发送指令:
AT+QSOCON=0,5000,"193.112.19.116"
, 连接服务器, 注意端口和 IP 地址请自行更改.
发送指令:
AT+QSOSEND=0,5,48656C6C6F
, 发送数据 "Hello", 观察服务器是否收到.
发送指令:
AT+QSOSEND=0,10,54435020536F636B6574
, 发送数据 "TCP Socket", 观察服务器是否收到.
服务器端发送数据 abc.
客户端收到 + QSONMI=0,3.
发送指令: AT+QSORF=0,3 接收缓冲区数据.
服务器端发送数据 good-bye.
客户端收到 + QSONMI=0,8.
发送指令: AT+QSORF=0,8 接收缓冲区数据.
发送指令: AT+QSODIS=0 断开连接.
发送指令: AT+QSOCL=0 关闭 Socket.
运行效果如下图所示.
使用 UDP 协议进行通信
UDP 协议具有资源消耗小, 处理速度快的优点, 但它是不靠的. 接下来演示使用 UDP 协议进行通信, 使用 UDP 和使用 TCP 的思维方式是不一样的. UDP 没有连接, 也就没有所谓的断开连接, 但有意思的是, 使用 AT 指令发送 UDP 信息时, 依然和 TCP 一样, 需要进行连接和断开连接操作(在 C# 中写 UDP 程序是没有这些的). 你不能说建立一个连接后, 在这个连接的基础上你来我往. UDP 的一个 Socket 只会侦听某一端口的所有信息, 而这个信息可能是不同客户端发送的, 所以, 每次接收信息都要创建一个新的 IPEndPoint. 所以这次我把程序改为将接收到的信息原样发回.
服务器端
新建一个 UDPSocket 文件夹, 进入后, 使用命令 dotnet new console 创建一个新控制台项目, 代码如下:
using System; using System.NET; using System.NET.Sockets; using System.Threading.Tasks; using System.Text; namespace TCPSocket { class Program { static void Main(string[] args) { // 设置服务器 IP, 如果是腾讯云, 必须使用内网地址, 而不是公网 IP. IPAddress ip = IPAddress.Parse("172.16.0.11"); IPEndPoint point = new IPEndPoint(ip, 5000); // 端口指定为 5000 Socket udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); udpSocket.Bind(point); Console.WriteLine("服务器开始侦听..."); // 创建线程接收客户端的消息 Task.Factory.StartNew(() => ReceiveMessage(udpSocket), TaskCreationOptions.LongRunning); Console.ReadLine(); // 按回车直接退出程序 } // 监听客户端连接的线程方法 static void ReceiveMessage(Socket udpSocket) { byte[] buff = new byte[1024]; // 创建一个接收缓冲区 try { while (true) { EndPoint remote = new IPEndPoint(IPAddress.Any, 0); int count = udpSocket.ReceiveFrom(buff, ref remote); // 将接收到的数据转化为 ASCII 字符 string recvStr = Encoding.ASCII.GetString(buff, 0, count); Console.WriteLine($"接收到来自 {remote.ToString()} 数据:{recvStr}"); udpSocket.SendTo(buff, 0, count, 0, remote); } } catch (Exception e) { Console.WriteLine(e.Message); } finally { udpSocket.Close(); } } } }
本程序实现如下功能:
由于没有所谓的连接, 可以一直的接收 UDP 信息, 也就是说, 客户端可以多次创建 Socket 向服务器发信息, 而服务器不需要重启程序便可接收所有 Socket 的信息.
当收到消息时, 会将消息转化为 ASCII 码字符串打印.
服务器在收到信息后, 原样返回给客户端.
按回车即可退出程序.
程序运行效果如下图所示, 由于和上一个程序类似, 我不再详细讲解.
感想
做完两个程序后, 还是有一些感想的, 我买了两块板, 有一块信号不太好, 导致调试程序的过程异常痛苦, 使用另一块板之后才能确定是信号而不是程序的问题, 将来万物互联, NB-IOT 的连接设备数量会非常巨大, 在这种情况下, 使用 TCP 协议或许并不是最优选择, 毕竟 TCP 光建立一个连接就要费不少周折, 而且保持连接还会耗费服务器资源和带宽, NB-IOT 的带宽并不优裕. UDP 是更好的选择, 来信息直接处理, 无需耗费资源保持连接, 不占用带宽, 可靠性问题可以通过应用层的控制来满足. 所以我们也看到, BC26 上的大多数协议也是基于 UDP 进行开发的. 这些协议我在后面会一一讲解.
来源: http://www.bubuko.com/infodetail-3357062.html