TCP RAW API
1.1 新建TCP控制块
函数原型:
struct tcp_pcb * tcp_new(void)
1.2 绑定控制块 tcp_bind()
用于服务端程序
将本地的 IP 地址、端口号与一个控制块进行绑定
函数原型:
err_t tcp_bind(struct tcp_pcb *pcb, const p_addr_t *ipaddr, u16_t port)
1.3 设置控制块处于监听状态 tcp_listen()
用于服务端程序,在接收连接前必须让TCP处于监听状态
#define tcp_listen(pcb) tcp_listen_with_backlog(pcb, TCP_DEFAULT_LISTEN_BACKLOG)
struct tcp_pcb * tcp_listen_with_backlog_and_err(struct tcp_pcb *pcb, u8_t backlog, err_t *err)
1.4 处理连接 tcp_accept()
用于服务端,处理客户端连接
函数原型:
void tcp_accept(struct tcp_pcb *pcb, tcp_accept_fn accept)
tcp_accept_fn回调函数类型定义:
typedef err_t (*tcp_accept_fn)(void *arg,
struct tcp_pcb *newpcb,
err_t err);
1.5 建立连接 tcp_connect()
TCP客户端使用该函数主动发起连接
函数原型:
err_t tcp_connect(struct tcp_pcb *pcb,
const ip_addr_t *ipaddr,
u16_t port,
tcp_connected_fn connected)
当TCP连接成功connected回调函数就会被调用
tcp_connected_fn回调函数类型定义:
typedef err_t (*tcp_connected_fn)(void *arg,
struct tcp_pcb *tpcb,
err_t err);
1.6 终止连接 tcp_close()
客户端应用程序主动调用tcp_close()终止一个TCP连接
err_t tcp_close(struct tcp_pcb *pcb)
1.7 接收数据 tcp_recv()
函数原型:
void tcp_recv(struct tcp_pcb *pcb, tcp_recv_fn recv) //注册recv回调函数字段
回调函数原型:
typedef err_t (*tcp_recv_fn)(void *arg,
struct tcp_pcb *tpcb,
struct pbuf *p,
err_t err);
该回调函数被调用的契机:
- 内核接收到数据,该函数被调用并将数据递交给应用层,也就是将数据传入回调函数
- 内核检测到对方主动终止TCP连接,也会触发回调函数
所以数据的处理和应用程序编写就在该回调函数中实现
1.8 发送数据 tcp_sent()
void tcp_sent(struct tcp_pcb *pcb, tcp_sent_fn sent) //将TCP控制块sent回调函数字段注册一个tcp_sent_fn的函数,数据发送成功将调用sent回调函数通知应用数据已经被对方接收
tcp_sent_fn回调函数类型:
typedef err_t (*tcp_sent_fn)(void *arg,
struct tcp_pcb *tpcb,
u16_t len);
服务端编程实现
1、服务器的初始化
struct ip4_addr_fmt {
uint32_t addr1 : 8;
uint32_t addr2 : 8;
uint32_t addr3 : 8;
uint32_t addr4 : 8;
};
void tcp_user_server_init(void)
{
err_t err;
tcp_server = tcp_new(); /* 创建TCP控制块 */
if (tcp_server == NULL)
{
LOG_LINE("failed to new tcp pcb");
return;
}
err = tcp_bind(tcp_server, IP_ADDR_ANY, 9005); /* 绑定IP端口 */
if (err != ERR_OK)
{
LOG_LINE("failed to bind tcp");
tcp_close(tcp_server);
return;
}
tcp_server = tcp_listen(tcp_server); /* 启用接听 */
tcp_accept(tcp_server, tcp_server_accept_cb); /* 设置收到客户端连接请求的回调函数 */
LOG_LINE("TCP Server OK!!!");
return;
}
接收请求连接的回调:
err_t tcp_server_accept_cb(void *arg, struct tcp_pcb *newpcb, err_t err)
{
struct ip4_addr_fmt *ip = (struct ip4_addr_fmt *)&newpcb->remote_ip;
LOG_LINE("client %d.%d.%d.%d/%d connected", ip->addr1, ip->addr2, ip->addr3, ip->addr4, newpcb->remote_port);
tcp_recv(newpcb, tcp_server_recv_cb);
tcp_err(newpcb, tcp_err_cb);
return ERR_OK; /* 记得return ERR_OK,很重要/(ㄒoㄒ)/~~ */
}
tcp_recv(newpcb, tcp_server_recv_cb);
在接收请求回调中设置客户端的数据接收函数回调函数tcp_err(newpcb, tcp_err_cb);
设置错误的处理的回调函数
在测试过程中出现客户端连接上后又断开,一直在反复重连,调试很久一段时间发现是因为tcp_server_accept_cb
函数的最后没有return ERR_OK
客户端数据的接收:
err_t tcp_server_recv_cb(void *arg, struct tcp_pcb *tpcb,
struct pbuf *p, err_t err)
{
if (p != NULL)
{
struct pbuf *q;
int recv_count = 0;
tcp_recved(tpcb, p->tot_len); /* 更新接收窗口 */
for (q = p; q != NULL; q = q->next)
{
if (q->len > sizeof(recv_test_buf))
{
memcpy(recv_test_buf, q->payload, sizeof(recv_test_buf));
break;
}
else
{
if (recv_count >= sizeof(recv_test_buf))
break;
memcpy(&recv_test_buf[recv_count], q->payload, q->len);
recv_count += q->len;
}
}
pbuf_free(p);
}
else if (err == ERR_OK) /* 接收成功但数据包是空的说明客户端断开连接 */
{
LOG_LINE("客户端断开连接");
return tcp_close(tpcb);
}
struct ip4_addr_fmt *ip = (struct ip4_addr_fmt *)&tpcb->remote_ip;
LOG_LINE("recv from %d.%d.%d.%d/%d, msg %s",
ip->addr1, ip->addr2, ip->addr3, ip->addr4, tpcb->remote_port, recv_test_buf);
memset(recv_test_buf, 0, sizeof(recv_test_buf));
return ERR_OK; /* 记得return ERR_OK,很重要/(ㄒoㄒ)/~~ */
}
- 接收数据前要调用
tcp_recved(tpcb, p->tot_len);
更新接收窗口
客户端编程实现
客户端编程就比较简单了
void tcp_user_client_init(void)
{
tcp_client = tcp_new();
if (tcp_client == NULL)
{
LOG_LINE("failed to new tcp client");
return;
}
ip4_addr_t server_ip;
IP4_ADDR(&server_ip, 192, 168, 57, 112); /* 服务器IP地址 */
err_t err;
tcp_arg(tcp_client, tcp_client);
tcp_err(tcp_client, tcp_client_err_cb);
err = tcp_connect(tcp_client, &server_ip, 52000, tcp_client_connected_cb);
}
在成功连接的回调函数中设置客户端数据接收回调:
err_t tcp_client_connected_cb(void *arg, struct tcp_pcb *tpcb, err_t err)
{
tcp_recv(tpcb, tcp_client_recv_cb); /* 设置数据接收回调 */
tcp_poll(tpcb, tcp_poll_cb, 10); /* 设置轮询回调 */
LOG_LINE("连接服务器成功");
return ERR_OK; /* 记得return ERR_OK,很重要/(ㄒoㄒ)/~~ */
}
数据接收:
err_t tcp_client_recv_cb(void *arg, struct tcp_pcb *tpcb,
struct pbuf *p, err_t err)
{
if (p != NULL)
{
struct pbuf *q;
int recv_count = 0;
tcp_recved(tpcb, p->tot_len); /* 更新接收窗口 */
for (q = p; q != NULL; q = q->next)
{
if (q->len > sizeof(recv_test_buf))
{
memcpy(recv_test_buf, q->payload, sizeof(recv_test_buf));
break;
}
else
{
if (recv_count >= sizeof(recv_test_buf))
break;
memcpy(&recv_test_buf[recv_count], q->payload, q->len);
recv_count += q->len;
}
}
pbuf_free(p);
}
else if (err == ERR_OK) /* 接收成功但数据包是空的说明客户端断开连接 */
{
LOG_LINE("服务端断开连接");
tcp_close(tpcb);
tcp_client = NULL;
tcp_user_client_init(); /* 重连服务器 */
return err;
}
struct ip4_addr_fmt *ip = (struct ip4_addr_fmt *)&tpcb->remote_ip;
LOG_LINE("recv from %d.%d.%d.%d/%d, msg %s",
ip->addr1, ip->addr2, ip->addr3, ip->addr4, tpcb->remote_port, recv_test_buf);
memset(recv_test_buf, 0, sizeof(recv_test_buf));
return ERR_OK;
}
在定时轮询回调中向服务器发送数据:
err_t tcp_poll_cb(void *arg, struct tcp_pcb *tpcb)
{
static uint32_t test_count = 0;
char buf[100] = { 0 };
snprintf(buf, sizeof(buf), "tcp client test %d\r\n", test_count++);
return tcp_write(tpcb, buf, sizeof(buf), 1); /* 记得return,很重要/(ㄒoㄒ)/~~ */
}
总结
在测试过程中出现客户端连接上后又断开,一直在反复重连,调试很久一段时间发现是因为tcp_server_accept_cb
函数的最后没有return ERR_OK
。
代码还是要去写,不想看着接口很简单,想当然一点问题也没有,看一眼就会了,结果当自己亲自去写的时候出现很多愚蠢的问题。