LWIP_STM32_ENC28J60_NETCONN_UDP(3)

前面移植了lwip之后只是简单地做了一个dhcp的程序,但是实际工作中经常要用来通讯,那今天就来讲一讲怎么用lwip来进行UDP通讯

要使用数据通信首先第一步得知道lwip是怎么样保存数据的,在使用netconn数据包进行通讯的时候,netbuf是主要的数据结构,该数据结构的构成如下

struct netbuf {
  struct pbuf *p, *ptr;
  ip_addr_t addr;
  u16_t port;
#if LWIP_NETBUF_RECVINFO || LWIP_CHECKSUM_ON_COPY
#if LWIP_CHECKSUM_ON_COPY
  u8_t flags;
#endif /* LWIP_CHECKSUM_ON_COPY */
  u16_t toport_chksum;
#if LWIP_NETBUF_RECVINFO
  ip_addr_t toaddr;
#endif /* LWIP_NETBUF_RECVINFO */
#endif /* LWIP_NETBUF_RECVINFO || LWIP_CHECKSUM_ON_COPY */
};

蛮长一串,但是不用全部弄明白只需要关注这几个

struct pbuf *p, *ptr;
ip_addr_t addr;
u16_t port;

首先,系统中有一个pbuf的链表,p指向pbuf的顶端,ptr指向当前正在使用的pbuf,netbuf_next()和 netbuf_first()操作 ptr 字段。 addr 和 port 字段用来记录数据发送方的 IP 地址和端口号,netbuf_fromaddr 和 netbuf_fromport 这两个宏定义用于返回 addr 和 port 这两个字段。netbuf和 pbuf 之间的关系如图

不管是 TCP 连接还是 UDP 连接, 接收到数据包后会将数据封装在一个 netbuf 中,然后将这个 netbuf 交给应用程序去处理。在数据发送时,根据不同的连接有不同的处理:对于 TCP 连
接,用户只需要提供待发送数据的起始地址和长度,内核会根据实际情况将数据封装在合适大小的数据包中,并放入发送队列中,对于 UDP 来说,用户需要自行将数据封装在 netbuf 结构
中,当发送函数被调用的时候内核直接将数据包中的数据发送出去

操作netbuf的api主要有下面几个

这只是数据结构,接下来是使用这些数据结构的api,主要是下面这些

这里面用到了一个新的数据结构,叫做netconn,也就是今天我们的核心,他的构成如下

/** A netconn descriptor */
struct netconn {
  /** 连接类型 (TCP, UDP or RAW) */
  enum netconn_type type;
  /** 当前连接状态 */
  enum netconn_state state;
  /** 内核中与这个相关的控制块指针 */
  union {
    struct ip_pcb  *ip;
    struct tcp_pcb *tcp;
    struct udp_pcb *udp;
    struct raw_pcb *raw;
  } pcb;
  /** 这个链接最近的一个错误 */
  err_t last_err;
  /** 用于两个api上下文同步的信号量 */
  sys_sem_t op_completed;
  /**接收数据的消息邮箱 */
  sys_mbox_t recvmbox;
#if LWIP_TCP
  /** 用于TCP服务器,连接请求的缓冲队列 */
  sys_mbox_t acceptmbox;
#endif /* LWIP_TCP */
  /** socket描述符,用于socket类的api */
#if LWIP_SOCKET
  int socket;
#endif /* LWIP_SOCKET */
#if LWIP_SO_SNDTIMEO
  /** 发送数据的超时时间 */
  s32_t send_timeout;
#endif /* LWIP_SO_RCVTIMEO */
#if LWIP_SO_RCVTIMEO
  /** 接收数据的超时时间 */
  int recv_timeout;
#endif /* LWIP_SO_RCVTIMEO */
#if LWIP_SO_RCVBUF
  /** 接收消息队列的长度 */
  int recv_bufsize;
  /** 当前接收邮箱中已经缓存的数据长度 */
  s16_t recv_avail;
#endif /* LWIP_SO_RCVBUF */
  /** netconn内部的状态标识符 */
  u8_t flags;
#if LWIP_TCP
  /** 当调用 netconn_write 发送数据但缓存不足的时候,数据会暂时存放在 current_msg 中,等待下
一次数据发送, write_offset 记录下一次发送时的索引 */
  size_t write_offset;
  /** TCP: when data passed to netconn_write doesn‘t fit into the send buffer,
      this temporarily stores the message.
      Also used during connect and close. */
  struct api_msg_msg *current_msg;
#endif /* LWIP_TCP */
  /** //连接相关回调函数,实现 socket API 时使用 */
  netconn_callback callback;
};

与这个api相关的还有一些枚举,这里就先不说了,基本上通过这些api就能够进行网络通讯,接下来看看怎样实现udp通讯

流程是这样的:创建一个udp链接--绑定一个udp端口--设定目的地的端口和ip,然后就能发送了,因为udp本身是无连接的,所以我们不需要等待udp连连接上在操作,具体的代码如下

//创建UDP线程
//返回值:0 UDP创建成功
//        其他 UDP创建失败
INT8U udp_demo_init(void)
{
    INT8U res;
    OS_CPU_SR cpu_sr;

    OS_ENTER_CRITICAL();    //关中断
    res = OSTaskCreate(udp_thread,(void*)0,(OS_STK*)&UDP_TASK_STK[UDP_STK_SIZE-1],UDP_PRIO); //创建UDP线程
    OS_EXIT_CRITICAL();        //开中断

    return res;
}

创建任务之后任务如下

//udp任务函数
static void udp_thread(void *arg)
{
    OS_CPU_SR cpu_sr;
    err_t err;
    static struct netconn *udpconn;
    static struct netbuf  *recvbuf;
    static struct netbuf  *sentbuf;
    struct ip_addr destipaddr;
    u32 data_len = 0;
    struct pbuf *q;

    while(dhcpstatus != 2)//等待dhcp成功
    {
        OSTimeDly(10);
        printf("wait dhcp\r\n");
    }

    LWIP_UNUSED_ARG(arg);
    udpconn = netconn_new(NETCONN_UDP);  //创建一个UDP链接
    udpconn->recv_timeout = 10;          

    if(udpconn != NULL)  //创建UDP连接成功
    {
        err = netconn_bind(udpconn,IP_ADDR_ANY,UDP_DEMO_PORT);
        IP4_ADDR(&destipaddr,192,168,1,105); //构造目的IP地址
        netconn_connect(udpconn,&destipaddr,UDP_DEMO_PORT);     //连接到远端主机
        if(err == ERR_OK)//绑定完成
        {
            while(1)
            {
                if(keyValue == KEY_DOWN)
                {
                    udp_flag = LWIP_SEND_DATA;
                }
                if((udp_flag & LWIP_SEND_DATA) == LWIP_SEND_DATA) //有数据要发送
                {
                    sentbuf = netbuf_new();
                    netbuf_alloc(sentbuf,strlen((char *)udp_demo_sendbuf));
                    sentbuf->p->payload = (char*)udp_demo_sendbuf;       //指udp_demo_sendbuf组
                    err = netconn_send(udpconn,sentbuf);      //将netbuf中的数据发送出去
                    if(err != ERR_OK)
                    {
                        printf("发送失败\r\n");
                        netbuf_delete(sentbuf);      //删除buf
                    }
                    udp_flag &= ~LWIP_SEND_DATA;    //清除数据发送标志
                    netbuf_delete(sentbuf);          //删除buf
                    keyValue = 0;
                }    

                netconn_recv(udpconn,&recvbuf); //接收数据
                if(recvbuf != NULL)          //接收到数据
                {
                    OS_ENTER_CRITICAL(); //关中断
                    printf("receive data\r\n");
                    memset(udp_demo_recvbuf,0,UDP_DEMO_RX_BUFSIZE);  //数据接收缓冲区清零
                    for(q=recvbuf->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客户端接收数组,跳出
                    }
                    OS_EXIT_CRITICAL();  //开中断
                    data_len=0;  //复制完成后data_len要清零。
                    //打印接收到的数据
                    printf("%s\r\n",udp_demo_recvbuf);
                    netbuf_delete(recvbuf);      //删除buf
                }
                else
                    OSTimeDlyHMSM(0,0,0,10);  //延时5ms
            }
        }
        else
            printf("UDP绑定失败\r\n");
    }
    else
        printf("UDP连接创建失败\r\n");
}

这都没什么好说的,但是需要注意的是,最好将udp的优先级别设置的低于数据查询任务的优先级别

工程代码如下

http://download.csdn.net/detail/dengrengong/8599069

时间: 2024-08-27 22:40:32

LWIP_STM32_ENC28J60_NETCONN_UDP(3)的相关文章

使用 IDEA 创建 Maven Web 项目 (异常)- Disconnected from the target VM, address: '127.0.0.1:59770', transport: 'socket'

运行环境: JDK 版本:1.8 Maven 版本:apache-maven-3.3.3 IDEA 版本:14 maven-jetty-plugin 配置: <plugin> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-maven-plugin</artifactId> <configuration> <webAppSourceDirectory>${pro

在深圳有娃的家长必须要懂的社保少儿医保,不然亏大了!(收藏)

在深圳有娃的家长必须要懂的社保少儿医保,不然亏大了!(收藏) 转载2016-07-26 17:21:47 标签:深圳少儿医保社保医疗保险住院 在深圳工作或生活的家长们可能还有人不清楚,其实小孩子最大的基础保障福利就是少儿医保.如果以前没重视关注的,现在您看到这篇文章还来得及!少儿医保每年政府财政补贴384元,自己只需交200元左右,就可以享受门诊报销1000元,住院报销比例90%,最高报销额度达148万,大病门诊最高报销比例90%!如何享受?有哪些待遇?接下来就详细来做一个介绍: 少儿医保投保需

彻底解决_OBJC_CLASS_$_某文件名&quot;, referenced from:问题(转)

最近在使用静态库时,总是出现这个问题.下面总结一下我得解决方法: 1. .m文件没有导入   在Build Phases里的Compile Sources 中添加报错的文件 2. .framework文件没有导入静态库编译时往往需要一些库的支持,查看你是否有没有导入的库文件同样是在Build Phases里的Link Binary With Libraries中添加 3. 重复编译,可能你之前复制过两个地方,在这里添加过两次,删除时系统没有默认删除编译引用地址在Build Settings里搜索

爱奇艺、优酷、腾讯视频竞品分析报告2016(一)

1 背景 1.1 行业背景 1.1.1 移动端网民规模过半,使用时长份额超PC端 2016年1月22日,中国互联网络信息中心 (CNNIC)发布第37次<中国互联网络发展状况统计报告>,报告显示,网民的上网设备正在向手机端集中,手机成为拉动网民规模增长的主要因素.截至2015年12月,我国手机网民规模达6.20亿,有90.1%的网民通过手机上网. 图 1  2013Q1~2015Q3在线视频移动端和PC端有效使用时长份额对比 根据艾瑞网民行为监测系统iUserTracker及mUserTrac

Android 导航条效果实现(六) TabLayout+ViewPager+Fragment

TabLayout 一.继承结构 public class TabLayout extends HorizontalScrollView java.lang.Object ? android.view.View ? android.view.ViewGroup ? android.widget.FrameLayout ? android.widget.HorizontalScrollView ? android.support.design.widget.TabLayout 二.TabLayou

微信小程序——豆瓣电影——(2):小程序运行部署

Demo 预览 演示视频(流量预警 2.64MB) GitHub Repo 地址 仓库地址:https://github.com/zce/weapp-demo 使用步骤 将仓库克隆到本地: bash $ git clone https://github.com/zce/weapp-demo.git weapp-douban --depth 1 $ cd weapp-douban 打开微信Web开放者工具(注意:必须是0.9.092300版本) 必须是0.9.092300版本,之前的版本不能保证正

初识运维3--在虚拟机中安装Linux发行版系统(CentOS)的方法

在讲Linux系统发行版本的安装过程之前,先大略说明一下虚拟化. 虚拟化:将底层硬件资源抽象为用户更容易读懂和使用的逻辑抽象层的技术. 最早由IBM提出,现使用率较高的虚拟化软件平台有三类:VMware workstation.VirtualBOX.HyperV.在这里使用VMware workstation作为例子讲解说明安装过程. 虚拟化网络: 桥接模式:让物理机和虚拟机利用物理网络接口完成通信.虚拟机可以访问互联网. 仅主机模式:让虚拟机和物理机利用被虚拟出来的VMnet1网络接口完成通信

pythonの函数学习笔记(一)

函数是可以实现一些特定功能的小方法或小程序定义函数function的方法:def function_name(arg1,arg2[,...]): statement [return value]注意事项:1.def开头,代表定义函数,def和函数名中间要敲一个空格:2.返回值不是必须的,如果没有renturn语句,则默认返回值None:3.函数名必须以下划线或字母开头,可以包含任意字母.数字或下划线的组合,区分大小写且不能是保留字: py使用名称空间的概念存储对象,这个名称空间就是对象作用的区域

网络攻防第一次作业(201421450010)

姓名:陈书扬 学号:201421450010 指导教师:高见 1.虚拟机安装与调试 安装windows和linux(kali)两个虚拟机,均采用NAT网络模式,查看主机与两个虚拟机器的IP地址,并确保其连通性.同时为两个虚拟机做一个快照 windows虚拟机 Linux虚拟机 本地主机win10 两台主机都ping通 2.Windows基本命令 dir显示目录   cd 进入目录 Arp -a -d -s arp缓存 net share 查看计算机IPC$共享资源 netstat -ano网络链