基于w5500实现TCP/IP协议后应用层开发
一、TCP Server/Client
首先需要搞清楚什么是服务端,什么是客户端。服务器端,指网络中能为用户提供服务的计算机系统,是为客户端服务的;客户端是指与服务器相对应,它是接受服务的一段,为客户提供本地服务的程序。看图就明白啦。
此时服务器处于守候状态,并侦听客户端的请求。客户端发出请求,并请求经互联网送给服务器。一旦服务器接收到这个请求,就可以执行请求所制定的任务,并将执行的结果经互联网回送给客户。
TCP 协议通过三个报文段完成连接的建立,这个过程称为三次握手(three-way handshake)。建立一个连接需要三次握手,而终止一个连接要经过四次挥手,这是由 TCP的半关闭(half-close)造成的。
1.1w5500作为tcp server
流程图如下:
第一步先初始化socket,socket处于SOCK_INIT 状态。
第二步作为TCP服务器就要执行listen()函数来侦听端口。socket进入SYN_RCVD状态。
第三步等到TCP三次握手完成便建立连接了。socket进入ESTABLISHED状态。现在可以进行数据的收发了
第四步,数据通信完成后,执行disconnect()函数,该socket进入SOCK_CLOSE_WAIT状态。
第五步连接出错或者超时,将会进入入 SOCK_CLOSE 状态。
*@brief TCP Server回环演示函数。
*@param 无
*@return 无
*/
void do_tcp_server(void)
{
uint16 len=0;
switch(getSn_SR(SOCK_TCPS)) /*获取socket的状态*/
{
case SOCK_CLOSED: /*socket处于关闭状态*/
socket(SOCK_TCPS ,Sn_MR_TCP,local_port,Sn_MR_ND); /*打开socket*/
break;
case SOCK_INIT: /*socket已初始化状态*/
listen(SOCK_TCPS); /*socket建立监听*/
break;
case SOCK_ESTABLISHED: /*socket处于连接建立状态*/
if(getSn_IR(SOCK_TCPS) & Sn_IR_CON)//接收中断,类似于串口接收中断。
{
setSn_IR(SOCK_TCPS, Sn_IR_CON); /*清除接收中断标志位*/
}
len=getSn_RX_RSR(SOCK_TCPS); /*定义len为已接收数据的长度*/
if(len>0)
{
recv(SOCK_TCPS,buff,len); /*接收来自Client的数据*/
buff[len]=0x00; /*添加字符串结束符*/
printf("%s\r\n",buff);
send(SOCK_TCPS,buff,len); /*向Client发送数据*/
}
break;
case SOCK_CLOSE_WAIT: /*socket处于等待关闭状态*/
close(SOCK_TCPS);
break;
}
}
//这里可以通过getSn_SR(SOCK_TCPS)这个函数来获得此时socket的状态。
#define SOCK_TCPS 0
#define SOCK_HUMTEM 0
#define SOCK_PING 0
#define SOCK_TCPC 1
#define SOCK_UDPS 2
#define SOCK_WEIBO 2
#define SOCK_DHCP 3
#define SOCK_HTTPS 4
#define SOCK_DNS 5
#define SOCK_SMTP 6
#define SOCK_NTP 7
可以看到这里的8个socket通过宏定义都有了自己的任务。如果socket的状态进入到上文提到的第三步也就是established状态,就可以实现客户端和服务端的通信了。使用如下两个函数,收发
*@brief This function is an application I/F function which is used to receive the data in TCP mode.
It continues to wait for data as much as the application wants to receive.
*@param s: socket number.
*@param buf: data buffer to receive.
*@param len: data length.
*@return received data size for success else 0.
*/
uint16 recv(SOCKET s, uint8 * buf, uint16 len)
{
uint16 ret=0;
if ( len > 0 )
{
recv_data_processing(s, buf, len);
IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_RECV);
/* wait to process the command... */
while( IINCHIP_READ(Sn_CR(s) ));
/* ------- */
ret = len;
}
return ret;
}
可以看出很多接收数据的函数都类似啊,变量就是socket号,缓存的数组和接收的字节数。
同时可以看到len=getSn_RX_RSR(SOCK_TCPS)这个函数是用来得到接收缓存区字节数大小的一个函数。他的brief是This fuction is to give size of received data in receive buffer.所以用这个函数来判断缓存区是否有数据到达。
同样也有发送函数即 uint16 send(SOCKET s, const uint8 * buf, uint16 len),发送成功返回值为1,失败为0.
1.2 w5500作为TCP client
其实也是差不多的流程,一样需要刚刚提到的getSn_SR(socket号)函数来获取socket号状态。只不过没有侦听(listen)的过程了,需要连接(connect)的过程。看下图:
第一步先选择ip信息,例程里提供了几种ip来源
#define IP_FROM_DEFINE 0 /*使用初始定义的IP信息*/
#define IP_FROM_DHCP 1 /*使用DHCP获取IP信息*/
#define IP_FROM_EEPROM 2 /*使用EEPROM定义的IP信息*/
extern uint8 ip_from; /*选择IP信息配置源*/
宏定义真的很方便啊,可以选择ip的来源。
1.3 w5500 用作UDP
1.将UDP模式下的socket端口初始化,进入SOCK_UDP状态。此时就可以通过广播方式发送数据了。
使用getSn_SR函数获取状态,输入量为socket端口序号,如果获取到udp端口的状态 SOCK_CLOSED,则初始化端口,使用函数uint8 socket(SOCKET s, uint8 protocol, uint16 port, uint8 flag)进行初始化,SOCKET s:为socket端口号,protocol:为选择的协议,port:为远程端口还是本地端口,flag为0;
2.进入SOCK_UDP状态后就可以发送和接收数据了。
不好意思过了好久才更新,最近一直忙项目。
1.4 DHCP
W5500作为DHCP的客户端,路由器作为DHCP的服务端。在 DHCP请求的过程中,包括 4 个主要的阶段:发现阶段、提供阶段、选择阶段以及确认阶段。
首先 W5500 客户端发送 DHCPDISCOVER 消息(IP 地址租用申请),这个消息通过广播方式发出。
/**
*@brief 发送DISCOVER信息给DHCP服务器
*@param 无
*@return 无
*/
void send_DHCP_DISCOVER(void)
使用这个函数就可以了。
应用在检查DHCP状态上
switch ( dhcp_state )
{
case STATE_DHCP_READY: /*DHCP初始化状态*/
DHCP_timeout = 0; /*DHCP超时标志设置为1*/
reset_DHCP_time(); /*复位超时时间*/
send_DHCP_DISCOVER(); /*发送DISCOVER包*/
DHCP_timer = 0; /*set_timer0(DHCP_timer_handler); */
dhcp_state = STATE_DHCP_DISCOVER; /*DHCP的DISCOVER状态*/
break;
当客户端由初始化状态进入到STATE_DHCP_DISCOVER状态,就可以等待服务器回应的DHCPOFFER消息。
case STATE_DHCP_DISCOVER:
if (type == DHCP_OFFER)
{
reset_DHCP_time(); /*复位超时时间*/
send_DHCP_REQUEST(); /*发送REQUEST包*/
dhcp_state = STATE_DHCP_REQUEST;
#ifdef DHCP_DEBUG
printf("state : STATE_DHCP_REQUEST\r\n");
#endif
}
else
check_DHCP_Timeout(); /*检查是否超时*/
break;
type是刚刚解析收到的封包类型。接下来进入到了送 DHCPREQUEST状态,这个状态下在 DHCPREQUEST 消息中将包含客户端申请的 IP 地址。
case STATE_DHCP_REQUEST : /*DHCP的REQUEST状态*/
if (type == DHCP_ACK) /*接收到DHCP服务器回应的off封包*/
{
reset_DHCP_time(); //DHCP定时初始化
if (check_leasedIP())
{
#ifdef DHCP_DEBUG
printf("state : STATE_DHCP_LEASED\r\n");
#endif
dhcp_state = STATE_DHCP_LEASED;//当检查ip不冲突后,进入ip租赁状态
return DHCP_RET_UPDATE; //获取地址成功
}
else
{
#ifdef DHCP_DEBUG
printf("state : STATE_DHCP_DISCOVER\r\n");
#endif
dhcp_state = STATE_DHCP_DISCOVER;
return DHCP_RET_CONFLICT;
}
}
else if (type == DHCP_NAK) //如果收不到ack回应包,就继续请求
{
reset_DHCP_time(); /*复位超时时间*/
dhcp_state = STATE_DHCP_DISCOVER;
#ifdef DHCP_DEBUG
printf("state : STATE_DHCP_DISCOVER\r\n");
#endif
}
else
check_DHCP_Timeout(); /*检查是否超时*/
break;
最后,DHCP 服务器将回送 DHCPACK 的响应消息来通知客户端可以使用该 IP 地址,该确认里面包含了分配的 IP 地址和该地址的一个稳定期限的租约(默认是 8 天),并同时更新 DHCP 数据库。
主程序的话依然是状态机编程,相比之前的tcp,udp,DHCP还是多了很多状态的复杂了。
void do_dhcp(void)
{
uint8 dhcpret=0;
ip_from=IP_FROM_DHCP; /*IP配置方法选择为DHCP*/
dhcp_timer_init(); /*初始化DHCP定时器*/
if(Conflict_flag == 1)
{
init_dhcp_client(); /*初始化DHCP客户端*/
Conflict_flag =0;
}
dhcpret = check_DHCP_state(SOCK_DHCP); /*获取DHCP服务状态*/
switch(dhcpret)
{
case DHCP_RET_NONE: /*IP地址获取不成功*/
break;
case DHCP_RET_TIMEOUT: /*IP地址获取超时*/
break;
case DHCP_RET_UPDATE: /*成功获取到IP地址*/
dhcp_ok=1;
set_w5500_ip(); /*将获取到的IP地址写入W5500寄存器*/
printf(" 已从DHCP服务器成功获得IP地址\r\n");
break;
case DHCP_RET_CONFLICT: /*IP地址获取冲突*/
printf(" 从DHCP获取IP地址失败\r\n");
dhcp_state = STATE_DHCP_READY;
printf(" 重试中\r\n");
dhcp_ok=0;
break;
default:
break;
}
}
对应刚才获取dhcp服务器状态的继续状态机,如果获取成功就将ip地址通过spi写入w5500寄存器。
1.5DNS域名服务器
通过DNS把相当于把网站的ip地址变成网站网址。
/**
*@brief 查询DNS报文信息,解析来自DNS服务器的回复
*@param s:DNS服务器socket,name:要解析的信息
*@return 成功: 返回1, 失败 :返回 -1
*/
uint8 dns_query(uint8 s, uint8 * name)
{
static uint32 dns_wait_time = 0;
struct dhdr dhp; /*定义一个结构体用来包含报文头信息*/
uint8 ip[4];
uint16 len, port;
switch(getSn_SR(s)) /*获取socket状态*/
{
case SOCK_CLOSED:
dns_wait_time = 0;
socket(s, Sn_MR_UDP, 3000, 0); /*打开W5500的socket的3000端口,并设置为UDP模式*/
break;
case SOCK_UDP: /*socket已打开*/
len = dns_makequery(0, name, BUFPUB, MAX_DNS_BUF_SIZE); /*接收DNS请求报文并存入BUFPUB*/
sendto(s, BUFPUB, len, EXTERN_DNS_SERVERIP, IPPORT_DOMAIN); /*发送DNS请求报文给DNS服务器*/
if ((len = getSn_RX_RSR(s)) > 0)
{
if (len > MAX_DNS_BUF_SIZE) len = MAX_DNS_BUF_SIZE;
len = recvfrom(s, BUFPUB, len, ip, &port); /*接收UDP传输的数据并存入BUFPUB*/
if(parseMSG(&dhp, BUFPUB)) /*解析DNS响应信息*/
{
close(s); /*关闭socket*/
return DNS_RET_SUCCESS; /*返回DNS解析成功域名信息*/
}
else
dns_wait_time = DNS_RESPONSE_TIMEOUT; /*等待响应时间设置为超时*/
}
else
{
delay_ms(1000); /*没有收到DNS服务器的UDP回复,避免太频繁,延时1s*/
dns_wait_time++; /*DNS响应时间加1*/
}
if(dns_wait_time >= DNS_RESPONSE_TIMEOUT) /*如果DNS等待时间超过3s*/
{
close(s); /*关闭socket*/
return DNS_RET_FAIL;
}
break;
}
return DNS_RET_PROGRESS;
}
烂尾了
后面的NTP,NetBIOS,HTTP Client,HTTP Server,ICMP以后再讲吧。