版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhaozhiyuan111/article/details/83088995
lwip_tcp Unbeatable client
项目测试记录:2018/10/16
保活机制的设置参考:
https://blog.csdn.net/allan0508/article/details/524686
(先自行了解保活机制)
LWIP中包括两个定时器函数:一个函数每 250 ms调用一次(快速定时器);另一个函数每500ms调用一次(慢速定时器)。
tcp_slowtmr函数的部分代码:
static unsigned char Close_Wait_keep_cnt_sent = 0;
/* Check if KEEPALIVE should be sent */
if(ip_get_option(pcb, SOF_KEEPALIVE) && //如果使用了保活机制
((pcb->state == ESTABLISHED) ||
(pcb->state == CLOSE_WAIT))) {
if((u32_t)(tcp_ticks - pcb->tmr) >
(pcb->keep_idle + TCP_KEEP_DUR(pcb)) / TCP_SLOW_INTERVAL) //2小时+9*75秒后断开连接
{
LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: KEEPALIVE timeout. Aborting connection to %"U16_F".%"U16_F".%"U16_F".%"U16_F".\n",
ip4_addr1_16(&pcb->remote_ip), ip4_addr2_16(&pcb->remote_ip),
ip4_addr3_16(&pcb->remote_ip), ip4_addr4_16(&pcb->remote_ip)));
++pcb_remove; //设置两个局部变量,后需删除控制块
++pcb_reset; //复位连接
}
else if((u32_t)(tcp_ticks - pcb->tmr) >
(pcb->keep_idle + pcb->keep_cnt_sent * TCP_KEEP_INTVL(pcb))
/ TCP_SLOW_INTERVAL)
{
tcp_keepalive(pcb); //发送保活探查报文
pcb->keep_cnt_sent++; //保活报文数加1
if(pcb->state == CLOSE_WAIT) Close_Wait_keep_cnt_sent++;
else Close_Wait_keep_cnt_sent = 0;
}
if(pcb->state == CLOSE_WAIT){
if(Close_Wait_keep_cnt_sent > pcb->keep_cnt) //close wait timeout for (pcb->keep_idle + TCP_KEEP_DUR(pcb))
{
++pcb_remove; //设置两个局部变量,后需删除控制块
++pcb_reset; //复位连接
}
}
}
保活机制(ip_get_option(pcb, SOF_KEEPALIVE))打开后,保证在ESTABLISHED和CLOSE_WAIT两状态下发生异常(ESTABLISHED状态的异常如网线断开,CLOSE_WAIT状态的异常如sever主动断开。),并发送完保活探查报文后(发送次数由pcb->keep_cnt决定),程序能够执行pcb_remove和pcb_reset的置位。
/* If the PCB should be removed, do it. */
if (pcb_remove) {
struct tcp_pcb *pcb2;
tcp_err_fn err_fn;
void *err_arg;
tcp_pcb_purge(pcb);
/* Remove PCB from tcp_active_pcbs list. */
if (prev != NULL) {
LWIP_ASSERT("tcp_slowtmr: middle tcp != tcp_active_pcbs", pcb != tcp_active_pcbs);
prev->next = pcb->next;
} else {
/* This PCB was the first. */
LWIP_ASSERT("tcp_slowtmr: first pcb == tcp_active_pcbs", tcp_active_pcbs == pcb);
tcp_active_pcbs = pcb->next;
}
if (pcb_reset) {
tcp_rst(pcb->snd_nxt, pcb->rcv_nxt, &pcb->local_ip, &pcb->remote_ip,
pcb->local_port, pcb->remote_port);
}
err_fn = pcb->errf; //错误回调函数
err_arg = pcb->callback_arg; //错误回调函数参数
pcb2 = pcb;
pcb = pcb->next;
memp_free(MEMP_TCP_PCB, pcb2);
tcp_active_pcbs_changed = 0;
TCP_EVENT_ERR(err_fn, err_arg, ERR_ABRT);
if (tcp_active_pcbs_changed) {
goto tcp_slowtmr_start;
}
}
pcb_remove置位后,上面的代码会生效,重点是在这里可以加载一个错误回调函数,我们可以在错误回调函数中处理异常。然而这个错误回调函数我们需要在客户端初始化的时候给它注册。
u_sTcp_pcb[i] = tcp_new();
tcp_bind(u_sTcp_pcb[i], &IPAddr, pConnectMesg->wSrcPort);
if(byCliServ == LWIP_CLIENT)
{
if(u_sTcp_pcb[i] != NULL)
ip_set_option(u_sTcp_pcb[i], SOF_KEEPALIVE);
tcp_connect(u_sTcp_pcb[i], &RemoteIP, pConnectMesg->wDstPort, pConnectMesg->pTcpConnected);
tcp_arg(u_sTcp_pcb[i], (void *)pConnectMesg); //指定网络错误回调函数的参数
tcp_err(u_sTcp_pcb[i], echo_client_conn_err); //注册网络错误回调函数
tcp_recv(u_sTcp_pcb[i], pConnectMesg->pTcpRecv);
return u_sTcp_pcb[i];
}
下面就该是错误回调函数的函数体了:
static void echo_client_conn_err(void *arg, err_t err)
{
int i = 0;
err_t ERR;
struct ip_addr SeverIP;
NETCONNECTMESG * pReconnectMesg = (NETCONNECTMESG *)arg;
if(err != ERR_ABRT) return;
IP4_ADDR(&SeverIP, pReconnectMesg->aDstIP[0], pReconnectMesg->aDstIP[1],
pReconnectMesg->aDstIP[2], pReconnectMesg->aDstIP[3]);
for(i = 0; i < LWIP_MAX_CONNECT; i++)
{
if(u_sTcp_pcb[i] != NULL)
{
ERR = tcp_close(u_sTcp_pcb[i]);
if(ERR == ERR_OK)
u_sTcp_pcb[i] = NULL;
}
if(u_sTcp_pcb[i] == NULL)
{
u_sTcp_pcb[i] = tcp_new();
if(u_sTcp_pcb[i] != NULL)
ip_set_option(u_sTcp_pcb[i], SOF_KEEPALIVE);
tcp_bind(u_sTcp_pcb[i], IP_ADDR_ANY, pReconnectMesg->wSrcPort);
tcp_connect(u_sTcp_pcb[i], &SeverIP, pReconnectMesg->wDstPort, pReconnectMesg->pTcpConnected);
tcp_arg(u_sTcp_pcb[i], pReconnectMesg);
tcp_err(u_sTcp_pcb[i], echo_client_conn_err);
tcp_recv(u_sTcp_pcb[i], pReconnectMesg->pTcpRecv);
return;
}
}
}
目前测试,client是打不死的小强。