LwIP RAW API TCP服务端客户端编程及问题

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
代码还是要去写,不想看着接口很简单,想当然一点问题也没有,看一眼就会了,结果当自己亲自去写的时候出现很多愚蠢的问题。

  • 3
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

欲盖弥彰1314

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值