最近本人想用开发板来做服务器,所以就想到这个方法。对于写pc端服务器的童鞋来说,这应该是件很容易的事情,所以,这里主要分为两种实现方法:
第一种:在stm32f4开发板实现,基于lwip
硬件:正点原子stm32f407开发板(带网络功能)
lan芯片:lan8720
系统:rt-thread
第二种:在Python上实现
第一种:基于stm32f407 + lan8720 实现多个客户端连接的TCP服务器
主要在官方的例子上进行修改,源文件为:examples\network\tcpserver.c
官方的程序主要是支持单路TCP客户端的连接。现在我们来修改原来的程序,暂且先扩大的支持的连接数为10个吧
首先更改需求前,我们应该要想清楚应该怎么去做这件事情。对我来说的话,线程是最好的解决方式。每来一个链接,我就增加一个线程来处理这个连接。一旦这个客户端出现异常或者是关闭,则立即释放该线程。这里的话,就涉及到一个内存的问题,因为本身单片机的空间就不大,所以就不适合用于处理很多个的连接。在该实验中,只验证了10个客户端的请求,如果感兴趣的童鞋,也可以试试更多的。
大致的思路如下:
先建立一个TCP服务器,然后等待新的TCP客户端连接,超时10S
当有新的TCP客户端请求连接时,先判断是否超过最大的连接数
符合要求时,再往下判断当前的conn_id是否被占用,这里是起了一个数组来判断,当connected[conn_id] = -1时,代表这个位置是可以空闲的。
最后,创建一个线程来处理该连接的请求。异常,则释放该资源。
需要注意的是:tcp_server的线程空间为2K,每个客户端的线程空间为1K,因为客户端的线程是在堆空间的,所以不占用tcp_server的空间。
修改的代码如下:
#include <rtthread.h>
#include <string.h>
//#if !defined(SAL_USING_POSIX)
//#error "Please enable SAL_USING_POSIX!"
//#else
//#include <sys/time.h>
//#include <sys/select.h>
//#endif
#include <sys/socket.h> /* 使用BSD socket,需要包含socket.h头文件 */
#include "netdb.h"
#define DEBUG_TCP_SERVER
#define DBG_ENABLE
#define DBG_SECTION_NAME "TCP"
#ifdef DEBUG_TCP_SERVER
#define DBG_LEVEL DBG_LOG
#else
#define DBG_LEVEL DBG_INFO /* DBG_ERROR */
#endif
#define DBG_COLOR
#include <rtdbg.h>
#define BUFSZ (1024)
#define MAX_CONNECT_NUM 10 //最大连接数
static int started = 0;
static int is_running = 0;
static int port = 5000;
static const char send_data[] = "This is TCP Server from RT-Thread."; /* 发送用到的数据 */
int ret;
static char *recv_data; /* 用于接收的指针,后面会做一次动态分配以请求可用内存 */
static int sock, bytes_received;
static int connected[MAX_CONNECT_NUM] = {0};//-1,说明该链接为空,可以执行连接
static struct sockaddr_in server_addr;
static struct sockaddr_in client_addr[MAX_CONNECT_NUM];
static struct timeval timeout;
static fd_set readset[MAX_CONNECT_NUM], readset_c[MAX_CONNECT_NUM];
static rt_uint8_t current_connnect_id = 0;
static rt_uint8_t total_connect_count = 0;
static void tcp_server_handler_msg(void *parameter)
{
rt_uint8_t id = *(rt_uint8_t *)parameter;
struct timeval client_timeout;
LOG_I("\tcp_server_handler_msg : id = %d...\n", id);
client_timeout.tv_sec = 0;
client_timeout.tv_usec = 1000 * 50;
while(1)
{
FD_ZERO(&readset_c);
FD_SET(connected[id], &readset_c[id]);
/* Wait for read or write */
if (select(connected[id] + 1, &readset_c[id], RT_NULL, RT_NULL, &client_timeout) == 0)
{
rt_thread_delay(10);
continue;
}
/* 从connected socket中接收数据,接收buffer是1024大小,但并不一定能够收到1024大小的数据 */
bytes_received = recv(connected[id], recv_data, BUFSZ, 0);
if (bytes_received < 0)
{
LOG_E("Received error, close the connect.");
closesocket(connected[id]);
connected[id] = -1;
total_connect_count --;
break;
}
else if (bytes_received == 0)
{
/* 打印recv函数返回值为0的警告信息 */
LOG_W("Received warning, recv function return 0.");
continue;
}
else
{
/* 有接收到数据,把末端清零 */
recv_data[bytes_received] = '\0';
if (strcmp(recv_data, "q") == 0 || strcmp(recv_data, "Q") == 0)
{
/* 如果是首字母是q或Q,关闭这个连接 */
LOG_I("Got a 'q' or 'Q', close the connect.");
closesocket(connected[id]);
connected[id] = -1;
total_connect_count --;
break;
}
else if (strcmp(recv_data, "exit") == 0)
{
/* 如果接收的是exit,则关闭整个服务端 */
closesocket(connected[id]);
connected[id] = -1;
total_connect_count --;
}
else
{
/* 在控制终端显示收到的数据 */
LOG_D("Received (%s:%d) data = %s----[id=%d]----", inet_ntoa(client_addr[id].sin_addr),
ntohs(client_addr[id].sin_port),
recv_data,id);
}
/* 发送数据到connected socket */
ret = send(connected[id], send_data, rt_strlen(send_data), 0);
if (ret < 0)
{
LOG_E("send error, close the connect.");
closesocket(connected[id]);
connected[id] = -1;
total_connect_count --;
break;
}
else if (ret == 0)
{
/* 打印send函数返回值为0的警告信息 */
LOG_W("Send warning, send function return 0.");
}
}
}
}
static void tcpserv(void *arg)
{
socklen_t sin_size = sizeof(struct sockaddr_in);
recv_data = rt_malloc(BUFSZ + 1); /* 分配接收用的数据缓冲 */
if (recv_data == RT_NULL)
{
LOG_E("No memory");
return;
}
rt_memset(connected,-1,sizeof(connected));//-1,说明该链接为空,可以执行连接
/* 一个socket在使用前,需要预先创建出来,指定SOCK_STREAM为TCP的socket */
if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
{
LOG_E("Create socket error");
goto __exit;
}
/* 初始化服务端地址 */
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port); /* 服务端工作的端口 */
server_addr.sin_addr.s_addr = INADDR_ANY;
rt_memset(&(server_addr.sin_zero), 0x0, sizeof(server_addr.sin_zero));
/* 绑定socket到服务端地址 */
if (bind(sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
{
LOG_E("Unable to bind");
goto __exit;
}
/* 在socket上进行监听 */
if (listen(sock, MAX_CONNECT_NUM) == -1)
{
LOG_E("Listen error");
goto __exit;
}
LOG_I("\nTCPServer Waiting for client on port %d...\n", port);
timeout.tv_sec = 10;
timeout.tv_usec = 0;
while (1)
{
if(total_connect_count < MAX_CONNECT_NUM)
{
while(connected[current_connnect_id] != -1)
{
LOG_I("***********the current link route status is busy! find next linker route****************\r\n");
current_connnect_id = (current_connnect_id + 1) % MAX_CONNECT_NUM;
}
FD_ZERO(&readset);
FD_SET(sock, &readset[current_connnect_id]);
LOG_I("Waiting for a new connection,current_connnect_id=%d...",current_connnect_id);
/* Wait for read or write */
if (select(sock + 1, &readset[current_connnect_id], RT_NULL, RT_NULL, &timeout) == 0)
{
rt_thread_delay(10);
continue;
}
/* 接受一个客户端连接socket的请求,这个函数调用是阻塞式的 */
connected[current_connnect_id] = accept(sock, (struct sockaddr *)&client_addr[current_connnect_id], &sin_size);
/* 返回的是连接成功的socket */
if (connected[current_connnect_id] < 0)
{
LOG_E("accept connection failed! errno = %d", errno);
continue;
}
/* 接受返回的client_addr指向了客户端的地址信息 */
LOG_I("I got a connection from (%s , %d)\n",
inet_ntoa(client_addr[current_connnect_id].sin_addr), ntohs(client_addr[current_connnect_id].sin_port));
char thread_name[8] = {0};
sprintf((const char *)thread_name,"tsh_%d",current_connnect_id);
rt_thread_t tid = rt_thread_create(thread_name,//"tcp_serv_handler",
tcp_server_handler_msg,
¤t_connnect_id,//直接传地址
1024,
12,//优先级比原来的高
20);
if (tid != RT_NULL)
{
rt_thread_startup(tid);
}
current_connnect_id = (current_connnect_id + 1) % MAX_CONNECT_NUM;
total_connect_count ++;
}
else
{
LOG_I("max num over\r\n");
rt_thread_delay(100);
}
}
__exit:
return;
}
static void usage(void)
{
rt_kprintf("Usage: tcpserver -p <port>\n");
rt_kprintf(" tcpserver --stop\n");
rt_kprintf(" tcpserver --help\n");
rt_kprintf("\n");
rt_kprintf("Miscellaneous:\n");
rt_kprintf(" -p Specify the host port number\n");
rt_kprintf(" --stop Stop tcpserver program\n");
rt_kprintf(" --help Print help information\n");
}
static void tcpserver_test(int argc, char** argv)
{
rt_thread_t tid;
if (argc == 1 || argc > 3)
{
LOG_I("Please check the command you entered!\n");
goto __usage;
}
else
{
if (rt_strcmp(argv[1], "--help") == 0)
{
goto __usage;
}
else if (rt_strcmp(argv[1], "--stop") == 0)
{
is_running = 0;
return;
}
else if (rt_strcmp(argv[1], "-p") == 0)
{
if (started)
{
LOG_I("The tcpclient has started!");
LOG_I("Please stop tcpclient firstly, by: tcpclient --stop");
return;
}
port = atoi(argv[2]);
}
else
{
goto __usage;
}
}
tid = rt_thread_create("tcp_serv",
tcpserv,
RT_NULL,
2048,
RT_THREAD_PRIORITY_MAX - 1,
20);
if (tid != RT_NULL)
{
rt_thread_startup(tid);
}
return;
__usage:
usage();
}
#ifdef RT_USING_FINSH
MSH_CMD_EXPORT_ALIAS(tcpserver_test, tcpserver,
Start a tcp server. Help: tcpserver --help);
#endif
测试步骤如下:
1.烧写代码,重启后,通过串口启动TCP服务器
2.打开TCP客户端工具,连接TCP服务器
3.客户端发送消息到服务器
第二种方法实现:Python 实现多个客户端连接的TCP服务器
使用python实现,就不需要这么麻烦了。直接导入socket模块即可
具体代码如下:
# -*- coding: UTF-8 -*-
import socket #导入socket包
def tcp_server_create(port):
print("tcp_server")
tcp_server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 绑定端口
tcp_server.bind(("",port))
tcp_server.listen(128)
client_addr_list = []
tcp_server.setblocking(False)
while True:
try:
new_client,new_client_addr = tcp_server.accept()
#new_client_addr[0] = ip
#new_client_addr[1] = port
print("Connected with %s:%s" % (new_client_addr[0],str(new_client_addr[1])))
except Exception as Ex:
pass
else:
new_client.setblocking(False)
client_addr_list.append(new_client)#将连接添加到列表中
for client_id in client_addr_list:
try:
new_client_content=client_id.recv(1024)
except Exception as Ex:
pass
else:
if new_client_content:
print("tcp cleint send data:%s" % (new_client_content))
client_id.send(str("hello,the message from tcp_server").encode("utf-8"))
else:
client_addr_list.remove(client_id)
client_id.close()
def main():
tcp_server_create(2222)
if __name__ == "__main__":
main()
测试结果如下: