一。DNS域名解析
1.DNS作用
DNS是计算机域名系统(Domain Name System 或 Domain Name Service)的缩写,它是由解析器和域名服务器组成的,作用是把域名转换成为网络可以识别的ip地址。举一个简单的例子,域名相当于门牌号,而IP地址相当于具体的地理位置。
DNS是用来做域名解析的,它会在你上网输入网址后,把它转换成IP,然后去访问对方服务器,没有它,如果想上百度就要记住百度的IP,上163就要记住163的IP,有了DNS的处理,你只需要记住对应的域名也就是网址就可以了。
2.DNS API
(1)宏开关
需要使用DNS,所以需要把这个宏开关打开,原因:本来就是这样设定
//opt.h
/**
* LWIP_DNS==1: Turn on DNS module. UDP must be available for DNS
* transport.
*/
#if !defined LWIP_DNS || defined __DOXYGEN__
#define LWIP_DNS 1
#endif
在LWIP的文件夹中
(2)配置DNS服务器
<1>dns.c添加代码
//dns.c 需要手动添加
/** DNS server IP address */
#ifndef DNS_SERVER_ADDRESS
extern ip4_addr_t gw; //网关地址
#define DNS_SERVER_ADDRESS(ipaddr) (memcpy(ipaddr, &gw, sizeof(ip4_addr_t)))
#endif
#ifdef DNS_SERVER_ADDRESS
/* initialize default DNS server address */
ip_addr_t dnsserver;
DNS_SERVER_ADDRESS(&dnsserver);
dns_setserver(0, &dnsserver);
#endif /* DNS_SERVER_ADDRESS */
<2>lwip.c修改代码
<3>gethostbyname
//netdb.h
/** @ingroup netdbapi */
#define gethostbyname(name) lwip_gethostbyname(name)
struct hostent *lwip_gethostbyname(const char *name);
struct hostent {
char *h_name; /* 正式主机名 */
char **h_aliases; /* 主机别名*/
int h_addrtype; /* 主机IP地址类型 */
int h_length; /* 主机IP地址字节长度*/
char **h_addr_list; /* 主机的IP地址列表*/
};
DNS获取到的内容为一个结构体,最主要的是最后一项(主机的ip地址列表)
3.实验:实现DNS解析
(1)dns_client.h
#ifndef _DNS_CLIENT_H
#define _DNS_CLIENT_H
void vDnsClientTask(void);
#endif
(2)dns_client.c
#include "socket_tcp_server.h"
#include "dns_client.h"
#include "socket_wrap.h"
#include "ctype.h"
#include "FreeRTOS.h"
#include "task.h"
#include "netdb.h"
static char ReadBuff[BUFF_SIZE];
void vDnsClientTask(void){
int cfd, n, i, ret;
struct sockaddr_in server_addr;
//dns 域名解析功能
struct hostent *p_hostent = NULL;
p_hostent = gethostbyname("www.makeru.com.cn");
if(p_hostent){
for(i = 0; p_hostent->h_addr_list[i]; i++){
printf("host ip:%s\r\n", inet_ntoa(*p_hostent->h_addr_list[i]));
}
}else{
printf("get host ip fail!\r\n");
}
again:
//创建socket
cfd = Socket(AF_INET, SOCK_STREAM, 0);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
//连接到服务器
//connect 其实是一个阻塞接口,内部要完成TCP的三次握手,当然有超时机制,所以我们需要等一段时间,才能重新连接到服务器
ret = Connect(cfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
if(ret < 0){
//100ms去连接一次服务器
vTaskDelay(100);
goto again;
}
printf("server is connect ok\r\n");
while(1){
//等待服务器发送数据
n = Read(cfd, ReadBuff, BUFF_SIZE);
if(n <= 0){
goto again;
}
//进行大小写转换
for(i = 0; i < n; i++){
ReadBuff[i] = toupper(ReadBuff[i]);
}
//写回服务器
n = Write(cfd, ReadBuff, n);
if(n <= 0){
goto again;
}
}
}
二。心跳及超时机制实现
1.心跳检测
网线掉了怎么办?
心跳检测就是应对网络断开出现的没有报错的问题,以前优化stm32作为服务器端的代码时,仅仅只是对客户端连接失败做出了错误提示,但是在网络断开的时候,是不会有任何反应的。
2.setsocketopt
//功能说明:
/*
获取或者与某个套接字关联的选 项。选项可能存在于多层中,它们总会出现在最上面的套接字层。当操作套接字选项时,选项位于的层和选项的名称必须给出。为了操作套接字层的选项,应该 将层的值指定为SOL_SOCKET。为了操作其它层的选项,控制选项的合适协议号必须给出。例如,为了表示一个选项由协议解析,层应该设定为协议 号TCP。
*/
//函数原型
int getsockopt(int sock, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen);
//参数:
/*
sock:将要被设置或者获取选项的套接字。
level:选项所在的协议层。
optname:需要访问的选项名。
optval:对于getsockopt(),指向返回选项值的缓冲。对于setsockopt(),指向包含新选项值的缓冲。
optlen:对于getsockopt(),作为入口参数时,选项值的最大长度。作为出口参数时,选项值的实际长度。对于setsockopt(),现选项的长度。
*/
//返回说明:
成功执行时,返回0。失败返回-1
//参数详细说明:
/*
level指定控制套接字的层次.可以取三种值:
1)SOL_SOCKET:通用套接字选项.
2)IPPROTO_IP:IP选项.
3)IPPROTO_TCP:TCP选项.选项名称 说明 数据类型
=====================================================================
SOL_SOCKET
---------------------------------------------------------------------
SO_BROADCAST 允许发送广播数据 int
SO_DEBUG 允许调试 int
SO_DONTROUTE 不查找 int
SO_ERROR 获得套接字错误 int
SO_KEEPALIVE 保持连接 int
SO_LINGER 延迟关闭连接 struct linger
SO_OOBINLINE 带外数据放入正常数据流 int
SO_RCVBUF 接收缓冲区大小 int
SO_SNDBUF 发送缓冲区大小 int
SO_RCVLOWAT 接收缓冲区下限 int
SO_SNDLOWAT 发送缓冲区下限 int
SO_RCVTIMEO 接收超时 struct timeval
SO_SNDTIMEO 发送超时 struct timeval
SO_REUSERADDR 允许重用本地地址和端口 int
SO_TYPE 获得套接字类型 int
SO_BSDCOMPAT 与BSD系统兼容 int
========================================================================
IPPROTO_IP
------------------------------------------------------------------------
IP_HDRINCL 在数据包中包含IP首部 int
IP_OPTINOS IP首部选项 int
IP_TOS 类型
IP_TTL 生存时间 int
========================================================================
IPPROTO_IP
------------------------------------------------------------------------
TCP_MAXSEG TCP最大数据段的大小 int
TCP_NODELAY 不使用Nagle算法 int
=====================================================================
*/
3.TCP Keepalive
(1)setsockopt参数
<br class="Apple-interchange-newline"><div></div>
#define SO_KEEPALIVE 0x0008 /* 保持连接 */ val = int
#define TCP_KEEPIDLE 0x03 /* 发送心跳空闲周期 S*/ val = int
#define TCP_KEEPINTVL 0x04 /* 发送心跳间隔 S */ val = int
#define TCP_KEEPCNT 0x05 /* 心跳重发次数 */ val = int
(2)宏开关
/**
* LWIP_TCP_KEEPALIVE==1: Enable TCP_KEEPIDLE, TCP_KEEPINTVL and TCP_KEEPCNT
* options processing. Note that TCP_KEEPIDLE and TCP_KEEPINTVL have to be set
* in seconds. (does not require sockets.c, and will affect tcp.c)
*/
#if !defined LWIP_TCP_KEEPALIVE || defined __DOXYGEN__
#define LWIP_TCP_KEEPALIVE 0
#endif
4.实验:心跳检测
步骤:
(1)opt中keepalive开启
(2)tcp_keepalive.h
#ifndef _TCP_KEEPALIVE_H
#define _TCP_KEEPALIVE_H
void vTcpKeepaliveTask(void);
#endif
(3)tcp_keepalive.c
#include "socket_tcp_server.h"
#include "tcp_keepalive.h"
#include "socket_wrap.h"
#include "ctype.h"
#include "FreeRTOS.h"
#include "task.h"
static char ReadBuff[BUFF_SIZE];
void vTcpKeepaliveTask(void)
{
int cfd, n, i, ret;
struct sockaddr_in server_addr;
int so_keepalive_val = 1;
int tcp_keepalive_idle = 3;
int tcp_keepalive_intvl = 3;
int tcp_keepalive_cnt = 3;
int tcp_nodelay = 1;
again:
//创建socket
cfd = Socket(AF_INET, SOCK_STREAM, 0);
//使能socket层 心跳检测
setsockopt(cfd, SOL_SOCKET, SO_KEEPALIVE, &so_keepalive_val, sizeof(int));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
//连接到服务器
//connect 其实是一个阻塞接口,内部要完成TCP的三次握手,当然有超时机制,所以我们需要等一段时间,才能重新连接到服务器
ret = Connect(cfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
if(ret < 0){
//100ms去连接一次服务器
vTaskDelay(100);
goto again;
}
//配置心跳检测参数 默认参数时间很长
setsockopt(cfd, IPPROTO_TCP, TCP_KEEPIDLE, &tcp_keepalive_idle, sizeof(int));
setsockopt(cfd, IPPROTO_TCP, TCP_KEEPINTVL, &tcp_keepalive_intvl, sizeof(int));
setsockopt(cfd, IPPROTO_TCP, TCP_KEEPCNT, &tcp_keepalive_cnt, sizeof(int));
setsockopt(cfd, IPPROTO_TCP, TCP_NODELAY, &tcp_nodelay, sizeof(int));
printf("server is connect ok\r\n");
while(1){
//等待服务器发送数据
n = Read(cfd, ReadBuff, BUFF_SIZE);
if(n <= 0){
goto again;
}
//进行大小写转换
for(i = 0; i < n; i++){
ReadBuff[i] = toupper(ReadBuff[i]);
}
//写回服务器
n = Write(cfd, ReadBuff, n);
if(n <= 0){
goto again;
}
}
}
三。组播与广播基础知识
1.组播,广播区分
2.广播实现
注意必须使能项
#if !defined LWIP_IGMP || defined __DOXYGEN__
#define LWIP_IGMP 1
#endif
(1)广播实现
//SOL_SOCKET
#define SO_BROADCAST 0x0020 /* 广播许可 */
//val = int
(2)组播实现
//IPPROTO_IP
#define IP_ADD_MEMBERSHIP 3 /*加入组播*/
#define IP_DROP_MEMBERSHIP 4 /*退出组播*/
#define IP_MULTICAST_IF 6 /*组播默认网卡选择*/
#define IP_MULTICAST_LOOP 7 /*组播默认回环*///val
typedef struct ip_mreq {
struct in_addr imr_multiaddr; /* IP multicast address of group */
struct in_addr imr_interface; /* local IP address of interface */
} ip_mreq;
(1)boradcast.h
#ifndef _BORADCAST_H
#define _BORADCAST_H
void vBoradcastTask(void);
#endif
(2)boradcast.c
#include "boradcast.h"
#include "socket_tcp_server.h"
#include "socket_wrap.h"
#include "ctype.h"
#include "string.h"
#include "FreeRTOS.h"
#include "task.h"
/**
* @brief boradcast 服务器任务
* @param None
* @retval None
*/
void vBoradcastTask(void){
int sfd;
struct sockaddr_in client_addr;
socklen_t client_addr_len;
int optval = 1;
//创建socket udp通信
sfd = Socket(AF_INET, SOCK_DGRAM, 0);
setsockopt(sfd, SOL_SOCKET, SO_BROADCAST, &optval, sizeof(optval));
client_addr.sin_family = AF_INET;
client_addr.sin_port = htons(SERVER_PORT);
client_addr.sin_addr.s_addr = inet_addr("255.255.255.255");
client_addr_len = sizeof(client_addr);
while(1){
//发送广播数据
Sendto(sfd, "broadcast data", strlen("broadcast data"), 0, (struct sockaddr *)&client_addr, client_addr_len);
vTaskDelay(5000);
}
}
4.组播实现:
配置:
使能IPv4的muticast
(1)multicast.h
#ifndef _MULTICAST_H
#define _MULTICAST_H
void vMulticastTask(void);
#endif
(2)multicast.c
#include "multicast.h"
#include "socket_tcp_server.h"
#include "socket_wrap.h"
#include "ctype.h"
#include "string.h"
#include "FreeRTOS.h"
#include "task.h"
#include "sockets.h"
/**
* @brief multicast 服务器任务
* @param None
* @retval None
*/
void vMulticastTask(void){
int sfd;
struct sockaddr_in client_addr;
socklen_t client_addr_len;
struct ip_mreq multicast_mreq;
//填充组播地址信息
multicast_mreq.imr_multiaddr.s_addr = inet_addr("224.0.1.1");
multicast_mreq.imr_interface.s_addr = htonl(INADDR_ANY);
//创建socket udp通信
sfd = Socket(AF_INET, SOCK_DGRAM, 0);
//设置组播选项
setsockopt(sfd, IPPROTO_IP, IP_MULTICAST_IF, &multicast_mreq, sizeof(multicast_mreq));
client_addr.sin_family = AF_INET;
client_addr.sin_port = htons(SERVER_PORT);
client_addr.sin_addr.s_addr = inet_addr("224.0.1.1"); //组播ip
client_addr_len = sizeof(client_addr);
while(1){
//发送广播数据
Sendto(sfd, "multicast data", strlen("multicast data"), 0, (struct sockaddr *)&client_addr, client_addr_len);
vTaskDelay(5000);
}
}
四。wireshark抓包验证
补充:wireshark的常用按键
1.wireshark使用
(1)使用流程
选择网卡---》过滤配置
(2)过滤器使用方法
<1>比较运算符
eq, == Equal
ne, != Not Equal
gt, > Greater Than
lt, < Less Than
ge, >= Greater than or Equal to
le, <= Less than or Equal to
<2>协议字段
#以太网过滤
eth.dst eq ff:ff:ff:ff:ff:ff
#IP地址过滤
ip.dst eq 192.168.1.10
ip.src == 192.168.1.1
#TCP过滤
tcp.port == 6666
# UDP过滤
udp.port == 6666
# http过滤
http.request.method == "POST"
<3>位域操作
# TCP SYN
tcp.flags & 0x02
<4>逻辑表达式
and, && Logical AND
or, || Logical OR
not, ! Logical NOT
# tcp.port == 80 and ip.src == 192.168.2.1
2.结果(在上述广播实验的基础上,在抓包工具中检测)
(1)这是广播的抓包
文章来源:https://www.toymoban.com/news/detail-662651.html
(2)这是组播的抓包文章来源地址https://www.toymoban.com/news/detail-662651.html
到了这里,关于7.物联网LWIP之DNS,超时机制,组播,广播的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!