目录
一、引言
二、硬件准备
三、软件准备
四、LWIP 协议栈的配置与初始化
五、创建 TCP 服务器
1.创建任务以及全局变量
2.创建 TCP 控制块
3.绑定端口
4. 进入监听状态
5.设置接收回调函数
六、处理多个客户端连接
七、总结
一、引言
在嵌入式系统开发中,常常需要实现设备之间的网络通信。STM32 作为一款广泛应用的微控制器,结合网络通信功能可以实现与多个设备的交互。本文将介绍如何在 STM32 上实现 TCP 服务器端,以便与多个设备进行通讯。
二、硬件准备
三、软件准备
四、LWIP 协议栈的配置与初始化
五、创建 TCP 服务器
1.创建任务以及全局变量
我用的是ucosIII的实时操作系统,用freertos也差不多,把调度的代码替换一下就行了,第五个大标题2 3 4 5的代码都是在 tcp_server_task 这个任务函数里面的,然后第六个标题是每个客户端的回调函数,整理在一起就是完整的代码。
#include "sys.h"
#include "lwip_comm.h"
#include "includes.h"
#include "lwip/api.h"
#include "lwip/err.h"
#include "lwip/tcp.h"
#include <stdio.h>
#define TCP_SERVER_PORT 8088 // 定义TCP服务器监听的端口号,可按需修改
#define MAX_CONNECTIONS 2 // 最大允许同时连接的客户端数量
#define RX_BUFFER_SIZE 1024 // 接收缓冲区大小
// 用于存储客户端连接的结构体数组
struct tcp_pcb *tcp_server_pcbs[MAX_CONNECTIONS];
// 用于保护对tcp_server_pcbs数组操作的互斥信号量
OS_SEM tcp_server_pcbs_sem;
// 处理客户端连接的函数
err_t client_connection_handler(void *arg, struct tcp_pcb *newpcb, err_t err);
// 处理客户端接收数据的函数
err_t client_recv_handler(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err);
// 处理客户端发送数据的函数(示例中简单回显数据给客户端)
err_t client_send_handler(void *arg, struct tcp_pcb *tpcb, u16_t len);
// 处理客户端断开连接的函数
void client_err_handler(void *arg, err_t err);
// TCP服务器任务函数
void tcp_server_task(void *p_arg);
//TCP客户端任务
#define TCP_PRIO7
//任务堆栈大小
#define TCP_STK_SIZE300
//任务控制块
OS_TCBTcpTaskTCB;
//任务堆栈
CPU_STK TCP_TASK_STK[TCP_STK_SIZE];
//创建TCP线程
//返回值:0 TCP创建成功
//其他 TCP创建失败
u8 tcp_demo_init(void)
{
OS_ERR err;
CPU_SR_ALLOC();
OS_CRITICAL_ENTER();//进入临界区
//创建TCP任务
OSTaskCreate((OS_TCB * )&TcpTaskTCB,
(CPU_CHAR* )"tcp task",
(OS_TASK_PTR )tcp_server_task,
(void* )0,
(OS_PRIO )TCP_PRIO,
(CPU_STK * )&TCP_TASK_STK[0],
(CPU_STK_SIZE)TCP_STK_SIZE/10,
(CPU_STK_SIZE)TCP_STK_SIZE,
(OS_MSG_QTY )0,
(OS_TICK )0,
(void * )0,
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
(OS_ERR * )&err);
OS_CRITICAL_EXIT();//退出临界区
return err;
}
2.创建 TCP 控制块
使用 LWIP 的 API 函数tcp_new()创建一个新的 TCP 控制块。
struct tcp_pcb *server_pcb;
err_t err;
OS_ERR os_err;
// 创建一个TCP协议控制块(PCB)用于服务器端
server_pcb = tcp_new();
if (server_pcb == NULL)
{
printf("Error creating TCP PCB\\n");
return;
}
3.绑定端口
使用tcp_bind()函数将 TCP 控制块绑定到一个特定的端口号。通常选择一个未被其他应用程序占用的端口。
// 绑定服务器的IP地址和端口号
err = tcp_bind(server_pcb, IP_ADDR_ANY, TCP_SERVER_PORT);
if (err!= ERR_OK)
{
printf("Error binding TCP socket: %d\\n", err);
tcp_close(server_pcb);
return;
}
这里的SERVER_PORT是服务器监听的端口号。
4. 进入监听状态
使用tcp_listen()函数使 TCP 控制块进入监听状态,等待客户端的连接请求。
// 将服务器PCB设置为监听状态,等待客户端连接
server_pcb = tcp_listen(server_pcb);
if (server_pcb == NULL)
{
printf("Error listening for connections\\n");
tcp_close(server_pcb);
return;
}
5.设置接收回调函数
使用tcp_accept()函数设置一个接收回调函数,当有客户端连接请求时,该回调函数将被调用。
// 设置接受客户端连接的回调函数
tcp_accept(server_pcb, client_connection_handler);
while (1)
{
// 任务可以在这里进行适当的阻塞等待,避免过度占用CPU资源
OSTimeDlyHMSM(0,0,0,100,OS_OPT_TIME_HMSM_STRICT,&os_err); //延时100ms
}
client_connection_handler是自定义的回调函数,用于处理客户端的连接请求。
六、处理多个客户端连接
1.在接收回调函数中,接受客户端的连接请求,并为每个连接创建一个新的 TCP 控制块来处理与该客户端的通信,由于单片机资源有限和需求,我设置的最多被两个客户端连接,再来了新的客户端连接会把之前最旧的关闭,这也是为了网线拔掉或者不主动关闭的无效连接占用资源。
err_t client_connection_handler(void *arg, struct tcp_pcb *newpcb, err_t err)
{
OS_ERR os_err;
int client_index;
if (err!= ERR_OK)
{
return err;
}
// 获取互斥信号量,保护对tcp_server_pcbs数组的访问
OSSemPend(&tcp_server_pcbs_sem,0,OS_OPT_PEND_BLOCKING,0,&os_err); //请求信号量
if(tcp_server_pcbs[close_tcp]!=NULL){
tcp_close(tcp_server_pcbs[close_tcp]);
}
tcp_server_pcbs[close_tcp]=newpcb;
if(close_tcp==0){
close_tcp=1;
}else{
close_tcp=0;
}
// 设置接收、发送和错误处理的回调函数
tcp_recv(newpcb, client_recv_handler);
tcp_sent(newpcb, client_send_handler);
tcp_err(newpcb, client_err_handler);
OSSemPost(&tcp_server_pcbs_sem, OS_OPT_POST_1, &os_err);
return ERR_OK;
}
2.为每个连接设置接收回调函数,以便在有数据到达时进行处理,如果tcp客户端主动断开连接会走 else if (err == ERR_OK && p == NULL) 这里。
err_t client_recv_handler(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
OS_ERR os_err;
if (err == ERR_OK && p!= NULL)
{
// 将接收到的pbuf数据复制到本地缓冲区(这里简单示例,可优化)
char rx_buffer[RX_BUFFER_SIZE];
int copied_bytes = 0;
struct pbuf *q = p;
while (q!= NULL)
{
int bytes_to_copy = (q->len < (RX_BUFFER_SIZE – copied_bytes))? q->len : (RX_BUFFER_SIZE – copied_bytes);
memcpy(&rx_buffer[copied_bytes], q->payload, bytes_to_copy);
copied_bytes += bytes_to_copy;
q = q->next;
}
// 这里可以对接收到的数据进行处理,比如根据协议解析等,现在简单回显数据给客户端
tcp_write(tpcb, rx_buffer, copied_bytes, TCP_WRITE_FLAG_COPY);
tcp_output(tpcb);
// 释放接收到的pbuf内存
pbuf_free(p);
}
else if (err == ERR_OK && p == NULL)
{
OSSemPend(&tcp_server_pcbs_sem,0,OS_OPT_PEND_BLOCKING,0,&os_err); //请求信号量
// 客户端关闭了连接,正常处理
for (int i = 0; i < MAX_CONNECTIONS; i++)
{
if (tcp_server_pcbs[i] == tpcb)
{
tcp_server_pcbs[i] = NULL;
break;
}
}
OSSemPost(&tcp_server_pcbs_sem, OS_OPT_POST_1, &os_err);
tcp_close(tpcb);
}
else
{
// 出现错误情况,关闭连接
tcp_close(tpcb);
}
return ERR_OK;
}
3. 发送函数,这个我没用到,我只是被动返回消息。
err_t client_send_handler(void *arg, struct tcp_pcb *tpcb, u16_t len)
{
// 这里可以根据实际发送情况做一些后续处理,当前示例只是简单回显,无需额外操作
return ERR_OK;
}
4.错误处理,如果有超时的那种tcp连接,应该是会走这里释放TCP连接。
void client_err_handler(void *arg, err_t err)
{
struct tcp_pcb *tpcb = (struct tcp_pcb *)arg;
OS_ERR os_err;
// 获取互斥信号量,保护对tcp_server_pcbs数组的访问
OSSemPend(&tcp_server_pcbs_sem,0,OS_OPT_PEND_BLOCKING,0,&os_err); //请求信号量
for (int i = 0; i < MAX_CONNECTIONS; i++)
{
if (tcp_server_pcbs[i] == tpcb)
{
tcp_server_pcbs[i] = NULL;
break;
}
}
OSSemPost(&tcp_server_pcbs_sem, OS_OPT_POST_1, &os_err);
tcp_close(tpcb);
}
七、总结
通过以上步骤,我们可以在 STM32 上实现一个 TCP 服务器,与多个设备进行通信。在实际应用中,可以根据具体的需求进行进一步的优化和扩展,例如添加安全认证、数据加密、流量控制等功能。同时,还需要注意网络稳定性和可靠性,以确保通信的正常进行。
希望本文对大家在 STM32 上实现 TCP 服务器与多个设备通信有所帮助。如果有任何问题或建议,欢迎在评论区留言交流。
评论前必须登录!
注册