本篇来使用WIFI进行TCP无线通信测试,测试OK3568-C开发板与ESP8266进行无线通信,另外,服务端还设计了多客户端的处理功能,除了与ESP8266进行通信外,还可以与其它的TCP客户端进行通信,比如Ubuntu虚拟机创建的客户端。
TCP是一个面向连接的传输层协议,在数据发送之前(即进程通信之前),必须先建立连接。通信完毕后,必须关闭连接。基于TCP传输协议的服务器与客户机间的通信工作流程如下图:
TCP通信的大致流程如下:
在TCP通信中,需要用到端口号,端口大致有两种意思:一是物理意义上的端口,比如ADSL Modem、集线器、交换机、路由器等用于连接其它网络设备的接口,如RJ-45端口、SC端口等。二是逻辑意义上的端口,一般指TCP/IP协议中的端口,端口范围从0~65535,比如浏览器网页服务(HTTP协议)的80端口,用于FTP服务的21端口等。端口号只有本地意义,即端口号是为了标识本地计算机的各个进程。
端口号分为两类,一类是由因特网指派名字和号码公司ICANN负责分配给一些常用的应用程序固定使用的”周知的端口“,其数值一般为0~1024,如:
在实际使用时,自己使用的端口号,不要与”周知的端口“相同。
epoll的全称为eventpoll,是linux内核实现IO多路复用的一个实现。epoll是select和poll的升级版,相较于这两个前辈,epoll改进了工作方式,使之更加高效。
下面来介绍如何使用epoll来实现多路复用功能。
int epoll_create(int size); //监听个数
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
第一个参数epfd是epoll_create()的返回值, 第二个参数op表示动作,用三个宏来表示:
第三个参数是需要监听的fd, 第四个参数是告诉内核需要监听什么事, struct epoll_event结构如下:
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
events可以是以下几个宏的集合:
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)
等待事件的产生,类似于select()调用。 参数events用来从内核得到事件的集合, maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size, 参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。 该函数返回需要处理的事件数目,如返回0表示已超时。
epoll相关的内部数据结构示意如下:
服务端程序运行在OK3568-C板子中,并具有epoll的多路处理功能,可以处理多个客户端的请求
void tcp_server_thread()
{
//创建服务器端套接字文件
int listenfd=socket(AF_INET, SOCK_STREAM, 0);
//初始化服务器端口地址
struct sockaddr_in tcpServerAddr;
bzero(&tcpServerAddr, sizeof(tcpServerAddr));
tcpServerAddr.sin_family=AF_INET;
tcpServerAddr.sin_addr.s_addr= htonl(INADDR_ANY);
tcpServerAddr.sin_port=htons(SERV_PORT);
//将套接字文件与服务器端口地址绑定
bind(listenfd, (struct sockaddr *)&tcpServerAddr, sizeof (tcpServerAddr)) ;
//监听,并设置最大连接数为20
listen(listenfd, 20);
printf("[%s] Accepting connections... \n", __func__);
//通过epoll来监控多个客户端的请求
int epollfd;
struct epoll_event events[EPOLLEVENTS];
int num;
char buf[MAXSIZE];
memset(buf,0,MAXSIZE);
epollfd = epoll_create(FDSIZE);
printf("[%s] create epollfd:%d\n", __func__, epollfd);
//添加监听描述符事件
epoll_set_fd_a_event(epollfd, EPOLL_CTL_ADD, listenfd, EPOLLIN);
while(1)
{
//获取已经准备好的描述符事件
printf("[%s] epollfd:%d epoll_wait...\n", __func__, epollfd);
num = epoll_wait(epollfd,events,EPOLLEVENTS,-1);
for (int i = 0;i < num;i++)
{
int fd = events[i].data.fd;
//listenfd说明有新的客户端请求连接
if ((fd == listenfd) &&(events[i].events & EPOLLIN))
{
//accept客户端的请求
struct sockaddr_in cliaddr;
socklen_t cliaddrlen = sizeof(cliaddr);
int clifd = accept(listenfd,(struct sockaddr*)&cliaddr,&cliaddrlen);
if (clifd == -1)
{
perror("accpet error:");
}
else
{
printf("[%s] accept a new client(fd:%d): %s:%d\n",
__func__, clifd, inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port);
//将客户端fd添加到epoll进行监听
epoll_set_fd_a_event(epollfd, EPOLL_CTL_ADD, clifd, EPOLLIN);
}
}
//收到已连接的客户端fd的消息
else if (events[i].events & EPOLLIN)
{
memset(buf,0,MAXSIZE);
//读取客户端的消息
int nread = read(fd,buf,MAXSIZE);
if (nread == -1)
{
perror("read error:");
close(fd);
epoll_set_fd_a_event(epollfd, EPOLL_CTL_DEL, fd, EPOLLIN);
}
else if (nread == 0)
{
printf("[%s] client(fd:%d) close.\n", __func__, fd);
close(fd);
epoll_set_fd_a_event(epollfd, EPOLL_CTL_DEL, fd, EPOLLIN);
}
else
{
//将客户端的消息打印处理, 并表明是哪里客户端fd发来的消息
printf("[%s] read message from fd:%d ---> %s\n", __func__, fd, buf);
}
}
}
}
close(epollfd);
}
客户端程序通过TCP与服务端进行相连,只要具有连网功能的硬件,理论上都可以作为客户端。这里测试ESP8266开发板和Ubuntu虚拟机作为两个客户端,测试与OK3568的通信功能
#include <ESP8266WiFi.h>
const char* ssid = "xxx"; //<----修改为自己的
const char* password = "xxx"; //<----修改为自己的
const uint16_t port = 6666;
const char *host = "192.168.5.182";
WiFiClient esp8266Client;
void setup()
{
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
delay(500);
Serial.print("连接到:");
Serial.println(ssid);
uint8_t i = 0;
while (WiFi.status() != WL_CONNECTED && i++ < 20)
{
delay(500);
}
if (i == 21)
{
Serial.print("没能连接到:");
Serial.println(ssid);
return ;
}
Serial.print("准备好了!使用的网络IP是: ");
Serial.println(WiFi.localIP());
Serial.print("连接到 ");
Serial.println(host);
if (!esp8266Client.connect(host, port))
{
Serial.println("TCP server端连接失败");
Serial.println("请等待5秒后重新连接...");
delay(5000);
return;
}
Serial.println("TCP server端连接成功");
}
void loop()
{
esp8266Client.print("This is ESP8266");
delay(2000);
}
void tcp_client_thread(std::string serverIP)
{
printf("[%s] in, prepare connect serverIP:%s\n", __func__, serverIP.c_str());
//创建客户端套接字文件
int tcpClientSocket= socket(AF_INET, SOCK_STREAM, 0);
printf("[%s] create tcpClientSocket:%d\n", __func__, tcpClientSocket);
//初始化服务器端口地址
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr)) ;
servaddr.sin_family= AF_INET;
inet_pton(AF_INET, serverIP.c_str(), &servaddr.sin_addr);
servaddr.sin_port= htons(SERV_PORT);
//请求连接
connect(tcpClientSocket, (struct sockaddr*)&servaddr, sizeof (servaddr));
printf("[%s] connect\n", __func__);
//要向服务器发送的信息
char buf [MAXLINE];
std::string msg = "this is ubuntu20";
while(1)
{
//发送数据
send(tcpClientSocket, msg.c_str(), msg.length(),0);
printf("[%s] send to server: %s\n", __func__, msg.c_str());
//接收服务器返回的数据
int n= recv(tcpClientSocket, buf, MAXLINE, MSG_DONTWAIT); //非阻塞读取
if(n>0)
{
printf("[%s] Response from server: %s\n", __func__, buf);
}
sleep(2);
}
//关闭连接
close(tcpClientSocket) ;
printf("[%s] end\n", __func__);
}
本篇的TCP服务端程序还没有用到Qt功能,仅用了C++的相关代码,因此只需要使用g++编译器编译即可,编译前先临时设置一下环境变量:
export PATH=/home/xxpcb/myTest/OK3568/gcc_aarch64/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin:$PATH
然后就可以使用对应的g++编译器进行编译了,最后使用file指令确认下文件的类型:
ESP8266程序使用的是Arduino IDE进行编程,Arduino IDE还自带了串口监视器,通过串口监视器可以查看程序的运行结果:
本篇测试OK3568-C开发板与ESP8266进行通信,通过WIFI实现TCP无线连接。OK3568-C上运行的TCP服务端还设计了多客户端的处理功能,在与ESP8266进行通信的同时,还可以与Ubuntu虚拟机创建的客户端进行通信。
更多回帖