STM32+FreeRTOS+LWIP TCP Server多端口并发通信实例

1. 问题的提出

在LWIP的TCP Server功能的应用中,如何创建多个TCP服务器端口同时与多台客户端设备通信,或者如何在同一个TCP服务器端口下创建多个连接同时与多台客户端通信?

2. 设计思路

  • 利用FreeRTOS的多任务调度机制,一个任务对应一个服务器端口,实现多端口并发通信。
  • 如果在同一个端口下收到多个客户端连接请求,就利用FreeRTOS的消息队列,把新的连接分配到其他的任务中。

3. 代码实现

本例程使用lwip的netconn结构来实现tcp server的功能,通过dhcp获取ip地址,再创建两个port,5001和5002,其中5001端口可以连接2个tcp client。也就是说,本例程实现了多个端口连接不同设备和单个端口连接多个设备的功能。

	ip address <--------->	netconn(5001) <-----> client1
			 		|						 |--> client2
					|
					|---->	netconn(5002) <-----> client3

5002端口的任务函数

static void tcp_server_task(void* pvParameters)
{
	char tcp_server_recvbuf[TCP_SERVER_RX_BUFSIZE];		//接收缓存区
	char tcp_server_sendbuf[] = "tcp server msg\r\n";	//发送缓存区
	unsigned int data_len = 0;
	struct pbuf *q;
	err_t err, recv_err;
	unsigned char remote_addr[4];
	struct netconn *conn, *newconn;
	struct netbuf *recvbuf;
	static ip_addr_t ipaddr;
	static u16_t port;
	
	LWIP_UNUSED_ARG(pvParameters);
	
	conn = netconn_new(NETCONN_TCP);		//创建一个新的连接
	netconn_bind(conn, IP_ADDR_ANY, 5002);	//绑定IP和端口
	netconn_listen(conn);					//开始监听conn
	netconn_set_recvtimeout(conn, 200);		//设置conn的接收超时
	
	while(1)
	{
		err = netconn_accept(conn, &newconn);	//在正在监听的连接conn上面接收新的连接newconn
												//这个函数本身是有等待超时的特性
		netconn_set_recvtimeout(newconn, 200);	//设置newconn的接收超时
		
		if(err == ERR_OK)	//如果newconn建立成功
		{
			netconn_getaddr(newconn, &ipaddr, &port, 0);
			remote_addr[3] = (u8_t)(ipaddr.addr >> 24);
			remote_addr[2] = (u8_t)(ipaddr.addr >> 16);
			remote_addr[1] = (u8_t)(ipaddr.addr >> 8);
			remote_addr[0] = (u8_t)(ipaddr.addr);
			printf("Dest IP: %d.%d.%d.%d  Port: %d\r\n", remote_addr[0],\
					remote_addr[1], remote_addr[2], remote_addr[3], port);
			
			while(1)
			{
				if(1)	//这里可以添加发送数据的标记位
				{
					err = netconn_write(newconn, tcp_server_sendbuf, \
							strlen(tcp_server_sendbuf), NETCONN_NOFLAG);
					if(err != ERR_OK)
						printf("tcp server send failed\r\n");
				}
				
				//接收newconn的数据,有等待超时
				if((recv_err = netconn_recv(newconn, &recvbuf)) == ERR_OK)
				{
					taskENTER_CRITICAL();//进入临界段
					memset(tcp_server_recvbuf, 0, TCP_SERVER_RX_BUFSIZE);
					for(q=recvbuf->p; q!=NULL; q=q->next)
					{
						if(q->len > (TCP_SERVER_RX_BUFSIZE-data_len))
						{
							memcpy(tcp_server_recvbuf+data_len, q->payload, \
									TCP_SERVER_RX_BUFSIZE-data_len);
						}
						else
						{
							memcpy(tcp_server_recvbuf+data_len, q->payload, q->len);
						}
						
						data_len += q->len;
						
						if(data_len > TCP_SERVER_RX_BUFSIZE)
							break;
					}
					taskEXIT_CRITICAL();//退出临界段
					data_len = 0;
					printf("%s", tcp_server_recvbuf);
					netbuf_delete(recvbuf);
				}
				//检测到newconn连接关闭,则断开
				else if(recv_err == ERR_CLSD)
				{
					netconn_close(newconn);
					netconn_delete(newconn);
					printf("tcp server disconnected\r\n");
					break;
				}
			}
		}
	}
}

5001端口分配连接的任务。收到连接请求就把新的netconn发送出去。

static void tcp_conn_distrib_task(void *pvParameters)
{
	struct netconn *conn = NULL;
	struct netconn *newconn = NULL;
	err_t err;
	
	conn = netconn_new(NETCONN_TCP);		//创建连接
	netconn_bind(conn, IP_ADDR_ANY, 5001);	//绑定IP和端口
	netconn_listen(conn);					//监听conn
	netconn_set_recvtimeout(conn, 200);		//设置conn超时时间
	
	while(1)
	{
		err = netconn_accept(conn, &newconn);	//接收newconn
		netconn_set_recvtimeout(newconn, 200);	//设置newconn的超时时间
		
		if(err == ERR_OK && newconn != last_newconn)
		{
			printf("queue send\r\n");
			xQueueSend(tcp_conn_distrib, &newconn, 0);	//检测到有新的连接就分配
		}
	}
}

5001端口的第一个netconn

static void tcp_conn_first_task(void *pvParameters)
{
	char tcp_server_sendbuf[] = "first tcp con msg\r\n";
	char tcp_server_recvbuf[TCP_SERVER_RX_BUFSIZE];
	err_t err, recv_err;
	struct netconn *first_conn;
	struct netbuf *recvbuf;
	struct pbuf *q;
	unsigned int data_len = 0;
	static ip_addr_t ipaddr;
	static u16_t port;
	unsigned char remote_addr[4];
	
	while(1)
	{
		//等待分配连接的消息队列,阻塞
		xQueueReceive(tcp_conn_distrib, &first_conn, portMAX_DELAY);
		
		netconn_getaddr(first_conn, &ipaddr, &port, 0);
		remote_addr[3] = (u8_t)(ipaddr.addr >> 24);
		remote_addr[2] = (u8_t)(ipaddr.addr >> 16);
		remote_addr[1] = (u8_t)(ipaddr.addr >> 8);
		remote_addr[0] = (u8_t)(ipaddr.addr);
		printf("Dest IP: %d.%d.%d.%d  Port: %d\r\n", remote_addr[0], \
				remote_addr[1], remote_addr[2], remote_addr[3], port);
		
		while(1)
		{
			if(1)	//这里可以添加发送数据的标记位
			{
				err = netconn_write(first_conn, tcp_server_sendbuf, \
							strlen(tcp_server_sendbuf), NETCONN_NOFLAG);
				if(err != ERR_OK)
					printf("first conn send failed\r\n");
			}
			
			if((recv_err = netconn_recv(first_conn, &recvbuf)) == ERR_OK)
			{
				taskENTER_CRITICAL();//进入临界段
				memset(tcp_server_recvbuf, 0, TCP_SERVER_RX_BUFSIZE);
				for(q=recvbuf->p; q!=NULL; q=q->next)
				{
					if(q->len > (TCP_SERVER_RX_BUFSIZE-data_len))
					{
						memcpy(tcp_server_recvbuf+data_len, q->payload, \
								TCP_SERVER_RX_BUFSIZE-data_len);
					}
					else
					{
						memcpy(tcp_server_recvbuf+data_len, q->payload, q->len);
					}
					
					data_len += q->len;
					
					if(data_len > TCP_SERVER_RX_BUFSIZE)
						break;
				}
				taskEXIT_CRITICAL();//退出临界段
				data_len = 0;
				printf("%s", tcp_server_recvbuf);
				netbuf_delete(recvbuf);
			}
			else if(recv_err == ERR_CLSD)
			{
				netconn_close(first_conn);
				netconn_delete(first_conn);
				printf("first conn disconnected\r\n");
				break;
			}
		}
	}
}

5001端口的第二个netconn,代码和上一个任务基本是一样的

static void tcp_conn_second_task(void *pvParameters)
{
	err_t err, recv_err;
	struct netconn *second_conn;
	struct netbuf *recvbuf;
	struct pbuf *q;
	unsigned int data_len = 0;
	static ip_addr_t ipaddr;
	static u16_t port;
	unsigned char remote_addr[4];
	char tcp_server_sendbuf[] = "second tcp con msg\r\n";
	char tcp_server_recvbuf[TCP_SERVER_RX_BUFSIZE];
	
	while(1)
	{
		//等待分配连接的消息队列,阻塞
		xQueueReceive(tcp_conn_distrib, &second_conn, portMAX_DELAY);
		
		netconn_getaddr(second_conn, &ipaddr, &port, 0);
		remote_addr[3] = (u8_t)(ipaddr.addr >> 24);
		remote_addr[2] = (u8_t)(ipaddr.addr >> 16);
		remote_addr[1] = (u8_t)(ipaddr.addr >> 8);
		remote_addr[0] = (u8_t)(ipaddr.addr);
		printf("Dest IP: %d.%d.%d.%d  Port: %d\r\n", remote_addr[0], \
				remote_addr[1], remote_addr[2], remote_addr[3], port);
		
		while(1)
		{
			if(1)	//这里可以添加发送数据的标记位
			{
				err = netconn_write(second_conn, tcp_server_sendbuf, \
							strlen(tcp_server_sendbuf), NETCONN_NOFLAG);
				if(err != ERR_OK)
						printf("second conn send failed\r\n");
			}
			
			if((recv_err = netconn_recv(second_conn, &recvbuf)) == ERR_OK)
			{
				taskENTER_CRITICAL();//进入临界段
				memset(tcp_server_recvbuf, 0, TCP_SERVER_RX_BUFSIZE);
				for(q=recvbuf->p; q!=NULL; q=q->next)
				{
					if(q->len > (TCP_SERVER_RX_BUFSIZE-data_len))
					{
						memcpy(tcp_server_recvbuf+data_len, q->payload, \
								TCP_SERVER_RX_BUFSIZE-data_len);
					}
					else
					{
						memcpy(tcp_server_recvbuf+data_len, q->payload, q->len);
					}
					
					data_len += q->len;
					
					if(data_len > TCP_SERVER_RX_BUFSIZE)
						break;
				}
				taskEXIT_CRITICAL();//退出临界段
				data_len = 0;
				printf("%s", tcp_server_recvbuf);
				netbuf_delete(recvbuf);
			}
			else if(recv_err == ERR_CLSD)
			{
				netconn_close(second_conn);
				netconn_delete(second_conn);
				printf("second conn disconnected\r\n");
				break;
			}
		}
	}
}

4. 参考

  • 野火电子《LwIP 应用开发实战指南》第15章 使用NETCONN变成
  • 正点原子《STM32F4 LWIP开发手册V3.0》第十章 NETCONN 编程接口 TCP 服务器实验

版权声明:本文为CSDN博主「万象君_1024」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_41428779/article/details/120885621

1. 问题的提出

在LWIP的TCP Server功能的应用中,如何创建多个TCP服务器端口同时与多台客户端设备通信,或者如何在同一个TCP服务器端口下创建多个连接同时与多台客户端通信?

2. 设计思路

  • 利用FreeRTOS的多任务调度机制,一个任务对应一个服务器端口,实现多端口并发通信。
  • 如果在同一个端口下收到多个客户端连接请求,就利用FreeRTOS的消息队列,把新的连接分配到其他的任务中。

3. 代码实现

本例程使用lwip的netconn结构来实现tcp server的功能,通过dhcp获取ip地址,再创建两个port,5001和5002,其中5001端口可以连接2个tcp client。也就是说,本例程实现了多个端口连接不同设备和单个端口连接多个设备的功能。

	ip address <--------->	netconn(5001) <-----> client1
			 		|						 |--> client2
					|
					|---->	netconn(5002) <-----> client3

5002端口的任务函数

static void tcp_server_task(void* pvParameters)
{
	char tcp_server_recvbuf[TCP_SERVER_RX_BUFSIZE];		//接收缓存区
	char tcp_server_sendbuf[] = "tcp server msg\r\n";	//发送缓存区
	unsigned int data_len = 0;
	struct pbuf *q;
	err_t err, recv_err;
	unsigned char remote_addr[4];
	struct netconn *conn, *newconn;
	struct netbuf *recvbuf;
	static ip_addr_t ipaddr;
	static u16_t port;
	
	LWIP_UNUSED_ARG(pvParameters);
	
	conn = netconn_new(NETCONN_TCP);		//创建一个新的连接
	netconn_bind(conn, IP_ADDR_ANY, 5002);	//绑定IP和端口
	netconn_listen(conn);					//开始监听conn
	netconn_set_recvtimeout(conn, 200);		//设置conn的接收超时
	
	while(1)
	{
		err = netconn_accept(conn, &newconn);	//在正在监听的连接conn上面接收新的连接newconn
												//这个函数本身是有等待超时的特性
		netconn_set_recvtimeout(newconn, 200);	//设置newconn的接收超时
		
		if(err == ERR_OK)	//如果newconn建立成功
		{
			netconn_getaddr(newconn, &ipaddr, &port, 0);
			remote_addr[3] = (u8_t)(ipaddr.addr >> 24);
			remote_addr[2] = (u8_t)(ipaddr.addr >> 16);
			remote_addr[1] = (u8_t)(ipaddr.addr >> 8);
			remote_addr[0] = (u8_t)(ipaddr.addr);
			printf("Dest IP: %d.%d.%d.%d  Port: %d\r\n", remote_addr[0],\
					remote_addr[1], remote_addr[2], remote_addr[3], port);
			
			while(1)
			{
				if(1)	//这里可以添加发送数据的标记位
				{
					err = netconn_write(newconn, tcp_server_sendbuf, \
							strlen(tcp_server_sendbuf), NETCONN_NOFLAG);
					if(err != ERR_OK)
						printf("tcp server send failed\r\n");
				}
				
				//接收newconn的数据,有等待超时
				if((recv_err = netconn_recv(newconn, &recvbuf)) == ERR_OK)
				{
					taskENTER_CRITICAL();//进入临界段
					memset(tcp_server_recvbuf, 0, TCP_SERVER_RX_BUFSIZE);
					for(q=recvbuf->p; q!=NULL; q=q->next)
					{
						if(q->len > (TCP_SERVER_RX_BUFSIZE-data_len))
						{
							memcpy(tcp_server_recvbuf+data_len, q->payload, \
									TCP_SERVER_RX_BUFSIZE-data_len);
						}
						else
						{
							memcpy(tcp_server_recvbuf+data_len, q->payload, q->len);
						}
						
						data_len += q->len;
						
						if(data_len > TCP_SERVER_RX_BUFSIZE)
							break;
					}
					taskEXIT_CRITICAL();//退出临界段
					data_len = 0;
					printf("%s", tcp_server_recvbuf);
					netbuf_delete(recvbuf);
				}
				//检测到newconn连接关闭,则断开
				else if(recv_err == ERR_CLSD)
				{
					netconn_close(newconn);
					netconn_delete(newconn);
					printf("tcp server disconnected\r\n");
					break;
				}
			}
		}
	}
}

5001端口分配连接的任务。收到连接请求就把新的netconn发送出去。

static void tcp_conn_distrib_task(void *pvParameters)
{
	struct netconn *conn = NULL;
	struct netconn *newconn = NULL;
	err_t err;
	
	conn = netconn_new(NETCONN_TCP);		//创建连接
	netconn_bind(conn, IP_ADDR_ANY, 5001);	//绑定IP和端口
	netconn_listen(conn);					//监听conn
	netconn_set_recvtimeout(conn, 200);		//设置conn超时时间
	
	while(1)
	{
		err = netconn_accept(conn, &newconn);	//接收newconn
		netconn_set_recvtimeout(newconn, 200);	//设置newconn的超时时间
		
		if(err == ERR_OK && newconn != last_newconn)
		{
			printf("queue send\r\n");
			xQueueSend(tcp_conn_distrib, &newconn, 0);	//检测到有新的连接就分配
		}
	}
}

5001端口的第一个netconn

static void tcp_conn_first_task(void *pvParameters)
{
	char tcp_server_sendbuf[] = "first tcp con msg\r\n";
	char tcp_server_recvbuf[TCP_SERVER_RX_BUFSIZE];
	err_t err, recv_err;
	struct netconn *first_conn;
	struct netbuf *recvbuf;
	struct pbuf *q;
	unsigned int data_len = 0;
	static ip_addr_t ipaddr;
	static u16_t port;
	unsigned char remote_addr[4];
	
	while(1)
	{
		//等待分配连接的消息队列,阻塞
		xQueueReceive(tcp_conn_distrib, &first_conn, portMAX_DELAY);
		
		netconn_getaddr(first_conn, &ipaddr, &port, 0);
		remote_addr[3] = (u8_t)(ipaddr.addr >> 24);
		remote_addr[2] = (u8_t)(ipaddr.addr >> 16);
		remote_addr[1] = (u8_t)(ipaddr.addr >> 8);
		remote_addr[0] = (u8_t)(ipaddr.addr);
		printf("Dest IP: %d.%d.%d.%d  Port: %d\r\n", remote_addr[0], \
				remote_addr[1], remote_addr[2], remote_addr[3], port);
		
		while(1)
		{
			if(1)	//这里可以添加发送数据的标记位
			{
				err = netconn_write(first_conn, tcp_server_sendbuf, \
							strlen(tcp_server_sendbuf), NETCONN_NOFLAG);
				if(err != ERR_OK)
					printf("first conn send failed\r\n");
			}
			
			if((recv_err = netconn_recv(first_conn, &recvbuf)) == ERR_OK)
			{
				taskENTER_CRITICAL();//进入临界段
				memset(tcp_server_recvbuf, 0, TCP_SERVER_RX_BUFSIZE);
				for(q=recvbuf->p; q!=NULL; q=q->next)
				{
					if(q->len > (TCP_SERVER_RX_BUFSIZE-data_len))
					{
						memcpy(tcp_server_recvbuf+data_len, q->payload, \
								TCP_SERVER_RX_BUFSIZE-data_len);
					}
					else
					{
						memcpy(tcp_server_recvbuf+data_len, q->payload, q->len);
					}
					
					data_len += q->len;
					
					if(data_len > TCP_SERVER_RX_BUFSIZE)
						break;
				}
				taskEXIT_CRITICAL();//退出临界段
				data_len = 0;
				printf("%s", tcp_server_recvbuf);
				netbuf_delete(recvbuf);
			}
			else if(recv_err == ERR_CLSD)
			{
				netconn_close(first_conn);
				netconn_delete(first_conn);
				printf("first conn disconnected\r\n");
				break;
			}
		}
	}
}

5001端口的第二个netconn,代码和上一个任务基本是一样的

static void tcp_conn_second_task(void *pvParameters)
{
	err_t err, recv_err;
	struct netconn *second_conn;
	struct netbuf *recvbuf;
	struct pbuf *q;
	unsigned int data_len = 0;
	static ip_addr_t ipaddr;
	static u16_t port;
	unsigned char remote_addr[4];
	char tcp_server_sendbuf[] = "second tcp con msg\r\n";
	char tcp_server_recvbuf[TCP_SERVER_RX_BUFSIZE];
	
	while(1)
	{
		//等待分配连接的消息队列,阻塞
		xQueueReceive(tcp_conn_distrib, &second_conn, portMAX_DELAY);
		
		netconn_getaddr(second_conn, &ipaddr, &port, 0);
		remote_addr[3] = (u8_t)(ipaddr.addr >> 24);
		remote_addr[2] = (u8_t)(ipaddr.addr >> 16);
		remote_addr[1] = (u8_t)(ipaddr.addr >> 8);
		remote_addr[0] = (u8_t)(ipaddr.addr);
		printf("Dest IP: %d.%d.%d.%d  Port: %d\r\n", remote_addr[0], \
				remote_addr[1], remote_addr[2], remote_addr[3], port);
		
		while(1)
		{
			if(1)	//这里可以添加发送数据的标记位
			{
				err = netconn_write(second_conn, tcp_server_sendbuf, \
							strlen(tcp_server_sendbuf), NETCONN_NOFLAG);
				if(err != ERR_OK)
						printf("second conn send failed\r\n");
			}
			
			if((recv_err = netconn_recv(second_conn, &recvbuf)) == ERR_OK)
			{
				taskENTER_CRITICAL();//进入临界段
				memset(tcp_server_recvbuf, 0, TCP_SERVER_RX_BUFSIZE);
				for(q=recvbuf->p; q!=NULL; q=q->next)
				{
					if(q->len > (TCP_SERVER_RX_BUFSIZE-data_len))
					{
						memcpy(tcp_server_recvbuf+data_len, q->payload, \
								TCP_SERVER_RX_BUFSIZE-data_len);
					}
					else
					{
						memcpy(tcp_server_recvbuf+data_len, q->payload, q->len);
					}
					
					data_len += q->len;
					
					if(data_len > TCP_SERVER_RX_BUFSIZE)
						break;
				}
				taskEXIT_CRITICAL();//退出临界段
				data_len = 0;
				printf("%s", tcp_server_recvbuf);
				netbuf_delete(recvbuf);
			}
			else if(recv_err == ERR_CLSD)
			{
				netconn_close(second_conn);
				netconn_delete(second_conn);
				printf("second conn disconnected\r\n");
				break;
			}
		}
	}
}

4. 参考

  • 野火电子《LwIP 应用开发实战指南》第15章 使用NETCONN变成
  • 正点原子《STM32F4 LWIP开发手册V3.0》第十章 NETCONN 编程接口 TCP 服务器实验

版权声明:本文为CSDN博主「万象君_1024」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_41428779/article/details/120885621

生成海报
点赞 0

万象君_1024

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

STM32+FreeRTOS+LWIP TCP Server多端口并发通信实例

1. 问题的提出 在LWIP的TCP Server功能的应用中,如何创建多个TCP服务器端口同时与多台客户端设备通信,或者如何在同一个TCP服务器端口下创建多个连接同时与多台客户端通信? 2. 设计

W5500+STM32F103C8T6进行TCP通信(modbus)

一、W5500以太网模块介绍 Niren_W5500模块是一款基于WIZnet W5500芯片的以太网模块,是泥人电子继 Niren_W5100模块后设计的一块性能更好、性价比更高的以太网模块。模块集成硬件化TCP/IP协议:内