概述
前言
在选购好NB开发板之后,我们就可以使用AT指令进行socket通信了,主要问题在于我们之前学习socket编程只需要一台电脑就可以进行学习,服务器和客户端都是在本机上运行,或者是两台处于同一局域网下的主机之间进行互相通信,而NB-IOT芯片信号是通过空中接口(Uu)发送到基站(eNB)信号塔(此段仅为个人理解),个人主机是在局域网环境下运行的,没有公网IP(可直接连接Internet),基站信号塔是找不到我们个人主机的,NB开发板就无法跟我们的主机进行通信,所以我们还需要准备一台具有公网IP的服务器,读者可以选择购买阿里或腾讯的服务器。
进行开发首先需要在个人主机上编写好程序,然后拷贝到咱们的服务器上面,这个过程可以参考我前面一篇文章 文章链接,配置好之后咱们就可以进行实验了
TCP/IP
BC26还专门出了一组TCP通信的AT指令手册:《Quectel_BC26&BC20_TCP(IP)_AT_V1.1_Preliminary》,之前就有TCP与UDP的socket通信的命令手册,这里我也不了解,可能是为了兼容,当然了TCP/IP也可以进行UDP通信,本章不讲解UDP。
命令介绍
TCP是一种面向连接、可靠的通信协议,TCP发送的信息对方必须接收到,如果出现差错或者丢失数据也可以进行重传,前提是对方必须知道,而UDP是一种面向无连接的协议,只管发送信息,至于对方是否接收到,就不用咱们关心了,接下来将介绍TCP/IP使用socket通信所用到的命令。
AT+QIOPEN=<contextID>,<connectID>,<service_type>,<IP_address>/<domain_name>,<remote_port><local_port>,<access_mode>,<<protocol_type>
打开一个Socket服务
<contextID>
: 整形,上下文ID,范围:1-3,目前仅支持设置为1<connectID>
:整形,Socket服务索引,范围:0-4<service_type>
:字符串类型,Socket协议类型,"TCP"或"UDP"<IP_address>
:字符串类型,远程服务器的IP地址domain_name>
:字符串类型,远程服务器的域名地址<remote_port>
:远程服务器的端口<local_port>
:指定本地端口号,一般为0,系统会自动分配本地端口号<access_mode>
:整形,Socket的数据访问模式,0为缓存访问模式 1为直接访问模式<protocol_type>
:整形,IP协议类型 0为IPv4 1为IPv6
AT+QICLOSE=<connectID>
关闭一个Socket服务
<connectID>
:整形,Socket服务索引,范围0-4
AT+QISTATE=<query_type>,<contextID>
查询Socket连接状态
<query_type>
:整形,表示查询类型,根据通过<contextID>
0 或<contextID>
1 来查询连接状态<contextID>
:整形,上下文ID,范围:1-3,目前仅支持设置为1
AT+QISEND=<connectID>,<send_length>,<data>
向服务器发送十六进制/文本字符串数据
<connectID>
:整形,Socket服务索引,范围:0-4<send_length>
:待发数据长度,单位:字节<data>
:待发送数据
AT+QISEND=<connectID>
发送不定长数据
- 向服务器发送数据,待响应“>” 后,输入要发送的数据,按"Ctrl + z"发送数据,按“Esc”取消发送
AT+QISEND=<connectID>,<send_length>
发送定长数据
- 向服务器发送数据,待响应“>” 后,输入长度等于
<send_length>
的待发数据
AT+QISENDEX=<connectID>,<send_length>,<hex_string>
发送十六进制字符串数据
<connectID>
:整形,Socket服务索引,范围:0-4<send_length>
:待发数据长度,最大字节512<hex_string>
:待发送的十六进制字符串数据
AT+QIRD=<connectID>,<read_length>
将接收到的数据缓存并读取
<connectID>
:整形,Socket服务索引,范围:0-4<read_length>
:读取最大长度数据,最大值为512字节
其余命令暂时用不上,这里就不在一一列举,TCP/IP命令手册获取 命令手册连接 提取码:eu6h
数据访问模式
BC26模块支持两种数据访问模式
- 缓存访问模式
- 直接访问模式
当通过 AT+QIOPEN
打开一个 Socket 服务时,可以通过参数<access_mode>
来指定数据访问模式。
当 Socket 服务成功打开,可以通过 AT+QISWTMD
改变数据访问模式。
1:在缓存访问模式下,可通过命令 AT+QISEND/AT+QISENDEX
发送数据。当接收到数据时,模块会
缓存所接收的数据,并有 URC 上报,格式为:+QIURC: "recv",<connectID>[,<current_recv_length>]
,模块
可以通过命令 AT+QIRD
来读取缓存数据
2:直接访问模式下,可通过命令 AT+QISEND/AT+QISENDEX
发送数据。模块新接收的数据会通过
上报 URC 格式为:+QIURC: "recv",<connectID>, <currect_recv_length><CR><LF><data>
直接输出
服务器/客户端程序
程序跟之前无变化,可以接收多个连接并回射数据
服务端
#include <stdio.h> //printf
#include <sys/socket.h> //socket
#include <netinet/in.h> //sockaddr_in
#include <arpa/inet.h> //htons inet_addr
#include <unistd.h> //close
#include <stdlib.h> //exit
#include <string.h>
#include <sys/wait.h> //wait waitpid
#include <signal.h> //signal
#define MAXSIZE 255
void error_hanglde(char *message);
void process_hanglde(int sig);
int main(int argc,const char *argv[])
{
int sockfd,cli_sock;
ssize_t n;
socklen_t clilen;
char message[MAXSIZE];
pid_t pid;
struct sockaddr_in ser_addr,cli_addr;
struct sigaction act;
if(argc != 3)
{
fprintf(stderr,"Usage %s ip potrn",argv[0]);
}
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
error_hanglde("sockfd() error!");
bzero(&ser_addr,sizeof(ser_addr));
ser_addr.sin_family = AF_INET;
ser_addr.sin_addr.s_addr = inet_addr(argv[1]); //设置IP地址,如果是阿里云,要设置成内网IP地址,而不是公网IP地址
ser_addr.sin_port = htons(atoi(argv[2])); //端口号
if(bind(sockfd,(struct sockaddr *)&ser_addr,sizeof(ser_addr)) == -1)
error_hanglde("bind() error!");
if(listen(sockfd,10) == -1)
error_hanglde("listen() error!");
act.sa_handler = process_hanglde;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGCHLD,&act,0);
//进行通信
for(;;)
{
clilen = sizeof(cli_addr);
cli_sock = accept(sockfd,(struct sockaddr *)&cli_addr,&clilen);
if(cli_sock == -1)
continue;
else
printf("[%s - %d] Connect Client:%dn",inet_ntoa(cli_addr.sin_addr),ntohs(cli_addr.sin_port),cli_sock);
//创建子进程
pid = fork();
if(pid == -1)
{
close(cli_sock);
continue;
}
if(pid == 0) //子进程执行区域
{
close(sockfd); //关闭监听套接字
while((n = read(cli_sock,message,MAXSIZE)) != 0) //数据收发
{
write(cli_sock,message,n);
message[n] = 0;
}
printf("[%s - %d] client disconnte:%dn",inet_ntoa(cli_addr.sin_addr),ntohs(cli_addr.sin_port),cli_sock);
close(cli_sock);
return 0;
}
}
close(sockfd);
close(cli_sock);
return 0;
}
void process_hanglde(int sig)
{
pid_t pid;
int status;
pid = waitpid(-1,&status,WNOHANG);
if(WIFEXITED(status))
{
printf("process recv id = %dn",pid);
}
}
void error_hanglde(char *message)
{
fputs(message,stderr);
fputs("n",stderr);
exit(0);
}
客户端
#include <stdio.h> //printf
#include <sys/socket.h> //socket
#include <netinet/in.h> //sockaddr_in
#include <arpa/inet.h> //htons inet_addr
#include <unistd.h> //close
#include <stdlib.h> //exit
#include <string.h>
#define MAXSIZE 255
void error_hag(char *message);
int main(int argc,const char *argv[])
{
int sockfd;
ssize_t n;
char message[MAXSIZE],ReadLine[MAXSIZE];
if(argc != 3)
{
fprintf(stderr,"Usage %s ip potrn",argv[0]);
}
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
error_hag("sockfd() error");
struct sockaddr_in ser_addr;
bzero(&ser_addr,sizeof(ser_addr));
ser_addr.sin_family = AF_INET;
ser_addr.sin_addr.s_addr = inet_addr(argv[1]); //设置为公网IP地址
ser_addr.sin_port = htons(atoi(argv[2])); //对应服务器端口号
if(connect(sockfd,(struct sockaddr *)&ser_addr,sizeof(ser_addr)) == -1)
error_hag("connect() error");
for(;;)
{
fputs("Input info(Q ~ Quit):",stdout);
fgets(message,MAXSIZE,stdin);
if(strcmp(message,"Qn") == 0 || strcmp(message,"Quitn") == 0)
break;
write(sockfd,message,strlen(message));
n = read(sockfd,ReadLine,MAXSIZE);
ReadLine[n] = 0;
fputs(ReadLine,stdout);
}
close(sockfd);
}
void error_hag(char *message)
{
fputs(message,stderr);
fputs("n",stderr);
exit(0);
}
启动程序后就可以去做实验了,这里我用的是我们老师自己做的工具:AT指令助手,个人用起来还是蛮舒服的,功能也很强大,很方便,AT助手工具获取链接 提取码:k05h
缓存访问模式
咱们开始先介绍BC26数据类型模式:缓存访问模式
接下来发送数据,用AT+QISEND
和AT+QISENDEX
这两个命令
发送定长数据
//打开一个 Socket 服务
>>>>>>>>>> AT+QIOPEN=1,0,"TCP","120.79.83.71",9999,0,0
AT+QIOPEN=1,0,"TCP","120.79.83.71",9999,0,0
OK
+QIOPEN: 0,0
//发送长度为11字节的文本字符串
>>>>>>>>>> AT+QISEND=0,11,Hello World
AT+QISEND=0,11,Hello World
OK
SEND OK
+QIURC: "recv",0 //上报URC,0表示接收到数据并缓存
//发送长度为5字节的十六进制字符串 “01234”
>>>>>>>>>> AT+QISENDEX=0,5,3031323334
AT+QISENDEX=0,5,3031323334
OK
SEND OK
//关闭 Socket 连接
>>>>>>>>>> AT+QICLOSE=0
AT+QICLOSE=0
OK
CLOSE OK
第一次发送的是字符串数据,服务器返回数据并上报URC,第二次发送的是进制形式,服务器接收到数据但没有上传到URC,原因是第一次接收的缓存数据未清空。
发送不定长数据
发送不定长数据需要用到AT+QISEND=0
,此时服务器会响应一个“ > ”,等待用户输入数据。
>>>>>>>>>> AT+QIOPEN=1,0,"TCP","120.79.83.71",9999,0,0
AT+QIOPEN=1,0,"TCP","120.79.83.71",9999,0,0
OK
+QIOPEN: 0,0
//发送不定长数据
>>>>>>>>>> AT+QISEND=0
AT+QISEND=0
//待发数据
>
>>>>>>>>>> xrl.yyds
OK
SEND OK
+QIURC: "recv",0 // 0 上报URC 接收到数据
//关闭 Scoket 连接
>>>>>>>>>> AT+QICLOSE=0
AT+QICLOSE=0
OK
CLOSE OK
数据的接收
数据的接收需要使用AT+QIRD
这个命令
//打开一个 Socket 服务
>>>>>>>>>> AT+QIOPEN=1,0,"TCP","120.79.83.71",9999,0,0
AT+QIOPEN=1,0,"TCP","120.79.83.71",9999,0,0
OK
+QIOPEN: 0,0 //返回0,0表示无误 连接成功
//发送长度为11字节的字符串
>>>>>>>>>> AT+QISEND=0,11,Hello World
AT+QISEND=0,11,Hello World
OK
SEND OK
+QIURC: "recv",0 // 0 上报URC,接收到数据
//读取接收到的数据,最大长度为512字节,防止过长溢出
>>>>>>>>>> AT+QIRD=0,512
AT+QIRD=0,512
+QIRD: 11 //收到长度为11字节
Hello World //收到长度为11字节的字符串
OK
//再次接收
>>>>>>>>>> AT+QIRD=0,512
AT+QIRD=0,512
+QIRD: 0 // 0 表示接收区为空
OK
//关闭 Scoket 连接
>>>>>>>>>> AT+QICLOSE=0
AT+QICLOSE=0
OK
CLOSE OK
其次也可以指定接收的长度
//打开一个 Socket 服务
>>>>>>>>>> AT+QIOPEN=1,0,"TCP","120.79.83.71",9999,0,0
AT+QIOPEN=1,0,"TCP","120.79.83.71",9999,0,0
OK
+QIOPEN: 0,0 //返回0,0表示无误 连接成功
//发送长度为11字节的字符串
>>>>>>>>>> AT+QISEND=0,11,Hello World
AT+QISEND=0,11,Hello World
OK
SEND OK
+QIURC: "recv",0 // 0 上报URC,接收到数据
>>>>>>>>>> AT+QIRD=0,5
AT+QIRD=0,5 //指定接收数据的长度为5字节
+QIRD: 5 //收到长度为5字节数据
Hello
OK
//关闭 Scoket 连接
>>>>>>>>>> AT+QICLOSE=0
AT+QICLOSE=0
OK
CLOSE OK
直接访问模式
使用直接访问模式会在发送数据后上传给URC直接显示接收到的数据
可以看出,如果接收到的数据量较小的话,直接访问模式还是很方便的。
结尾
文章如有误望指正,也希望本文章能帮助读者,临近毕业,对口相关岗位还是很难找,不知道后面会不会坚持下来从事IT行业
最后
以上就是和谐小虾米为你收集整理的使用AT指令与BC26进行socket通信的全部内容,希望文章能够帮你解决使用AT指令与BC26进行socket通信所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复