RAW网络编程
LWIP提供了三种的可以被应用程序直接调用的接口API:
(1) 低水平的,基于内核/回调函数的API(后面称 RAW API) 适用于数据量不大,没有os的MCU
(2) 高水平的,连续的API(后面称LwIP API) 这种方式最常用,需要os支持,适用于传输数据量大的场合
(3) BSD风格的套接字API(后面称BSD socket) 目前还不太稳定
本文介绍的是处于传输层的udp和tcp。两者的区别和各自使用的场合这里就不再赘叙
TCP/IP网络四层模型
1.RAW_UDP
(1).udp简介
端口号表示发送和接收进程, UDP 协议使用端口号为不同的应用保留各自的数据传输通,UDP 和 TCP 协议都是采用端口号对同一时刻内多项应用同时发送和接收数据,而数据接收方则通过目标端口接收数据。有的网络应用只能使用预先为其预留或注册的静态端口;而另外一些网络应用则可以使用未被注册的动态端口。因为 UDP 报头使用两个字节存放端口号,所以端口号的有效范围是从 0 到 65535。一般来说,大于 49151 的端口号都代表动态端口。据报的长度是指包括报头和数据部分在内的总字节数。因为报头的长度是固定的,所以该域主要被用来计算可变长度的数据部分(又称为数据负载)。数据报的最大长度根据操作环境的不同而各异。从理论上说,包含报头在内的数据报的最大长度为 65535 字节。UDP 协议使用报头中的校验和来保证数据的安全 。
在LWIP中有关处理的函数关系如下:
(2)udp整个通信过程如下图:
(3)其实通讯过程很简单,下面看一下代码
① 接收函数就是遍历整个pbuf链表,将pbuf的数据存储区域payload里面的数据memcpy到数组里
//UDP回调函数
void udp_demo_recv(void *arg,struct udp_pcb *upcb,struct pbuf *p, struct ip4_addr *addr,u16_t port)
{
u32 data_len = ;
struct pbuf *q;
if(p!=NULL) //接收到不为空的数据时
{
memset(udp_demo_recvbuf,,UDP_DEMO_RX_BUFSIZE); //数据接收缓冲区清零
for(q=p;q!=NULL;q=q->next) //遍历完整个pbuf链表
{
//判断要拷贝到UDP_DEMO_RX_BUFSIZE中的数据是否大于UDP_DEMO_RX_BUFSIZE的剩余空间,如果大于
//的话就只拷贝UDP_DEMO_RX_BUFSIZE中剩余长度的数据,否则的话就拷贝所有的数据
if(q->len > (UDP_DEMO_RX_BUFSIZE-data_len)) memcpy(udp_demo_recvbuf+data_len,q->payload,(UDP_DEMO_RX_BUFSIZE-data_len));//拷贝数据
else memcpy(udp_demo_recvbuf+data_len,q->payload,q->len);
data_len += q->len;
if(data_len > UDP_DEMO_RX_BUFSIZE) break; //超出TCP客户端接收数组,跳出
}
upcb->remote_ip=*addr; //记录远程主机的IP地址
upcb->remote_port=port; //记录远程主机的端口号
udp_demo_flag|=<<; //标记接收到数据了
pbuf_free(p);//释放内存
}
else
{
debug("connect has been break !!!!\r\n");
}
}
udp_demo_recv
② 发送函数首先为pbuf申请内存,然后将要发送的内容复制到pbuf结构体中,最后发送出去
//UDP服务器发送数据
void udp_demo_senddata(struct udp_pcb *upcb)
{
struct pbuf *ptr;
ptr=pbuf_alloc(PBUF_TRANSPORT,strlen((char*)udp_demo_sendbuf),PBUF_POOL); //申请内存
if(ptr)
{
pbuf_take(ptr,(char*)udp_demo_sendbuf,strlen((char*)udp_demo_sendbuf)); //将tcp_demo_sendbuf中的数据打包进pbuf结构中
udp_send(upcb,ptr); //udp发送数据
pbuf_free(ptr);//释放内存
}
}
udp_demo_senddata
③ 关闭连接就是先断开连接,然后删除pcb控制块
void udp_demo_connection_close(struct udp_pcb *upcb)
{
udp_disconnect(upcb);
udp_remove(upcb); //断开UDP连接
udp_demo_flag &= ~(<<); //标记连接断开
debug("connect close!!!\r\n");
}
udp_demo_connection_close
④ 测试函数就是按照上图的流程,很简单,不要忘记在测试函数循环中,调用LWIP内核需要定时处理的函数 sys_check_timeouts();
//UDP 测试全局状态标记变量
//bit7:没有用到
//bit6:0,没有收到数据;1,收到数据了.
//bit5:0,没有连接上;1,连接上了.
//bit4~0:保留
u8 udp_demo_flag = ;
struct ip4_addr rmtipaddr; //远端ip地址
extern uint8_t REMOTE_IP_ADDRESS[];
void udp_demo_test(void)
{
u8 *tbuf;
err_t err;
u8 res;
struct udp_pcb *udppcb; //定义一个TCP服务器控制块
struct ip4_addr rmtipaddr; //远端ip地址 tbuf=malloc(); //申请内存
if(tbuf==NULL)return; udppcb=udp_new();
if(udppcb)
{
IP4_ADDR(&rmtipaddr,REMOTE_IP_ADDRESS[],REMOTE_IP_ADDRESS[],REMOTE_IP_ADDRESS[],REMOTE_IP_ADDRESS[]);
err=udp_connect(udppcb,&rmtipaddr,UDP_DEMO_PORT); //UDP客户端连接到指定IP地址和端口号的服务器 if(err==ERR_OK)
{
err=udp_bind(udppcb,IP_ADDR_ANY,UDP_DEMO_PORT);//绑定本地IP地址与端口号
if(err==ERR_OK) //绑定完成
{
udp_recv(udppcb,udp_demo_recv,NULL);//注册接收回调函数
debug("STATUS Connected \r\n");
udp_demo_flag |= <<; //标记已经连接上
debug("Recv Data: \r\n");
}else res=;
}else res=;
}else res=;
while(res==)
{
if(udp_demo_flag&<<)//是否收到数据?
{
udp_demo_flag&=~(<<);//标记数据已经被处理了.
udp_demo_senddata(udppcb);
debug("%s \r\n",udp_demo_recvbuf);
}
MX_LWIP_Process();
delay_us();
}
udp_demo_connection_close(udppcb);
free(tbuf);
}
udp_demo_test
2.RAW TCP CLIENT
LWIP的各函数之间的关系如下图所示
可以看一下void tcp_client_test(void)这个测试函数,前面部分和udp一样,创建pcb控制块,然后直接调用tcp_connect进行连接,不过需要注册连接成功后的回调函数
//TCP Client 测试全局状态标记变量
//bit7:0,没有数据要发送;1,有数据要发送
//bit6:0,没有收到数据;1,收到数据了.
//bit5:0,没有连接上服务器;1,连接上服务器了.
//bit4~0:保留
u8 tcp_client_flag; void tcp_client_test(void)
{
struct tcp_pcb *tcppcb; //定义一个TCP服务器控制块
struct ip4_addr rmtipaddr; //远端ip地址 u8 *tbuf;
u8 res=;
u8 t=;
tbuf = malloc();
if(tbuf==NULL)return ;
tcppcb=tcp_new();
if(tcppcb)
{
IP4_ADDR(&rmtipaddr,REMOTE_IP_ADDRESS[],REMOTE_IP_ADDRESS[],REMOTE_IP_ADDRESS[],REMOTE_IP_ADDRESS[]);
tcp_connect(tcppcb,&rmtipaddr,TCP_CLIENT_PORT,tcp_client_connected);
}else res = ;
delay_ms(); while(!res)
{
if(tcp_client_flag&<<)
{
debug("recv data: ");
debug("%s\r\n",tcp_client_recvbuf);
tcp_client_usersent(tcppcb);
tcp_client_flag&=~(<<);
} MX_LWIP_Process();
delay_ms();
t++;
if(t==)
{
if((tcp_client_flag&<<)==)
{
tcp_client_connection_close(tcppcb,);//关闭连接
tcppcb=tcp_new(); //创建一个新的pcb
if(tcppcb) //创建成功
{
tcp_connect(tcppcb,&rmtipaddr,TCP_CLIENT_PORT,tcp_client_connected);
}
}
t=;
}
}
tcp_client_connection_close(tcppcb,);
debug("TCP CLIENT BREAK!\r\n");
free(tbuf); }
tcp_client_test
err_t tcp_client_connected(void *arg, struct tcp_pcb *tpcb, err_t err)
此函数主要是完成对接收、发送、错误、轮询函数的注册
err_t tcp_client_connected(void *arg, struct tcp_pcb *tpcb, err_t err)
{ if(err==ERR_OK)
{
es=(struct tcp_client_struct*)malloc(sizeof(struct tcp_client_struct)); //申请内存
if(es) //内存申请成功
{ es->state=ES_TCPCLIENT_CONNECTED;//状态为连接成功
es->pcb=tpcb;
es->p=NULL;
tcp_arg(tpcb,es); //使用es更新tpcb的callback_arg
tcp_recv(tpcb,tcp_client_recv); //初始化LwIP的tcp_recv回调功能
tcp_err(tpcb,tcp_client_error); //初始化tcp_err()回调函数
tcp_sent(tpcb,tcp_client_sent); //初始化LwIP的tcp_sent回调功能
tcp_poll(tpcb,tcp_client_poll,); //初始化LwIP的tcp_poll回调功能
tcp_client_flag|=<<; //标记连接到服务器了
debug("tcp client connected !!!\r\n");
err=ERR_OK;
}else
{
debug("tcp client break !!!\r\n");
tcp_client_connection_close(tpcb,es);//关闭连接
err=ERR_MEM; //返回内存分配错误
}
}else
{
tcp_client_connection_close(tpcb,);//关闭连接
}
return err;
}
tcp_client_connected
err_t tcp_client_recv(void *arg,struct tcp_pcb *tpcb,struct pbuf *p,err_t err)
判断数据接收状态,运行UDP那一套
err_t tcp_client_recv(void *arg,struct tcp_pcb *tpcb,struct pbuf *p,err_t err)
{
u32 data_len = ;
struct pbuf *q;
// struct tcp_client_struct *es;
err_t ret_err;
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,,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|=<<; //标记接收到数据了
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_client_recv
err_t tcp_client_usersent(struct tcp_pcb *tpcb)
和udp一样
//LWIP数据发送,用户应用程序调用此函数来发送数据
//tpcb:TCP控制块
//返回值:0,成功;其他,失败
err_t tcp_client_usersent(struct tcp_pcb *tpcb)
{
err_t ret_err;
struct tcp_client_struct *es;
es=tpcb->callback_arg;
if(es!=NULL) //连接处于空闲可以发送数据
{
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&=~(<<); //清除数据发送标志
if(es->p)pbuf_free(es->p); //释放内存
ret_err=ERR_OK;
}else
{
tcp_abort(tpcb);//终止连接,删除pcb控制块
ret_err=ERR_ABRT;
}
return ret_err;
}
tcp_client_usersent
err_t tcp_client_poll(void *arg, struct tcp_pcb *tpcb)
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->state==ES_TCPCLIENT_CLOSING) //连接断开
{
debug("poll close\r\n");
tcp_client_connection_close(tpcb,es); //关闭TCP连接
}
ret_err=ERR_OK;
return ret_err;
}
tcp_client_poll
err_t tcp_client_sent(void *arg, struct tcp_pcb *tpcb, u16_t len)
检测到远程主机的应答后,发送函数
//lwIP tcp_sent的回调函数(当从远端主机接收到ACK信号后发送数据)
err_t tcp_client_sent(void *arg, struct tcp_pcb *tpcb, u16_t len)
{
struct tcp_client_struct *es;
LWIP_UNUSED_ARG(len);
es=(struct tcp_client_struct*)arg;
if(es->p)tcp_client_senddata(tpcb,es);//发送数据
return ERR_OK;
}
//此函数用来发送数据
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,); //将要发送的数据加入到发送缓冲队列中
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_client_sent
void tcp_client_connection_close(struct tcp_pcb *tpcb, struct tcp_client_struct * es)
删除PCB控制块,将回调函数指向空
//关闭与服务器的连接
void tcp_client_connection_close(struct tcp_pcb *tpcb, struct tcp_client_struct * es)
{
//移除回调
tcp_abort(tpcb);//终止连接,删除pcb控制块
tcp_arg(tpcb,NULL);
tcp_recv(tpcb,NULL);
tcp_sent(tpcb,NULL);
tcp_err(tpcb,NULL);
tcp_poll(tpcb,NULL,);
if(es)free(es);
tcp_client_flag&=~(<<);//标记连接断开了
}
tcp_client_connection_close
3.RAW TCP SERVER
下面是从别的博客复制过来的一张图片,很清晰的描述了整个TCP server的通讯流程,从最底层到应用层
注意:tcp_client是从connect的时候就会注册那四个函数,而tcp_sever是没有connect的,在accpt的时候注册阻塞函数(阻塞函数中注册那四个函数),等待客户端的连接
除了这两个初始化步骤和阻塞函数与客户端不一样,其他的都差不多
#include "raw_tcp_server_test.h" u8 tcp_server_recvbuf[TCP_SERVER_RX_BUFSIZE]; const char *tcp_server_sendbuf="Apollo STM32F7 TCP Server send data\r\n"; //TCP Server 测试全局状态标记变量
//bit7:0,没有数据要发送;1,有数据要发送
//bit6:0,没有收到数据;1,收到数据了.
//bit5:0,没有客户端连接上;1,有客户端连接上了.
//bit4~0:保留
u8 tcp_server_flag; void tcp_server_test(void)
{
err_t err;
struct tcp_pcb *tcppcbnew; //定义一个TCP服务器控制块
struct tcp_pcb *tcppcbconn; //定义一个TCP服务器控制块 u8 *tbuf;
u8 rSes=; tbuf=malloc(); //申请内存
if(tbuf==NULL)return ; //内存申请失败了,直接退出
tcppcbnew=tcp_new();
if(tcppcbnew) //创建成功
{
err=tcp_bind(tcppcbnew,IP_ADDR_ANY,TCP_SERVER_PORT);
if(err==ERR_OK) //绑定完成
{
tcppcbconn=tcp_listen(tcppcbnew); //设置tcppcb进入监听状态
tcp_accept(tcppcbconn,tcp_server_accept); //初始化LWIP的tcp_accept的回调函数
}else rSes=;
}else rSes=;
while(rSes==)
{
if(tcp_server_flag&<<)//是否收到数据?
{
debug("recv data: %s\r\n",tcp_server_recvbuf);
tcp_server_flag&=~(<<);//标记数据已经被处理了.
tcp_server_usersent(tcppcbnew);
}
MX_LWIP_Process();
delay_ms();
}
tcp_server_connection_close(tcppcbnew,);//关闭TCP Server连接
tcp_server_connection_close(tcppcbconn,);//关闭TCP Server连接
tcp_server_remove_timewait();
memset(tcppcbnew,,sizeof(struct tcp_pcb));
memset(tcppcbconn,,sizeof(struct tcp_pcb));
free(tbuf);
}
struct tcp_server_struct *Ses;
err_t tcp_server_accept(void *arg,struct tcp_pcb *newpcb,err_t err)
{
err_t ret_err; LWIP_UNUSED_ARG(arg);
LWIP_UNUSED_ARG(err);
tcp_setprio(newpcb,TCP_PRIO_MIN);//设置新创建的pcb优先级 Ses=(struct tcp_server_struct*)mem_malloc(sizeof(struct tcp_server_struct)); //分配内存
if(Ses!=NULL) //内存分配成功
{
Ses->state=ES_TCPSERVER_ACCEPTED; //接收连接
Ses->pcb=newpcb;
Ses->p=NULL; tcp_arg(newpcb,Ses);
tcp_recv(newpcb,tcp_server_recv); //初始化tcp_recv()的回调函数
tcp_err(newpcb,tcp_server_error); //初始化tcp_err()回调函数
tcp_poll(newpcb,tcp_server_poll,); //初始化tcp_poll回调函数
tcp_sent(newpcb,tcp_server_sent); //初始化发送回调函数
debug("new client connected \r\n");
tcp_server_flag|=<<; //标记有客户端连上了
ret_err=ERR_OK;
}else ret_err=ERR_MEM;
return ret_err;
} err_t tcp_server_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
err_t ret_err;
u32 data_len = ;
struct pbuf *q;
struct tcp_server_struct *Ses;
LWIP_ASSERT("arg != NULL",arg != NULL);
Ses=(struct tcp_server_struct *)arg;
if(p==NULL) //从客户端接收到空数据
{
Ses->state=ES_TCPSERVER_CLOSING;//需要关闭TCP 连接了
Ses->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(Ses->state==ES_TCPSERVER_ACCEPTED) //处于连接状态
{
if(p!=NULL) //当处于连接状态并且接收到的数据不为空时将其打印出来
{
memset(tcp_server_recvbuf,,TCP_SERVER_RX_BUFSIZE); //数据接收缓冲区清零
for(q=p;q!=NULL;q=q->next) //遍历完整个pbuf链表
{
//判断要拷贝到TCP_SERVER_RX_BUFSIZE中的数据是否大于TCP_SERVER_RX_BUFSIZE的剩余空间,如果大于
//的话就只拷贝TCP_SERVER_RX_BUFSIZE中剩余长度的数据,否则的话就拷贝所有的数据
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; //超出TCP客户端接收数组,跳出
}
tcp_server_flag|=<<; //标记接收到数据了
tcp_recved(tpcb,p->tot_len);//用于获取接收数据,通知LWIP可以获取更多数据
pbuf_free(p); //释放内存
ret_err=ERR_OK;
}
}else//服务器关闭了
{
tcp_recved(tpcb,p->tot_len);//用于获取接收数据,通知LWIP可以获取更多数据
Ses->p=NULL;
pbuf_free(p); //释放内存
ret_err=ERR_OK;
}
return ret_err; }
//lwIP tcp_err函数的回调函数
void tcp_server_error(void *arg,err_t err)
{
LWIP_UNUSED_ARG(err);
debug("tcp error:%x\r\n",(u32)arg);
if(arg!=NULL)mem_free(arg);//释放内存
} //LWIP数据发送,用户应用程序调用此函数来发送数据
//tpcb:TCP控制块
//返回值:0,成功;其他,失败
err_t tcp_server_usersent(struct tcp_pcb *tpcb)
{
err_t ret_err;
struct tcp_server_struct *Ses;
Ses=tpcb->callback_arg;
if(Ses!=NULL) //连接处于空闲可以发送数据
{
Ses->p=pbuf_alloc(PBUF_TRANSPORT, strlen((char*)tcp_server_sendbuf),PBUF_POOL); //申请内存
pbuf_take(Ses->p,(char*)tcp_server_sendbuf,strlen((char*)tcp_server_sendbuf)); //将tcp_server_sentbuf[]中的数据拷贝到Ses->p_tx中
tcp_server_senddata(tpcb,Ses); //将tcp_server_sentbuf[]里面复制给pbuf的数据发送出去
tcp_server_flag&=~(<<); //清除数据发送标志
if(Ses->p!=NULL)pbuf_free(Ses->p);//释放内存
ret_err=ERR_OK;
}else
{
tcp_abort(tpcb);//终止连接,删除pcb控制块
ret_err=ERR_ABRT;
}
return ret_err;
} //lwIP tcp_poll的回调函数
err_t tcp_server_poll(void *arg, struct tcp_pcb *tpcb)
{
err_t ret_err;
struct tcp_server_struct *Ses;
Ses=(struct tcp_server_struct *)arg;
if(Ses->state==ES_TCPSERVER_CLOSING)//需要关闭连接?执行关闭操作
{
tcp_server_connection_close(tpcb,Ses);//关闭连接
}
ret_err=ERR_OK;
return ret_err;
}
//lwIP tcp_sent的回调函数(当从远端主机接收到ACK信号后发送数据)
err_t tcp_server_sent(void *arg, struct tcp_pcb *tpcb, u16_t len)
{
struct tcp_server_struct *Ses;
LWIP_UNUSED_ARG(len);
Ses = (struct tcp_server_struct *) arg;
if(Ses->p)tcp_server_senddata(tpcb,Ses);//发送数据
return ERR_OK;
}
//此函数用来发送数据
void tcp_server_senddata(struct tcp_pcb *tpcb, struct tcp_server_struct *Ses)
{
struct pbuf *ptr;
u16 plen;
err_t wr_err=ERR_OK;
while((wr_err==ERR_OK)&&Ses->p&&(Ses->p->len<=tcp_sndbuf(tpcb)))
{
ptr=Ses->p;
wr_err=tcp_write(tpcb,ptr->payload,ptr->len,);
if(wr_err==ERR_OK)
{
plen=ptr->len;
Ses->p=ptr->next; //指向下一个pbuf
if(Ses->p)pbuf_ref(Ses->p); //pbuf的ref加一
pbuf_free(ptr);
tcp_recved(tpcb,plen); //更新tcp窗口大小
}else if(wr_err==ERR_MEM)Ses->p=ptr;
tcp_output(tpcb); //将发送缓冲队列中的数据立即发送出去
}
} //关闭tcp连接
void tcp_server_connection_close(struct tcp_pcb *tpcb, struct tcp_server_struct *Ses)
{
tcp_close(tpcb);
tcp_arg(tpcb,NULL);
tcp_sent(tpcb,NULL);
tcp_recv(tpcb,NULL);
tcp_err(tpcb,NULL);
tcp_poll(tpcb,NULL,);
if(Ses)mem_free(Ses);
tcp_server_flag&=~(<<);//标记连接断开了
}
extern void tcp_pcb_purge(struct tcp_pcb *pcb); //在 tcp.c里面
extern struct tcp_pcb *tcp_active_pcbs; //在 tcp.c里面
extern struct tcp_pcb *tcp_tw_pcbs; //在 tcp.c里面
//强制删除TCP Server主动断开时的time wait
void tcp_server_remove_timewait(void)
{
struct tcp_pcb *pcb,*pcb2;
u8 t=;
while(tcp_active_pcbs!=NULL&&t<)
{
MX_LWIP_Process(); //继续轮询
t++;
delay_ms(); //等待tcp_active_pcbs为空
}
pcb=tcp_tw_pcbs;
while(pcb!=NULL)//如果有等待状态的pcbs
{
tcp_pcb_purge(pcb);
tcp_tw_pcbs=pcb->next;
pcb2=pcb;
pcb=pcb->next;
memp_free(MEMP_TCP_PCB,pcb2);
}
}
tcp_server_test
******************************************************************
参考资料:
正点原子《stm32f7 lwip开发手册》
沧海一粟的《lwIP协议栈开发嵌入式网络的三种方法分析》
博客园地址:http://www.cnblogs.com/fozu/p/3613804.html
RAW网络编程的更多相关文章
- 猫哥网络编程系列:详解 BAT 面试题
从产品上线前的接口开发和调试,到上线后的 bug 定位.性能优化,网络编程知识贯穿着一个互联网产品的整个生命周期.不论你是前后端的开发岗位,还是 SQA.运维等其他技术岗位,掌握网络编程知识均是岗位的 ...
- python学习笔记11 ----网络编程
网络编程 网络编程需要知道的概念 网络体系结构就是使用这些用不同媒介连接起来的不同设备和网络系统在不同的应用环境下实现互操作性,并满足各种业务需求的一种粘合剂.网络体系结构解决互质性问题彩是分层方法. ...
- C#网络编程基础知识
C#网络编程基础知识一 1.IPAddress类 用于表示一个IP地址.IPAddress默认构造函数 public IPAddress(long address);一般不用 其中Parse()方法最 ...
- 用C++实现网络编程---抓取网络数据包的实现方法
一般都熟悉sniffer这个工具,它可以捕捉流经本地网卡的所有数据包.抓取网络数据包进行分析有很多用处,如分析网络是否有网络病毒等异常数据,通信协议的分析(数据链路层协议.IP.UDP.TCP.甚至各 ...
- C语言之网络编程(服务器和客户端)
Linux网络编程 1. 套接字:源IP地址和目的IP地址以及源端口号和目的端口号的组合称为套接字.其用于标识客户端请求的服务器和服务. 常用的TCP/IP协议的3种套接字类型如下所示. (1)流套接 ...
- 【Android实战】----从Retrofit源代码分析到Java网络编程以及HTTP权威指南想到的
一.简单介绍 接上一篇[Android实战]----基于Retrofit实现多图片/文件.图文上传中曾说非常想搞明确为什么Retrofit那么屌. 近期也看了一些其源代码分析的文章以及亲自查看了源代码 ...
- socket网络编程【python】
转自:http://www.jb51.net/article/19751.htm socket 是网络连接端点. 一.网络知识的一些介绍 socket 是网络连接端点.例如当你的Web浏览器请求www ...
- UNIX网络编程读书笔记:原始套接口
概述 应用程序可以绕过传输层而直接使用IPv4和IPv6,这称为原始套接口(raw socket).http://www.cnblogs.com/nufangrensheng/p/3583435.ht ...
- UNIX网络编程读书笔记:简介
认知套接口编程接口 理解原始套接口(raw socket)的概念 值得注意的是,客户和服务器是典型的用户进程,而TCP和IP协议则通常是系统内核协议栈的一部分. 上图中在TCP和UDP之间留有间隙 ...
随机推荐
- Charles 修改请求/compose和Compose New
本文参考:撰写工具/compose和Compose New 撰写工具/compose和Compose New compose 是在原有的请求基础上,修改: 可以写各种状态: – URL: – Meth ...
- sersync 实时同步
1.什么是实时同步 监控一个目录的变化, 当该目录触发事件(创建\删除\修改) 就执行动作, 这个动作可以是 rsync同步 ,也可以是其他. 2.为什么要实时同步 1.能解决nfs单点故障问题. ...
- day 12 特殊权限
1.suid (set uid) 特殊权限 suid优点: 可以让普通用户执行无法执行的命令 方便 suid缺点: 如果rm 为suid, 无论谁执行该命令,都能删除系统的任何 资源 set uid ...
- Vue学习之不同组件之间的消息传递
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- [AWS] S3 Bucket
云存储服务 2.1 为网站打开属性 属性和权限设置 设置bucket属性,打开功能:Static website hosting(静态网站托管) 设置bucket权限,Permissions ---- ...
- JAVA设计模式-单例模式(Singleton)线程安全与效率
一,前言 单例模式详细大家都已经非常熟悉了,在文章单例模式的八种写法比较中,对单例模式的概念以及使用场景都做了很不错的说明.请在阅读本文之前,阅读一下这篇文章,因为本文就是按照这篇文章中的八种单例模式 ...
- SPSS学习笔记参数检验—单样本t检验
单样本t检验 目的:利用来自总体的样本数据,推断该总体的均值是否与指定的检验值存在差异. 适用条件:样本来自的总体应服从或者近似服从正态分布. 注:当样本量n比较大时:由中心极限定理得知,即使原数据不 ...
- Spring 梳理-profile与条件化定义bean
定义profile <beans> //root <beans profile="dev"> <bean id=.../> </beans ...
- javascript中字符串对象常用的方法和属性
前言 字符串是一种非常重要的数据类型,在Java等面向对象编程语言中,它代表对象类型,而在javascript中它却是一种基本数据类型,在开发的领域中,我们经常会碰到,无论是前端还是后台.比如后台验证 ...
- .Net Core 商城微服务项目系列(十三):搭建Log4net+ELK+Kafka日志框架
之前是使用NLog直接将日志发送到了ELK,本篇将会使用Docker搭建ELK和kafka,同时替换NLog为Log4net. 一.搭建kafka 1.拉取镜像 //下载zookeeper docke ...