用过正点原子LWIP服务器例程开发的朋友可能知道,例程的设计是只支持一个客户端连接的,但实际应用中往往需要用到多客户端连接。下面是在正点原子扩展例程 网络实验14 NETCONN_TCP 服务器(UCOSIII版本) 的基础上进行修改,实现多客户端连接的一个方法。
1、TCP服务器创建过程
建立一个TCP服务器需要经过
创建连接
conn=netconn_new(NETCONN_TCP); //创建一个TCP链接
绑定端口号
netconn_bind(conn,IP_ADDR_ANY,TCP_SERVER_PORT); //绑定端口 8088号端口
监听
netconn_listen(conn); //进入监听模式
接收连接请求
err = netconn_accept(conn,&newconn); //接收连接请求
等步骤。而例程里将这些步骤都放在了同一个任务线程里面去操作,一旦接收到连接就进入while (1) 死循环里传输数据,这当然会限制连接。要实现多客户端连接,那就得把这些步骤分开来操作。
2、多客户端连接设计思路
我们可以分成三个任务线程来实现多客户端的连接。第一个用来创建TCP服务器以及监听接收连接请求;第二个用来处理连接成功之后的数据传输以及断开连接等操作;另外一个任务专门用来创建第二个任务线程,这是为了方便内存以及连接数量和状态的管理。
3、实现代码
首先定义一个结构体方便管理客户端
#define CLIENTMAX 20
typedef struct
{
struct netconn *conn;
OS_TCB *clientTCB;
CPU_STK *clientSTK;
u8 num;
}tcp_client;
typedef struct
{
tcp_client *client[CLIENTMAX];
u8 state[CLIENTMAX];
}client_ad;
client_ad clientad;
然后是第一个任务线程:创建TCP服务器
void svr_task(void *arg)
{
u8 port=8888;
OS_ERR oserr;
struct netconn *conn,*newconn;
conn = netconn_new(NETCONN_TCP);
netconn_bind(conn,IP_ADDR_ANY,port);
netconn_listen(conn);
conn->recv_timeout = 5;
while(1)
{
if(netconn_accept(conn,&newconn) == ERR_OK)
{
newconn->recv_timeout = 5;
if(client_init((void *)newconn) != ERR_OK)
{
netconn_close(newconn);
netconn_delete(newconn);
}
}
OSTimeDlyHMSM(0,0,0,5,0,&oserr);
}
}
第二个任务线程:创建客户端 client_init()
err_t client_init(void *arg)
{
u8 clientnum;
OS_ERR err;
tcp_client *client;
CPU_SR_ALLOC();
client=(tcp_client*)mymalloc(SRAMDTCM,16);
if(client == NULL) return ERR_MEM;
client->conn = (struct netconn *)arg;
client->clientTCB=(OS_TCB *)mymalloc(SRAMDTCM,196);
if(client->clientTCB == NULL)
{
myfree(SRAMDTCM, (void * )client);
return ERR_MEM;
}
client->clientSTK=(CPU_STK*)mymalloc(SRAMDTCM,1024);
if(client->clientSTK == NULL)
{
myfree(SRAMDTCM, (void * )(client->clientTCB));
myfree(SRAMDTCM, (void * )client);
return ERR_MEM;
}
for(clientnum=1;clientnum<CLIENTMAX;clientnum++)
{
if(clientad.state[clientnum]==0)
{
client->num=clientnum;
clientad.client[clientnum]=client;
break;
}
}
CPU_CRITICAL_ENTER();
OSTaskCreate((OS_TCB * )(client->clientTCB),
(CPU_CHAR * )"tcp_Server task",
(OS_TASK_PTR )tcp_server_thread,
(void * )client,
(OS_PRIO )10,
(CPU_STK * )(client->clientSTK),
(CPU_STK_SIZE)256/10,
(CPU_STK_SIZE)256,
(OS_MSG_QTY )0,
(OS_TICK )0,
(void * )0,
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
(OS_ERR * )&err);
CPU_CRITICAL_EXIT();
client_count++;
if(err != OS_ERR_NONE)
{
client_count--;
myfree(SRAMDTCM, (void * )(client->clientTCB));
myfree(SRAMDTCM, (void * )(client->clientSTK));
myfree(SRAMDTCM, (void * )client);
return ERR_RTE;
}
clientad.state[clientnum]=1;
return ERR_OK;
}
这个任务开头部分可能看着有些复杂,但其实就是给一个变量申请内存以及申请失败的一些处理,应该还是不难理解的。
最后一个任务线程:客户端线程
static void tcp_server_thread(void *arg)
{
u8 *tcp_server_recvbuf;
u8 *tcp_server_sendbuf;
CPU_SR_ALLOC();
u8 online_check=0;
u32 readval;
OS_ERR oserr;
u32 data_len = 0;
static u16_t port;
struct pbuf *q;
err_t recv_err;
static ip_addr_t ip;
tcp_client *client = (tcp_client *)arg;
struct netbuf *recvbuf;
netconn_getaddr(client->conn,&ip,&port,0);
printf("%d号客户端 %d.%d.%d.%d 端口号 %d 已连接\r\n",
client->num,(u8)(ip.addr>>0),(u8)(ip.addr>>8),
(u8)(ip.addr>>16),(u8)(ip.addr>>24),port);
keep_alive=0;
tcp_server_recvbuf=mymalloc(SRAMIN,TCP_SERVER_RX_BUFSIZE);
tcp_server_sendbuf=mymalloc(SRAMIN,TCP_SERVER_TX_BUFSIZE);
while(1)
{
if((recv_err = netconn_recv(client->conn,&recvbuf)) == ERR_OK)
{
OS_CRITICAL_ENTER();
memset(tcp_server_recvbuf,0,TCP_SERVER_RX_BUFSIZE);
data_len = 0;
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;
}
OS_CRITICAL_EXIT();
recv_err = netconn_write(client->conn ,tcp_server_sendbuf,data_len,NETCONN_COPY);
data_len=0;
netbuf_delete(recvbuf);
}
else if(recv_err == ERR_CLSD||recv_err==ERR_RST)
break;
LAN8720_ReadPHY(LAN8720_BSR,&readval);
if((readval&LAN8720_BSR_LINK_STATUS)==0)
online_check = 1;
else
online_check = 0;
if(online_check)
break;
}
if(clientad.state[client->num]==1)
{
client_count--;
netconn_close(client->conn);
netconn_delete(client->conn);
clientad.state[client->num]=0;
printf("%d号客户端 %d.%d.%d.%d 端口号 %d 已断开\r\n",
client->num,(u8)(ip.addr>>0),(u8)(ip.addr>>8),
(u8)(ip.addr>>16),(u8)(ip.addr>>24),port);
myfree(SRAMIN, tcp_server_recvbuf);
myfree(SRAMIN, tcp_server_sendbuf);
myfree(SRAMDTCM, (void * )(client->clientTCB));
myfree(SRAMDTCM, (void * )(client->clientSTK));
myfree(SRAMDTCM, (void * )client);
OSTaskDel(NULL,&oserr);
}
}
数据的收发和处理都在这个线程里操作,接收到一个连接请求就创建一个客户端连接,每个线程之间相互独立,不相干扰!断开连接也能及时释放内存!
整理了一个Demo,有需要的可以自行下载!
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)