系列文章
一、实现思路
W5500内部是硬件TCP/IP协议栈,对外(MCU)只是提供了操作socket的能力,内部支持8个独立的socket,每一个socket通过Socket n寄存器区控制(0≤n≤7)。
所以在编写基于Socket的网络应用程序时,可以按照查询Socket状态寄存器实现一个状态机的思路来实现。
W5500驱动库中提供了Socket状态寄存器的读取宏:
/**
* @ingroup Socket_register_access_function
* @brief Get @ref Sn_SR register
* @param (uint8_t)sn Socket number. It should be <b>0 ~ 7</b>.
* @return uint8_t. Value of @ref Sn_SR.
*/
#define getSn_SR(sn) \
WIZCHIP_READ(Sn_SR(sn))
W5500驱动库对读取出的状态值也提供了宏定义:
/* Sn_SR values */
#define SOCK_CLOSED 0x00
#define SOCK_INIT 0x13
#define SOCK_LISTEN 0x14
#define SOCK_SYNSENT 0x15 //Socket n状态改变时的临时状态,已经发送连接请求到对方
#define SOCK_SYNRECV 0x16 //Socket n状态改变时的临时状态,成功接收到了连接请求包
#define SOCK_ESTABLISHED 0x17
#define SOCK_FIN_WAIT 0x18 //Socket n状态改变时的临时状态,socket正在关闭
#define SOCK_CLOSING 0x1A //Socket n状态改变时的临时状态,socket正在关闭
#define SOCK_TIME_WAIT 0x1B //Socket n状态改变时的临时状态,socket正在关闭
#define SOCK_CLOSE_WAIT 0x1C
#define SOCK_LAST_ACK 0x1D //Socket n状态改变时的临时状态,socket在被动关闭状态下,正在等待对断开连接请求做出回应
二、TCP 服务端的实现
1. 实现功能
实现一个TCP服务端,可以接收TCP客户端的请求,在收到客户端数据之后打印,并回发收到的数据。
2. TCP服务端状态机
3. 实现代码
编写demo文件tcp_server.c
:
#include "socket.h"
#include <stdio.h>
#define DATA_BUF_SIZE 2048
static uint8_t recv_buf[DATA_BUF_SIZE];
int tcp_server(uint8_t sn, uint16_t port)
{
int8_t ret;
uint8_t dest_ip[4];
uint16_t dest_port;
uint16_t size = 0;
switch (getSn_SR(sn)) {
case SOCK_CLOSED:
/* open socket */
printf("TCP server start\r\n");
if ((ret = socket(sn, Sn_MR_TCP, port, 0x00)) != sn) {
printf("socket %d open fail\r\n", sn);
return ret;
}
printf("socket %d open success\r\n", sn);
break;
case SOCK_INIT:
/* waiting for a client to connect */
printf("listen %d port...\r\n", port);
if ((ret = listen(sn)) != SOCK_OK) {
printf("%d:listen fail\r\n", sn);
return ret;
}
printf("%d:listen success\r\n", sn);
break;
case SOCK_ESTABLISHED:
/* socket has been established */
if(getSn_IR(sn) & Sn_IR_CON) {
getSn_DIPR(sn, dest_ip);
dest_port = getSn_DPORT(sn);
printf("%d:a client connect success, %d.%d.%d.%d:%d\r\n", sn, dest_ip[0], dest_ip[1], dest_ip[2], dest_ip[3], dest_port);
setSn_IR(sn,Sn_IR_CON);
}
// get the size of recv data in recv buffer
if ((size = getSn_RX_RSR(sn)) > 0) {
if (size > DATA_BUF_SIZE) {
size = DATA_BUF_SIZE;
}
//recv data
ret = recv(sn, recv_buf, DATA_BUF_SIZE);
if (ret <= 0) {
printf("%d:recv fail\r\n", sn);
return ret;
} else {
// The actual received size
size = (uint16_t)ret;
printf("%d:recv size:%d\r\n", sn, size);
recv_buf[size] = '\0';
printf("%d:recv data:[%s]\r\n", sn, recv_buf);
}
//send resp data
ret = send(sn, recv_buf, size);
if (ret <= 0) {
printf("%d:send fail\r\n", sn);
//close the socket
close(sn);
return ret;
} else {
// The actual sent size
size = (uint16_t)ret;
printf("%d:send size:%d\r\n", sn, size);
recv_buf[size] = '\0';
printf("%d:send data:[%s]\r\n", sn, recv_buf);
}
}
break;
case SOCK_CLOSE_WAIT:
/* closing the socket */
if ((ret = disconnect(sn)) != SOCK_OK) {
printf("%d:disconnect fail\r\n", sn);
return ret;
}
printf("%d: socket is closed\r\n", sn);
break;
default:
break;
}
return 0;
}
4. 实现效果
将编写的demo文件加入到工程中,在main.c中声明一下该测试函数:
extern int tcp_server(uint8_t sn, uint16_t port);
因为该函数的实现是状态机,所以在while(1)中调用:
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
tcp_server(0, 8000);
}
/* USER CODE END 3 */
编译,下载到开发板中,使用串口助手查看结果:
在PC上运行socket调试助手,建立一个tcp client连接开发板:
TCP客户端连接之后,可以看到开发板打印出的信息:
接着在网络调试助手中发送信息:
可以看到,网络调试助手发送之后,收到了开发板回发的消息,在开发板上也可以看到日志:
当TCP客户端断开链接后,查看开发板串口日志:
三、TCP客户端的实现
1. 实现功能
实现一个TCP客户端,可以发起TCP客户端的请求,先向服务器发送数据,然后接收服务器回发的数据之后打印。
2. TCP客户端状态机
3. 实现代码
编写demo文件tcp_client.c
:
#include "socket.h"
#include <stdio.h>
#include <string.h>
#define DEFAULT_PORT 6000
#define DATA_BUF_SIZE 2048
static uint8_t send_buf[DATA_BUF_SIZE];
static uint8_t recv_buf[DATA_BUF_SIZE];
int tcp_client(uint8_t sn, uint8_t *dest_ip, uint16_t dest_port)
{
int8_t ret;
uint16_t recv_size = 0;
uint16_t send_size = 0;
switch (getSn_SR(sn)) {
case SOCK_CLOSED:
/* open socket */
printf("TCP client start\r\n");
if ((ret = socket(sn, Sn_MR_TCP, DEFAULT_PORT, 0x00)) != sn) {
printf("socket %d open fail\r\n", sn);
return ret;
}
printf("socket %d open success\r\n", sn);
break;
case SOCK_INIT:
/* connect server */
printf("try to connect %d.%d.%d.%d:%d...\r\n", dest_ip[0], dest_ip[1], dest_ip[2], dest_ip[3], dest_port);
if ((ret = connect(sn, dest_ip, dest_port)) != SOCK_OK) {
return ret;
}
break;
case SOCK_ESTABLISHED:
/* socket has been established */
if(getSn_IR(sn) & Sn_IR_CON) {
printf("%d:Connected to - %d.%d.%d.%d : %d\r\n",sn, dest_ip[0], dest_ip[1], dest_ip[2], dest_ip[3], dest_port);
setSn_IR(sn,Sn_IR_CON);
}
// get the size of recv data in recv buffer
if ((recv_size = getSn_RX_RSR(sn)) > 0) {
if (recv_size > DATA_BUF_SIZE) {
recv_size = DATA_BUF_SIZE;
}
//recv data
ret = recv(sn, recv_buf, DATA_BUF_SIZE);
if (ret <= 0) {
printf("%d:recv fail\r\n", sn);
return ret;
} else {
// The actual received size
recv_size = (uint16_t)ret;
printf("%d:recv size:%d\r\n", sn, recv_size);
recv_buf[recv_size] = '\0';
printf("%d:recv data:[%s]\r\n", sn, recv_buf);
}
//send data
strcpy((char*)send_buf, "Hello, Server!");
ret = send(sn, send_buf, strlen((char*)send_buf));
if (ret <= 0) {
printf("%d:send fail\r\n", sn);
//close the socket
close(sn);
return ret;
} else {
// The actual sent size
send_size = (uint16_t)ret;
printf("%d:send size:%d\r\n", sn, send_size);
//recv_buf[size] = '\0';
printf("%d:send data:[%s]\r\n", sn, send_buf);
}
}
break;
case SOCK_CLOSE_WAIT:
/* closing the socket */
if ((ret = disconnect(sn)) != SOCK_OK) {
printf("%d:disconnect fail\r\n", sn);
return ret;
}
printf("%d: socket is closed\r\n", sn);
break;
default:
break;
}
return 1;
}
在main.c中定义目标地址和目标端口:
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
uint8_t dest_ip[4] = {192, 168, 10, 156};
uint16_t dest_port = 8000;
/* USER CODE END PV */
声明测试函数:
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
extern int tcp_client(uint8_t sn, uint8_t *dest_ip, uint16_t dest_port);
/* USER CODE END 0 */
在while(1)中循环调用:
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
tcp_client(0, dest_ip, dest_port);
}
/* USER CODE END 3 */
4. 实验结果
首先在电脑上使用网络调试助手建立一个TCP服务器,在8000端口:
然后编译下载程序:
在网络调试助手中发送消息,可以看到开发板收到数据后回发的消息:
在开发板一侧也可以看到日志: