BC26 还有一组专用于 TCP 通讯的 AT 指令:《BC26_TCP/IP_AT_Commands_Manual_V1.1》,以前已经有了 Socket 能够进行 TCP 通讯,如今又出一个 TCP/IP。或许就是 C# 中的 Socket 与 TCPClient 之间的关系吧,也有多是早期出了一个简单版本的可用于 TCP 编程的 Socket,以后又出一个功能更为强大的 TCP/IP,而为了兼容老程序,保留了 Socket 而已。总之,前面弄过的东西须要再来一遍。固然 TCP/IP 指令同样能够用于 UDP 通讯,本文就再也不讲解 UDP 了。编程
Quectel BC26 模块嵌入了 TCP/IP 协议栈,它使得主机能够直接经过 AT 指令访问 Internet。这大大减小了对 PPP 和外部 TCP/IP 协议栈的依赖,从而将成本降到最低。c#
Quectel BC26 模块提供了如下 socket 服务:TCP 客户端和 UDP 客户端。缓存
BC26 模块支持如下两种类型的数据访问模式:服务器
当经过AT+QIOPEN
打开一个 socket 服务,可经过参数<access_mode>
来指定数据访问模式。在 socket 服务开始后,AT+QISWTMD
可用于改变数据访问模式。网络
在缓存访问模式中,数据可经过AT+QISEND/AT+QISENDEX
指令发送。当接收到数据时,模块将缓存数据并报告一个 URC,格式为:+QIURC:“recv”,<connectID>[,<current_recv_length>]
。主机可以使用AT+QIRD
读取数据。并发
注意:在缓存访问模式中,若是缓存不为空,模块将不会报告新的 URC,直到全部收到的数据被
AT+QIRD
从缓存中读取。socket
AT+QISEND/AT+QISENDEX
指令发送。接收到的数据将直接经过如下 URC 输出:+QIURC: “recv”,<connectID>,<current_recv_length><CR><LF><data>
首先介绍本文所使用到的命令。工具
AT+QIOPEN=<contextID>,<connectID>,<service_type>,<IP_address>,<remote_port><local_port>,<access_mode>
打开一个 Socket 服务。编码
<contextID>
:上下文 ID,范围 1-3,用来干啥的我也不懂,通常状况下设为 1 就好了。<connectID>
:Socket 服务编号,其实就是以前讲过的,BC26 最多支持 5 个 Socket,编号 1-4。<service_type>
:协议类型,"TCP"或"UDP"。<IP_address>
:远程服务器十进制点分隔 IP 地址。<remote_port>
:远程服务器端口。<local_port>
:能够指定本地通讯端口,通常设为 0,表示让程序自动分配。<access_mode>
:Socket 服务数据访问模式,0 为缓存访问模式;1 为直接推送模式。AT+QISTATE=<query_type>,<connectID>
检查 Socket 服务的链接状态。spa
<query_type>
:指是经过<contextID>
(0)仍是经过<connectID>
(1)来查询链接状态。通常状况下都是用 1,即<connectID>
进行查询。<connectID>
:选择 5 个 socket 中的一个查询,范围 0-4。AT+QISEND=<connectID>,<send_length>,<data>
向服务器发送数据。
<send_length>
:发送数据的长度,以字节为单位<data>
:发送的数据AT+QISEND=<connectID>
向服务器发送变长数据。发送此命令后,服务器会响应一个>
,此时输入要发送的数据,并按快捷键【Ctrl + Z】便可发送给服务器。
AT+QISENDEX=<connectID>,<send_length>,<hex_string>
十六进制字符串格式发送数据,如AT+QISENDEX=0,5,3031323334
,是向 0 号 Socket 发送长度为 5 的字符串“01234”。
AT+QIRD=<connectID>,<read_length>
从接收缓存中读取数据。
<read_length>
:接收的长度,最大值为 512 字节,通常设置为 512 更方便,它会自动按缓存中的数据长度接收。AT+QICFG="showlength"[,<show_length_mode>]
设置在收到服务器信息时,显示的 URC 中是否包含数据长度信息。
<show_length_mode>
:设为 0 表示不显示,设为 1 表示显示。AT+QICFG="viewmode"[,<view_mode>]
设置在读取接收缓存中的数据时的显示格式。
<view_mode>
:
AT+QICLOSE=<connectID>
关闭链接。
AT+QPING=<contextID>,<host>
Ping 一个远程服务器。
<host>
:远程主机域名或 IP 地址AT+QNTP=<contextID>,<server>
从远程服务器同步时间。
<server>
:远程时间服务器域名或 IP 地址。本文使用的例子较多,常常从新链接,不能再象上一个程序那样,每个链接就要重启一次程序。此次程序改成可接收多个链接。
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); s.Bind(point); //开一个单独的线程去侦听客户端链接 Task.Factory.StartNew(() => Listening(s), TaskCreationOptions.LongRunning); Console.ReadLine(); //按回车关闭程序 } //侦听线程方法 static void Listening(Socket s) { s.Listen(5); Console.WriteLine("服务器开始侦听..."); while (true) { Socket subSocket = s.Accept(); //等待新链接 Console.WriteLine("获取一个来自{0}的链接", subSocket.RemoteEndPoint.ToString()); //建立线程接收客户端的消息 Task.Factory.StartNew(() => ReceiveMessage(subSocket), TaskCreationOptions.LongRunning); } } //监听客户端链接的线程方法 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}"); //将消息原样返回 subSocket.Send(buff, count, SocketFlags.None); } } catch (Exception e) { Console.WriteLine(e.Message); } finally { subSocket.Close();//客户端关闭时会引起异常,此时关闭此链接 Console.WriteLine($"客户端{subSocket.RemoteEndPoint.ToString()}已退出链接。"); } } } }
启动程序后,能够最小化去放心作实验了,重连多少次都不须要再回来看一眼。
BC26 支持两种数据访问模式:缓存访问模式和直接推送模式。咱们首先介绍缓存访问模式的操做。
发送数据使用AT+QISEND
和AT+QISENDEX
两个命令。
//打开 socket 服务,并指定为缓存访问模式 >>>>>>>>>> AT+QIOPEN=1,0,"TCP","193.112.19.116",5000,0,0 OK +QIOPEN: 0,0 //URC:两个参数分别表示 socket 编号和错误码。 //查询网络状态 >>>>>>>>>> AT+QISTATE=1,0 //倒数第三个参数 2 表示已经链接网络 +QISTATE: 0,"TCP","193.112.19.116",5000,0,2,1,0 OK //发送长度为10个字节的字符串“1234567890” >>>>>>>>>> AT+QISEND=0,10,1234567890 OK SEND OK +QIURC: "recv",0 //URC:表示 0 号 socket 接收到数据 //发送长度为5个字节的十六进制格式的字符串“01234” >>>>>>>>>> AT+QISENDEX=0,5,3031323334 OK SEND OK >>>>>>>>>> AT+QICLOSE=0 //关闭 socket 服务 OK CLOSE OK
此例使用两种方法向服务器发送数据,第一次是直接发送字符串,服务器返回数据,并报告 URC。第二次发送的是编码形式的数据,服务器返回数据但没有报告 URC,由于第一次接收的内容未接收,接收缓存未清空。
发送变长数据需使用AT+QISEND=0
命令,此时服务响应一个>
,表示等待用户输入,用户在输入数据后,在结尾添加 0x1A 便可向服务器发送无需标明长度的数据。弄懂这个命令费了一些周折。由于文档写的是输入命令后,按下【Ctrl + Z】键,上帝啊!我想不出哪一个串口工具可使用【Ctrl + Z】来发送命令啊!全部编辑框里的【Ctrl + Z】都是用来 Undo 的。后来发现只要在数据的结尾加上 0x1A 发送便可,0x1A 即表明【Ctrl + Z】键。毫不能结尾加上回车(0x0D,0x0A),必须是以 0x1A 结束。
本身写工具的好处就在于自由,想加啥都行,立即加上此功能,版本改成 1.01。以下图左边【发送区设置】区域内添加了一个“自动添加【Ctrl+Z】”项,选择此项后,再点绿色按钮发送数据,就会自动添加 0x1A 并发送。
按上图所示设置接收区和发送区选项,打开 TCP Client 脚本,全部命令须要使用右边脚本面板中每条命令右边的三角按钮发送。仅在AT+QISEND=0
命令以后的输入发送数据时使用发送区进行发送。在选择“自动添加【Ctrl+Z】”项时,因为没法在命令后面添加回车,发送区不能发送命令。如下是完整命令脚本
>>>>>>>>>> AT+QIOPEN=1,0,"TCP","193.112.19.116",5000,0,0 OK +QIOPEN: 0,0 >>>>>>>>>> AT+QISEND=0 > >>>>>>>>>> www.iotxfd.cn //注意,这里的结尾是 0x1A OK SEND OK +QIURC: "recv",0 >>>>>>>>>> AT+QICLOSE=0 OK CLOSE OK
在使用这种方式进行发送时,还能够指定基最大发送长度,如AT+QISEND=0,10
,表示只发送 10 个字节。下例演示了这种状况:
>>>>>>>>>> AT+QIOPEN=1,0,"TCP","193.112.19.116",5000,0,0 OK +QIOPEN: 0,0 >>>>>>>>>> AT+QISEND=0,10 > >>>>>>>>>> www.iotxfd.cn //注意,这里的结尾是 0x1A www.iotxfd //多出的字符串被截断 OK SEND OK +QIURC: "recv",0 >>>>>>>>>> AT+QICLOSE=0 OK CLOSE OK
能够看到,因为指定了最大长度,多出来的字符串未被发送。
先来一个最简单的接收示例:
>>>>>>>>>> AT+QIOPEN=1,0,"TCP","193.112.19.116",5000,0,0 //建立 Socket 服务 OK +QIOPEN: 0,0 //链接成功,使用的是 0 号 socket >>>>>>>>>> AT+QISEND=0,10,1234567890 //发送字符串“01234567890” OK SEND OK +QIURC: "recv",0 //URC:0 号 socket 收到信息 >>>>>>>>>> AT+QIRD=0,512 //接收 0 号 socket 的接收缓冲区,长度 512 +QIRD: 10 //收到 10 个字节 1234567890 //数据为:1234567890 OK >>>>>>>>>> AT+QIRD=0,512 //再次接收 +QIRD: 0 //指示接收缓冲已空 OK >>>>>>>>>> AT+QICLOSE=0 //关闭 socket 服务 OK CLOSE OK
这种接收方式应当是最经常使用的,每次按最大接收数 512 进行接收,最终只按实际数据长度进行接收,使用起来很是方便。你也能够指定接收的长度,如:
>>>>>>>>>> AT+QIOPEN=1,0,"TCP","193.112.19.116",5000,0,0 OK +QIOPEN: 0,0 >>>>>>>>>> AT+QISEND=0,10,1234567890 OK SEND OK +QIURC: "recv",0 //URC:0 号 socket 收到信息 >>>>>>>>>> AT+QIRD=0,6 //指定接收长度为 6 个字节 +QIRD: 6 //接收了 6 个字节 123456 OK >>>>>>>>>> AT+QIRD=0,6 //再次接收 +QIRD: 4 //接收了剩余的 4 个字节 7890 OK >>>>>>>>>> AT+QICLOSE=0 OK CLOSE OK
可使用AT+QICFG="showlength",1
指令更改收到信息 URC 的显示方式,让其指示收到了多少个字节。过程以下图所示:
可使用AT+QICFG="viewmode",1
更改接收信息的显示方式:
>>>>>>>>>> AT+QICFG="viewmode",0 //将接收信息显示方式改成 0 OK >>>>>>>>>> AT+QIOPEN=1,0,"TCP","193.112.19.116",5000,0,0 OK +QIOPEN: 0,0 >>>>>>>>>> AT+QISEND=0,10,1234567890 OK SEND OK +QIURC: "recv",0,10 >>>>>>>>>> AT+QIRD=0,4 //先接收 4 个字节 +QIRD: 4,6 //接收 4 个字节,剩余 6 个字节 1234 //换行显示 OK >>>>>>>>>> AT+QICFG="viewmode",1 //将接收信息显示方式改成 1 OK >>>>>>>>>> AT+QIRD=0,4 //再次接收 4 个字节 +QIRD: 4,2,5678 //接收 4 个字节,剩余 2 个字节,数据直接在逗号后面显示 OK >>>>>>>>>> AT+QIRD=0,4 //再次接收 4 个字节 +QIRD: 2,0,90 //只收到了 2 个字节 OK >>>>>>>>>> AT+QIRD=0,4 +QIRD: 0 OK >>>>>>>>>> AT+QICLOSE=0 OK CLOSE OK
从上例可观察到,在显示接收数据时,viewmode=0,会换行显示数据。viewmode=1 则直接在逗号后面显示数据。
使用直接推送模式会在 URC 中直接显示接收到的数据,以下图所示:
很明显,若是收到的数据量较小,使用直接推送模式会方便不少。
我这里有两块开发板,一块直接没法 Ping,另外一块能够 Ping,但速度很慢。
>>>>>>>>>> AT+QPING=1,193.112.19.116 OK +QPING: 569 +QPING: 0,"193.112.19.116",32,990,52 +QPING: 0,"193.112.19.116",32,2060,52 +QPING: 0,"193.112.19.116",32,1040,52 +QPING: 0,4,3,1,990,2060,1363 >>>>>>>>>> AT+QPING=1,"www.baidu.com" OK +QPING: 0,"39.156.66.18",32,1560,52 +QPING: 569 +QPING: 0,"39.156.66.18",32,560,52 +QPING: 569 +QPING: 0,4,2,2,560,1560,1060
上述代码中的 569 为错误码,表示超时。
时间同步也同样,一块开发板没法用,另外一块能够:
>>>>>>>>>> AT+QNTP=1,"ntp5.aliyun.com" OK +QNTP: 0,"20/01/01,12:50:38+32"
新年第一天,泡制完 2020 年的第一篇文章。这个系列得停一段时间,想着仍是得先把 RFID 写完了再回来继续。