这段时间用STM32移植LwIP做语音传输。但是遇到一个问题困扰许久,在使用TCP方式做一个client去连接server,由于数据量比较大经常在连接一个多小时候就出现断线而
也ping不通。接下来我们看一下这个问题是怎么出现的和他的决绝方法(小白一枚,说错的地方还望指正哈 。。。。共同学习 。嘻嘻 ^_^ )。
额,还没有学操作系统,还生活在裸奔的年代。。。 client和server采用LwIP的Raw函数编写。连接过程采用短连接,即发送一次数据就请求断开。
我们先看一个client端的程序。
/*
**创建一个连接
*/
void client_init(void)
{
#define server_point 1080 //server端口号
struct tcp_pcb *Clipcb; //创建一个pcb控制块
struct ip_addr ipaddr; //IP
IP4_ADDR(&ipaddr,192,168,1,18); //server地址
Clipcb = tcp_new(); ;//分配一个控制块
if(Clipcb != NULL)
tcp_connect(Clipcb,&ipaddr,server_point,TcpCli_Connected); //创建连接 并注册连接成功的回调函数 Tcp_Cli_Connected
}
如果创建成功那么就进入回调函数
err_t TcpCli_Connected(void *arg, struct tcp_pcb *tpcb, err_t err)
{
tcp_write(tpcb,(void *)sound_buf[NT_USE_BUF_NUM],sizeof(sound_buf[ NT_USE_BUF_NUM]),0); //发送数据
tcp_close(tpcb); //关闭连接
return ERR_OK;
}
在主函数中调用发送数据
int main(void)
{
do somthing
if(发送条件成立)
{
client_init();
}
do something
好,那么问题来了。上面这种方法在数据量比较小的时候基本上不会出现什么问题,但是在数据量比较大的时候就会出现协议栈卡死。而出现上述问题的关键是TCP连接的时候如果要断开一个连接要经过4次握手,而我们在断开连接的时候只是简单的调用了 tcp_close( )函数,然而它具体的调用结果是什么了,我们没有进行检测。额这里就来看一下tcp_close函数。
err_t
tcp_close(struct tcp_pcb *pcb)
{
err_t err;
#if TCP_DEBUG
LWIP_DEBUGF(TCP_DEBUG, ("tcp_close: closing in "));
tcp_debug_print_state(pcb->state);
#endif /* TCP_DEBUG */
switch (pcb->state) { //判断pcb控制块的连接状态
case CLOSED:
err = ERR_OK;
TCP_RMV(&tcp_bound_pcbs, pcb); //将pcb控制块移除绑定链表
memp_free(MEMP_TCP_PCB, pcb); //释放pcb控制块
pcb = NULL;
break;
case LISTEN:
err = ERR_OK;
tcp_pcb_remove((struct tcp_pcb **)&tcp_listen_pcbs.pcbs, pcb);//将pcb控制块移除监听队列
memp_free(MEMP_TCP_PCB_LISTEN, pcb);
pcb = NULL;
break;
case SYN_SENT:
err = ERR_OK;
tcp_pcb_remove(&tcp_active_pcbs, pcb); //移除活动队列
memp_free(MEMP_TCP_PCB, pcb);
pcb = NULL;
snmp_inc_tcpattemptfails();
break;
case SYN_RCVD:
err = tcp_send_ctrl(pcb, TCP_FIN); //发送FIN关闭请求
if (err == ERR_OK) {
snmp_inc_tcpattemptfails(); //一个宏定义没有看到函数体
pcb->state = FIN_WAIT_1;
}
break;
case ESTABLISHED:
err = tcp_send_ctrl(pcb, TCP_FIN); //发送FIN关闭请求
if (err == ERR_OK) {
snmp_inc_tcpestabresets(); //宏
pcb->state = FIN_WAIT_1;
}
break;
case CLOSE_WAIT:
err = tcp_send_ctrl(pcb, TCP_FIN); //发送FIN
if (err == ERR_OK) {
snmp_inc_tcpestabresets();
pcb->state = LAST_ACK;
}
break;
default: //其他状态认为连接已经关闭
/* Has already been closed, do nothing. */
err = ERR_OK;
pcb = NULL;
break;
}
if (pcb != NULL && err == ERR_OK) {
tcp_output(pcb); //将没有发送的数据发送出去
}
return err;
}
在分析这段代码之前我们先看一下pcb->state 是什么。它是pcb结构体的一员,他主要使用来记录pcb控制块的状态他总共有11中状态,在这里我把他分为了两类(他的所有状态都可以在tcp.c 这个源文件中找到)
第一类、处于连接状态 或者说是pcb控制块没有释放的状态吧:
CLOSED:没有任何连接状态
LISTEN:侦听来自远方的TCP端口的连接请求
SYN-SENT:再发送连接请求后等待匹配的连接请求
SYN-RECEIVED:再收到和发送一个连接请求后等待对方对连接请求的确认
ESTABLISHED:代表一个打开的连接
CLOSE-WAIT:等待从本地用户发来的连接中断请求
第二类、连接关闭状态(tcp -> state)
FIN-WAIT-1:等待远程TCP连接中断请求,或先前的连接中断请求的确认
FIN-WAIT-2:从远程TCP等待连接中断请求
CLOSING:等待远程TCP对连接中断的确认
LAST-ACK:等待原来的发向远程TCP的连接中断请求的确认
TIME-WAIT:等待足够的时间以确保远程TCP接收到连接中断请求的确认
还有一个虽然不是pcb->state 但是当pcb == NULL的时候也是处在没有连接的状态
了解了上面的以后我么就能够判断一个pcb控制块处于什么状态了。那么我们开始的那种创建连接的问题出在哪里了,在我们创建了一个连接的时候要经过3次握手,那么可能在某一连接中client发送出SYN以后对方还没有回复ACK正式建立连接我们就有创建了一个新的pcb那么在数据量比较大的时候(这次做音频应该是32K*4bps吧)就有一次的创建了一个pcb控制块,或者是在我们的回掉函数中没有正确关闭pcb连接。那么这个连接韩式存在而我们后续却没有对他进行操作。如果在应用层一直没有再次调用tcp_close()函数那么这个连接将一直存在,可见是一个灾难性的结果,最终协议栈资源耗光、挂掉了。
那么我们的目标很明确就是在每次创建一个新的连接的时候确保上一个连接已经关闭,于是我们在创建连接以后就注册一个poll函数,在我们上面的TcpCli_Connected()函数调用tcp_poll()函数注册一个用户轮询函数client_poll()在他里面关闭连接,在这里注册就保证了是已经正常接通的连接,同时回调函数会隔一段时间去调用。这样如果我们没有正常关闭连接就会调用用这个poll来关闭,对应的如果正常关闭了那么就不会再调用这个回调函数。然后第二个注意的就是在每次创建连接的时候都要判断当前连接的状态。第三就是在server函数中也注册一个回调函数用来关闭连接。
Clipcb是一个全局变量
/*
**创建一个连接
*/
void client_init(void)
{
#define server_point 1080
struct ip_addr ipaddr;
if(Code_Void_BA < CodeBufAmount-1 )
{
IP4_ADDR(&ipaddr,192,168,1,180); //server IP
/* add according to https://lists.gnu.org/archive/html/lwip-users/2012-06/msg00031.html*/
/*确保上一次连接正确关闭*/
if( (Clipcb == NULL ) || (Clipcb->state ==FIN_WAIT_1) || (Clipcb->state == TIME_WAIT)||(Clipcb->state == CLOSED)
||(Clipcb->state == CLOSING)||(Clipcb->state == FIN_WAIT_2)||(Clipcb->state ==LAST_ACK))
{
if(Clipcb->state == CLOSED)
{
tcp_close(Clipcb);
}
Clipcb = tcp_new();
if(Clipcb == NULL)
{
printf("malloc pcb err!\n");
}
else
{
tcp_connect(Clipcb,&ipaddr,server_point,TcpCli_Connected);//创建连接
}
}
NT_USE_BUF_NUM = (NT_USE_BUF_NUM+1)%CodeBufAmount; //切换缓冲区
Code_Void_BA++;
}
}
成功连接后的回调函数,client_poll函数就是直接关闭连接就OK了
err_t TcpCli_Connected(void *arg, struct tcp_pcb *tpcb, err_t err)
{
static uint32_t ti = 0;
Clipcb = tpcb;
tcp_poll(Clipcb, client_poll, 0); //注册回调函数
tcp_write(tpcb,(void *)cod_buf[NT_USE_BUF_NUM],sizeof(cod_buf[ NT_USE_BUF_NUM]),0); //将数据填写到发送队列
if(ERR_OK != tcp_close(tpcb))
{
sta = 1; printf("close err!\n");
}
else sta = 0;
return ERR_OK;
}
KO
源代码http://download.csdn.net/detail/u014070258/8373623