前面移植了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