本案例程序将演示怎么在拓维Niobe WiFi IoT Core开发板上编写一个创建tcp服务器的业务程序,实现开发板联网与tcp客户端数据通信。
传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793 定义。
TCP旨在适应支持多网络应用的分层协议层次结构。 连接到不同但互连的计算机通信网络的主计算机中的成对进程之间依靠TCP提供可靠的通信服务。TCP假设它可以从较低级别的协议获得简单的,可能不可靠的数据报服务。 原则上,TCP应该能够在从硬线连接到分组交换或电路交换网络的各种通信系统之上操作。
1、创建一个socket,用函数socket(); 2、设置socket属性,用函数setsockopt(); * 可选 3、绑定IP地址、端口等信息到socket上,用函数bind(); 4、开启监听,用函数listen(); 5、接收客户端上来的连接,用函数accept(); 6、收发数据,用函数send()和recv(),或者read()和write(); 7、关闭网络连接; 8、关闭监听
(1)基于流的方式; (2)面向连接; (3)可靠通信方式; (4)在网络状况不佳的时候尽量降低系统由于重传带来的带宽开销; (5)通信连接维护是面向通信的两个端点的,而不考虑中间网段和节点
struct sockaddr_in {
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
uint8_t sin_zero[8];
};
描述:
地址和端口信息
参数:
名字 | 描述 |
---|---|
sin_family | 指代协议族,在socket编程中只能是AF_INET |
sin_port | 存储端口号(使用网络字节顺序) |
sin_addr | 存储IP地址,使用in_addr这个数据结构 |
sin_zero | 为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节 |
示例代码如下:
//服务端地址信息
struct sockaddr_in server_sock;
bzero(&server_sock, sizeof(server_sock));
server_sock.sin_family = AF_INET;
//server_sock.sin_addr.s_addr = htonl(INADDR_ANY);
inet_pton(AF_INET, "192.168.1.121", &server_sock.sin_addr);
server_sock.sin_port = htons(_PROT_);
typedef struct {
unsigned long fds_bits[FD_SETSIZE / 8 / sizeof(long)];
} fd_set;
描述:
文件描述符集合
操作函数:
名字 | 描述 |
---|---|
void FD_CLR(int fd, fd_set *set) | 清除某一个被监视的文件描述符 |
int FD_ISSET(int fd, fd_set *set) | 测试一个文件描述符是否是集合中的一员 |
void FD_SET(int fd, fd_set *set) | 添加一个文件描述符,将set中的某一位设置成1 |
void FD_ZERO(fd_set *set) | 清空集合中的文件描述符,将每一位都设置为0 |
示例代码如下:
fd_set fds;
FD_ZERO(&fds);
FD_SET(sock_fd,&fds);//将sock_fd添加至fds
if(FD_ISSET(sock_fd,&fds))//判断sock_fd是否在fds中
;//
int WifiConnect(const char *ssid, const char *psk)
描述: 初始化网络,并连接指定wifi
参数:
名字 | 描述 |
---|---|
ssid | 指定wifi的账户名 |
psk | 指定wifi的密码 |
int socket(int domain, int type, int protocol)
描述:
创建套接字
参数:
名字 | 描述 |
---|---|
domain | 地址族,也就是 IP 地址类型,常用的有 AF_INET 和 AF_INET6 |
type | 数据传输方式/套接字类型,常用的有 SOCK_STREAM(流格式套接字/面向连接的套接字) 和 SOCK_DGRAM(数据报套接字/无连接的套接字)和SOCK_RAW(原始套接字) |
protocol | 传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议 |
int bind(int fd, const struct sockaddr *addr, socklen_t len)
描述:
把用于通信的地址和端口绑定到 socket 上
参数:
名字 | 描述 |
---|---|
fd | 需要绑定的socket |
addr | 存放服务端用于通信的地址和端口 |
len | addr 结构体的大小 |
int listen(int fd, int backlog)
描述:
将套接字置于侦听传入连接的状态
参数:
名字 | 描述 |
---|---|
fd | 服务端的socket,也就是socket函数创建的 |
backlog | 定义“fd”的挂起连接队列可能增长到的最大长度 |
int accept(int socket, struct sockaddr *address, socklen_t *address_len)
描述:
接收客户端上来的连接
参数:
名字 | 描述 |
---|---|
socket | 服务端的套接字 |
address | 存放服务端用于通信的地址和端口 |
address_len | 指向address结构体大小的指针 |
ssize_t recv(int fd, void *buf, size_t len, int flags)
描述:
接收远端主机经指定的socket 传来的数据
参数:
名字 | 描述 |
---|---|
fd | 服务端的套接字 |
buf | 指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据 |
len | buf参数指向的缓冲区的长度(以字节为单位) |
flags | 指定消息接收的类型。此参数的值通过逻辑或零或多个以下值形成:MSG_PEEK查看传入消息。数据被视为未读数据,下一个recv()或类似函数仍将返回此数据。 |
ssize_t send(int fd, const void *buf, size_t len, int flags)
描述:
发送数据
参数:
名字 | 描述 |
---|---|
fd | 客户端的套接字 |
buf | 存放应用程序要发送数据的缓冲区 |
len | 实际要发送的数据的字节数 |
flags | 表示消息传输的标志,一般置0 |
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
描述:
IO多路复用
参数:
名字 | 描述 |
---|---|
nfds | 表示集合中所有文件描述符的范围 |
readfds | select监视的可读文件句柄集合 |
writefds | select监视的可写文件句柄集合 |
exceptfds | select监视的异常文件句柄集合 |
timeout | 本次select()的超时结束时间,NULL表示永久等待 |
主要代码分析
static void TCPServerTaskWithSelect(void)
{
//服务端地址信息
struct sockaddr_in server_sock;
//客户端地址信息
struct sockaddr_in client_sock;
int sin_size = sizeof(struct sockaddr_in);
struct sockaddr_in *cli_addr;
//连接Wifi
if(WifiConnect(WIFI_SSID,WIFI_PASSWORD)!=0)
{
perror("Wifi connect error!");
exit(1);
}
in_addr_t localIpaddr = GetLocalIpaddr();
printf("ip = %x\n",localIpaddr);
//创建socket
printf("socket begin\n");
int sock_fd=-1,new_fd=-1;
if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket is error\r\n");
exit(1);
}
printf("sock_fd=%d\n",sock_fd);
bzero(&server_sock, sizeof(server_sock));
server_sock.sin_family = AF_INET;
//server_sock.sin_addr.s_addr = htonl(INADDR_ANY);
inet_pton(AF_INET, "192.168.1.114", &server_sock.sin_addr);
server_sock.sin_port = htons(_PROT_);
char str[IP_LEN];
inet_ntop(AF_INET, &server_sock.sin_addr, str, sizeof(str));
printf("tcp server IP_addr: %s at PORT %d\n",str,ntohs(server_sock.sin_port));
//调用bind函数绑定socket和地址
if (bind(sock_fd, (struct sockaddr *)&server_sock, sizeof(struct sockaddr)) == -1)
{
perror("bind is error\r\n");
exit(1);
}
//调用listen函数监听(指定port监听)
if (listen(sock_fd, TCP_BACKLOG) == -1)
{
perror("listen is error\r\n");
exit(1);
}
//init fd_arr
printf("start select\r\n");
int i=0;
for(;i<_MAX_SIZE_;++i)
{
fd_arr[i]=-1;
}
add_fd_arr(sock_fd);
fd_set fds;
FD_ZERO(&fds);
while(true)
{
//更新max_fd和文件描述符集
max_fd=sock_fd;
for(int ii=0; ii<_MAX_SIZE_; ii++)
{
if(fd_arr[ii]!=-1)
{
FD_SET(fd_arr[ii],&fds);
if(fd_arr[ii]>max_fd)
{
max_fd=fd_arr[ii];
}
}
}
//printf("update max_fd = %d\n",max_fd);
printf("select wait...\n");
int ret = select(max_fd + 1, &fds, NULL, NULL, NULL);
if(ret<=0){
//超时等
printf("[DISCOVERY]ret:%d\n", ret);
continue;
}
if(FD_ISSET(sock_fd,&fds)) //判断是否发生accept
{
new_fd = accept(sock_fd, (struct sockaddr *)&client_sock, (socklen_t *)&sin_size);
//printf("accept new fd = %d\n",new_fd);
if(-1!=new_fd)
{
//打印客户端的ip和port
char str[IP_LEN];
inet_ntop(AF_INET, &client_sock.sin_addr, str, sizeof(str));
printf("connected from %s at PORT %d\n",str,ntohs(client_sock.sin_port));
if(1==add_fd_arr(new_fd))
{
perror("fd_arr is full,close new_fd\n");
close(new_fd);//此处比较粗陋,应该关闭久未发生信息交付的fd
}
}
continue;
}
//客户端发来信息
for(i=0;i<_MAX_SIZE_;++i)
{
if(fd_arr[i]!=-1&&FD_ISSET(fd_arr[i],&fds))
{
char buf[1024];
memset(buf,'\0',sizeof(buf));
ssize_t size=recv(fd_arr[i],buf,sizeof(buf)-1,0);
//"exit"特殊字符串,表示客户端申请断开链接
if(size==0 || size==-1 || memcmp(buf,"exit",size)==0)
{
printf("remote client fd is %d, close,size is %d,msg is %s\n",fd_arr[i],size,buf);
FD_CLR(fd_arr[i],&fds);
close(fd_arr[i]);
fd_arr[i]=-1;
}else
{
printf("recv: fd:%d,msg:%s",fd_arr[i],buf);
if ((ret = send(fd_arr[i], tcpSendBuf, strlen(tcpSendBuf) + 1, 0)) == -1)
{
perror("send error \r\n");
}
}
osDelay(10);
continue;
}
}
}
close(sock_fd);
FD_CLR(sock_fd,&fds);
sock_fd=-1;
return ;
}
修改tcp_server.c
第34行和35行的WiFi热点SSID和密码,改成自己环境中的WiFi热点。
// 默认WiFi名和密码
#define WIFI_SSID "aaa"
#define WIFI_PASSWORD "talkweb1996"
修改tcp_server.c
第28行和30行的端口号和ip地址,改成自己链接上wifi后的ip地址,此处应该从wifi的sdk中获取本身的ip地址,但还未找到相应api。
// 默认服务端的ip地址和端口号
#define _PROT_ 8800
#define _SERVER_IP_ "192.168.1.112"
修改 applications/app/BUILD.gn
路径中的 BUILD.gn 文件,指定 network_tcpserver_demo
参与编译。
# "TW303_Network_mqttclient:network_mqttclient_example",
# "TW402_APP_oled_u8g2:app_oled_u8g2_example",
"TW304_Network_tcpserver:network_tcpserver_demo",
# "TW304_Network_tcpclient:network_tcpclient_demo",
# "TW305_Network_udpclient:network_udpclient_demo",
示例代码编译烧录代码后,按下开发板的RESET按键,通过串口助手查看日志,会打印连接到的Wifi热点信息,以及开启tcp服务端,等待客户端连接
链接wifi成功,并打印IP信息
callback function for wifi connect
WaitConnectResult:wait success[1]s
WiFi connect succeed!
begain to dhcp<-- DHCP state:Inprogress -->
<-- DHCP state:OK -->
server :
server_id : 192.168.1.1
mask : 255.255.255.0, 1
gw : 192.168.1.1
T0 : 7200
T1 : 3600
T2 : 6300
clients <1> :
mac_idx mac addr state lease tries rto
0 849dc22100d4 192.168.1.112 10 0 1 4
开启tcp服务端,并进行监听等待
tcp server IP_addr: 192.168.1.112 at PORT 8800
select wait...
有客户端接入并进行通信
connected from 192.168.1.114 at PORT 61941
select wait...
recv: fd:1,msg:0000
select wait...
recv: fd:1,msg:1111
select wait...
recv: fd:1,msg:2222
select wait...
remote client fd is 1, close,size is 4,msg is exit
select wait...
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。