本章对于TCP协议的内容不做过多的说明,具体TCP协议内容请看我博客发的TCP部分内容解析;
TCP层函数关系图:
这些函数都是系统编写好的函数,用户只需编写数据收发的回调函数;
设计逻辑图:
各函数体内容请看源码
注意:在TCP连接时,需要注册数据收发的回调函数(用户编写);
数据收发时使用的结构体:
//tcp服务器连接状态
enum tcp_client_states
{
ES_TCPCLIENT_NONE = 0, //没有连接
ES_TCPCLIENT_CONNECTED, //连接到服务器了
ES_TCPCLIENT_CLOSING, //关闭连接
};
//LWIP回调函数使用的结构体
struct tcp_client_struct
{
u8 state; //当前连接状
struct tcp_pcb *pcb; //指向当前的pcb
struct pbuf *p; //指向接收/或传输的pbuf
};
//lwIP tcp_recv()函数的回调函数
err_t tcp_client_recv(void *arg,struct tcp_pcb *tpcb,struct pbuf *p,err_t err)
{
u32 data_len = 0;
struct pbuf *q;
struct tcp_client_struct *es;
err_t ret_err;
LWIP_ASSERT("arg != NULL",arg != NULL);
es=(struct tcp_client_struct *)arg;
if(p==NULL)//如果从服务器接收到空的数据帧就关闭连接
{
es->state=ES_TCPCLIENT_CLOSING;//需要关闭TCP 连接了
es->p=p;
ret_err=ERR_OK;
}
else if(err!= ERR_OK)//当接收到一个非空的数据帧,但是err!=ERR_OK
{
if(p)pbuf_free(p);//释放接收pbuf
ret_err = err;
}
else if(es->state==ES_TCPCLIENT_CONNECTED) //当处于连接状态时
{
if(p!=NULL)//当处于连接状态并且接收到的数据不为空时
{
memset(tcp_client_recvbuf,0,TCP_CLIENT_RX_BUFSIZE); //数据接收缓冲区清零
for(q=p;q!=NULL;q=q->next) //遍历完整个pbuf链表
{
//判断要拷贝到TCP_CLIENT_RX_BUFSIZE中的数据是否大于TCP_CLIENT_RX_BUFSIZE的剩余空间,如果大于
//的话就只拷贝TCP_CLIENT_RX_BUFSIZE中剩余长度的数据,否则的话就拷贝所有的数据
if(q->len > (TCP_CLIENT_RX_BUFSIZE-data_len))
{
memcpy(tcp_client_recvbuf+data_len,q->payload,(TCP_CLIENT_RX_BUFSIZE-data_len));//拷贝数据
}
else
{
memcpy(tcp_client_recvbuf+data_len,q->payload,q->len);
}
data_len += q->len;
if(data_len > TCP_CLIENT_RX_BUFSIZE) break; //超出TCP客户端接收数组,跳出
}
tcp_client_flag|=1<<6; //标记接收到数据了
tcp_recved(tpcb,p->tot_len);//用于获取接收数据,通知LWIP可以获取更多数据
pbuf_free(p); //释放内存
ret_err=ERR_OK;
}
}
else //接收到数据但是连接已经关闭,
{
tcp_recved(tpcb,p->tot_len);//用于获取接收数据,通知LWIP可以获取更多数据
es->p=NULL;
pbuf_free(p); //释放内存
ret_err=ERR_OK;
}
return ret_err;
}
TCP数据的发送先为要发送数据申请内存pbuf_alloc();再将该数据拷贝到pbuf中,然后再调用tcp_client_senddata()函数进行发送;在tcp_client_senddata()函数中是使用系统函数tcp_write()将要发送的数据放到TCP发送缓存区队列中,由系统调用发送出去。
//lwIP tcp_poll的回调函数
err_t tcp_client_poll(void *arg, struct tcp_pcb *tpcb)
{
err_t ret_err;
struct tcp_client_struct *es;
es=(struct tcp_client_struct*)arg;
if(es!=NULL) //连接处于空闲可以发送数据
{
if(tcp_client_flag&(1<<7)) //判断是否有数据要发送
{
es->p=pbuf_alloc(PBUF_TRANSPORT, strlen((char*)tcp_client_sendbuf),PBUF_POOL); //申请内存
pbuf_take(es->p,(char*)tcp_client_sendbuf,strlen((char*)tcp_client_sendbuf)); //将tcp_client_sentbuf[]中的数据拷贝到es->p_tx中
tcp_client_senddata(tpcb,es);//将tcp_client_sentbuf[]里面复制给pbuf的数据发送出去
tcp_client_flag&=~(1<<7); //清除数据发送标志
if(es->p)pbuf_free(es->p); //释放内存
}
else if(es->state==ES_TCPCLIENT_CLOSING)
{
tcp_client_connection_close(tpcb,es);//关闭TCP连接
}
ret_err=ERR_OK;
}
else
{
tcp_abort(tpcb);//终止连接,删除pcb控制块
ret_err=ERR_ABRT;
}
return ret_err;
}
//此函数用来发送数据
void tcp_client_senddata(struct tcp_pcb *tpcb, struct tcp_client_struct * es)
{
struct pbuf *ptr;
err_t wr_err=ERR_OK;
while((wr_err==ERR_OK)&&(es->p)&&(es->p->len <= tcp_sndbuf(tpcb)))
{
ptr=es->p;
wr_err=tcp_write(tpcb,ptr->payload,ptr->len,1); //将要发送的数据加入到发送缓冲队列中
if(wr_err==ERR_OK)
{
es->p=ptr->next; //指向下一个pbuf
if(es->p)pbuf_ref(es->p); //pbuf的ref加一
pbuf_free(ptr); //释放ptr
}else if(wr_err==ERR_MEM)es->p=ptr;
tcp_output(tpcb); //将发送缓冲队列中的数据立即发送出去
}
}
TCP服务器的流程和上面的TCP客户端操作是差不多的;服务器只是多了绑定、监听、接受客户端的连接tcp_bind();tcp_listen();tcp_accept();
tcp_bind():绑定服务器的ip和端口号;
tcp_listen():监听是否有客户端连接;
tcp_accept():同意客户端的连接;