7.物联网LWIP之DNS,超时机制,组播,广播

本文详细介绍了如何在LWIP环境中实现DNS域名解析,包括DNS模块的开启、配置服务器地址,以及使用gethostbyname获取IP地址。同时探讨了心跳检测机制的设置,包括TCPKeepalive选项和广播/组播的基础知识,最后通过Wireshark抓包验证了广播和组播的效果。
摘要由CSDN通过智能技术生成

一。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)这是广播的抓包

 (2)这是组播的抓包

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值