LWIP裸机环境下实现TCP与UDP通讯
? 前面移植了LWIP,并且簡單的實用了DHCP的功能,今天來使用一下實際的數據通訊的功能
? 首先是實現TCP客戶端,我先上代碼
#ifndef __TCP_CLIENT_H_ #define __TCP_CLIENT_H_ #include "network.h"//連接狀態 enum tcp_client_states {ES_NONE = 0, ES_RECEIVED, //接收到了數據ES_CLOSING //連接關閉 };//TCP服務器狀態 struct tcp_client_state {u8_t state; };#define LWIP_CLIENT_BUF 200 //TCP鏈接緩存extern u8 lwip_client_buf[LWIP_CLIENT_BUF]; //定義用來發送和接收數據的緩存extern u8 lwip_tcp_client_flag; //用于定義lwip tcp client狀態//客戶端成功連接到遠程主機時調用 err_t Tcp_Client_Connect(void *arg,struct tcp_pcb *tpcb,err_t err);//連接輪詢時將要調用的函數 err_t Tcp_Client_Poll(void *arg, struct tcp_pcb *tpcb);//用于連接遠程主機 void Tcp_Client_Connect_Remotehost(void);//客戶端接收到數據之后將要調用的函數 err_t Tcp_Client_Recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err);//關閉連接 void Tcp_Client_Close(struct tcp_pcb *tpcb, struct tcp_client_state* ts);//初始化TCP客戶端 void Tcp_Client_Init(void);#endif #include "tcp_client.h"u8 lwip_tcp_client_flag; //用于定義lwip tcp client狀態//定義一個TCP的協議控制塊 struct tcp_pcb* tcp_client_pcb; //鏈接的自動回應信息 static const char* respond = "tcp_client connect success\r\n";u8 lwip_client_buf[LWIP_CLIENT_BUF]; //定義用來發送和接收數據的緩存//客戶端成功連接到遠程主機時調用 err_t Tcp_Client_Connect(void *arg,struct tcp_pcb *tpcb,err_t err) {struct tcp_client_state* ts;ts = arg; ts->state = ES_RECEIVED; //可以開始接收數據了lwip_tcp_client_flag |= LWIP_CONNECTED; //標記連接成功了tcp_write(tpcb,respond,strlen(respond),1); //回應信息 return ERR_OK; }//連接輪詢時將要調用的函數 err_t Tcp_Client_Poll(void *arg, struct tcp_pcb *tpcb) {err_t ret_err;struct tcp_client_state* ts;ts = arg; // lwip_log("tcp_client_polling!\r\n");if(ts!=NULL)//連接處于空閑可以發送數據 { if((lwip_tcp_client_flag&LWIP_SEND_DATA)==LWIP_SEND_DATA){tcp_write(tpcb,lwip_client_buf,strlen((char *)lwip_client_buf),1);//發送數據lwip_tcp_client_flag &=~LWIP_SEND_DATA; //清除發送數據的標志 }}else{tcp_abort(tpcb);ret_err = ERR_ABRT;}return ret_err; }//用于連接遠程主機 void Tcp_Client_Connect_Remotehost(void) {//記住如果此處需要頻繁重連的時候記得先關閉已經申請的tcb//最好將tcb換成全局變量 // Tcp_Client_Close(); Tcp_Client_Init(); }//客戶端接收到數據之后將要調用的函數 err_t Tcp_Client_Recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) {err_t ret_err;struct tcp_client_state* ts;ts = arg; //TCP PCB狀態if(p==NULL){ ts->state = ES_CLOSING; //連接關閉了 Tcp_Client_Close(tpcb,ts);lwip_tcp_client_flag &=~ LWIP_CONNECTED; //清除連接標志 }else if(err!=ERR_OK){ //位置錯誤釋放pbufif(p!=NULL){pbuf_free(p);}ret_err = err; //得到錯誤 }else if(ts->state==ES_RECEIVED){//連接收到了新的數據// printf("服務器新接收的數據:%s\r\n",p->payload);if((p->tot_len)>=LWIP_CLIENT_BUF){ //如果收的的數據大于緩存((char*)p->payload)[199] = 0; memcpy(lwip_client_buf,p->payload,200);}else{ memcpy(lwip_client_buf,p->payload,p->tot_len);lwip_client_buf[p->tot_len] = 0; }lwip_tcp_client_flag |= LWIP_NEW_DATA; //收到了新的數據tcp_recved(tpcb, p->tot_len); //用于獲取接收數據的長度, 表示可以獲取更多的數據 pbuf_free(p); //釋放內存ret_err = ERR_OK;}else if(ts->state==ES_CLOSING)//服務器關閉了 { tcp_recved(tpcb, p->tot_len); //遠程端口關閉兩次,垃圾數據 pbuf_free(p);ret_err = ERR_OK;}else{ //其他未知狀態tcp_recved(tpcb, p->tot_len);pbuf_free(p);ret_err = ERR_OK;}return ret_err;}//關閉連接 void Tcp_Client_Close(struct tcp_pcb *tpcb, struct tcp_client_state* ts) {tcp_arg(tcp_client_pcb, NULL); tcp_recv(tcp_client_pcb, NULL);tcp_poll(tcp_client_pcb, NULL, 0); if(ts!=NULL){mem_free(ts);}tcp_close(tpcb); }//指定連接的客戶端為1300端口 #define TCP_CLIENT_PORT 1300//初始化TCP客戶端 void Tcp_Client_Init(void) {struct tcp_client_state* ts;ip_addr_t ipaddr;IP4_ADDR(&ipaddr, 192, 168, 1, 101); tcp_client_pcb = tcp_new(); //新建一個PCBif(tcp_client_pcb!=NULL){ ts = mem_malloc(sizeof(struct tcp_client_state)); //申請內存tcp_arg(tcp_client_pcb, ts); //將程序的協議控制塊的狀態傳遞給多有的回調函數//設定TCP的回調函數tcp_connect(tcp_client_pcb,&ipaddr,TCP_CLIENT_PORT,Tcp_Client_Connect);tcp_recv(tcp_client_pcb, Tcp_Client_Recv); //指定連接接收到新的數據之后將要調用的回調函數tcp_poll(tcp_client_pcb, Tcp_Client_Poll, 0); //指定輪詢時將要調用的回調函數 } }??? 我們可以看到,在tcp客戶端初始化的時候我們使用了回調函數技術將接收數據和輪詢數據的函數添加到了網絡的底層輪轉中,還要指定鏈接的端口和ip,但是不需要指定本地端口
同時,我們還在在程序主循環中不停地處理網絡事件(因為沒有操作系統)
這樣,完整的TCP通訊鏈路就能建成了,數據的使用這樣
if((lwip_tcp_client_flag&LWIP_CONNECTED)==LWIP_CONNECTED){LCD_ShowString(0,24,240,320,(u8*)"TCP CLIENT Connect ",LCD_BLACK);if(keyValue == KEY_RIGHT){t_client_cnt++;sprintf((char*)lwip_client_buf,"tcp_client send %d\r\n",t_client_cnt); LCD_ShowString(18,48,240,320,(u8*)lwip_client_buf,LCD_BLACK);//顯示當前發送數據lwip_tcp_client_flag |= LWIP_SEND_DATA; //標記有數據需要發送keyValue = 0;}}else{ // Tcp_Client_Connect_Remotehost();//沒有連接上,此時處于TCP客戶端模式,則嘗試重新連接 }if((lwip_tcp_client_flag&LWIP_NEW_DATA)==LWIP_NEW_DATA){LCD_ShowString(18,36,240,320,(u8*)lwip_client_buf,LCD_BLACK);lwip_tcp_client_flag &=~LWIP_NEW_DATA; //清除接受數據的標志}
同理,TCP服務器的流程也差不多,但是多了一個東西,tcp服務器要監聽本地某一個特定端口,使用如下
可以看到上面的代碼多了一個bind的過程
剩下的就是udp通訊了,在之前tcp的源碼里面能看到,發送數據的時候不需要專門去做發送函數,指定一個標志位就行了,在輪轉的時候程序會自動把數據發送出去,當使用UDP的時候就不行了,因為TCP是面向鏈接的,而UDP本身就是無連接的,UDP的是使用源碼如下
#include "udp_send.h"//該文件即可以接受也可以發送數據 //都是用1500端口進行操作 u8 lwip_udp_send_buf[LWIP_UDP_SEND_BUF]; //定義用來發送和接收數據的緩存 u8 lwip_udp_send_flag; //用于定義lwip tcp client狀態struct udp_pcb* udp_client_pcb;//定義一個UDP的協議控制塊 struct pbuf * ubuf_client;//接收到數據包將要調用的函數 void udp_client_rev(void* arg,struct udp_pcb* upcb,struct pbuf* p,struct ip_addr*addr ,u16_t port) {if(p!=NULL){if((p->tot_len)>=LWIP_UDP_SEND_BUF){ //如果收的的數據大于緩存((char*)p->payload)[199] = 0; memcpy(lwip_udp_send_buf,p->payload,200);}else{ memcpy(lwip_udp_send_buf,p->payload,p->tot_len);lwip_udp_send_buf[p->tot_len] = 0; }lwip_udp_send_flag |= LWIP_NEW_DATA; //收到了新的數據 pbuf_free(p);} }//發送數據 void udp_client_send_data(void) {err_t err;if((lwip_udp_send_flag&LWIP_SEND_DATA)==LWIP_SEND_DATA){ubuf_client = pbuf_alloc(PBUF_TRANSPORT, strlen((char *)lwip_udp_send_buf), PBUF_RAM); //為發送包分配內存ubuf_client->payload = lwip_udp_send_buf;err=udp_send(udp_client_pcb,ubuf_client);//發送數據if(err!=ERR_OK){//lwip_log("UDP SERVER發送數據失敗!"); }lwip_udp_send_flag &=~LWIP_SEND_DATA; //清除發送數據的標志 pbuf_free(ubuf_client);} }//初始化UDP客戶端 void Init_UDP_Client(void) {//struct ip_addr* ipaddr; ip_addr_t ipaddr;IP4_ADDR(&ipaddr, 192, 168, 1, 101); //設置本地ip地址udp_client_pcb = udp_new(); //新建一個UDP協議控制塊if(udp_client_pcb!=NULL){udp_bind(udp_client_pcb,IP_ADDR_ANY,UDP_CLIENT_PORT); // udp_connect(udp_client_pcb,&ipaddr,UDP_CLIENT_PORT); //設置連接到遠程主機udp_recv(udp_client_pcb,udp_client_rev,NULL); //指定收到數據包時的回調函數 } }??? 這是一個UDP的客戶端,在初始化的時候指明了目的主機的地址和端口號,連接到目的主機上
接下來是一個UDP的服務器
#include "udp_recv.h"struct udp_pcb* udp_server_pcb;//定義一個UDP的協議控制塊 struct pbuf * ubuf;u8 lwip_udp_recv_buf[LWIP_UDP_RECV_BUF]; //定義用來發送和接收數據的緩存 u8 lwip_udp_recv_flag; //用于定義lwip tcp client狀態//接收到數據包將要調用的函數 void Udp_Server_Recv(void* arg,struct udp_pcb* upcb,struct pbuf* p,struct ip_addr*addr ,u16_t port) {if(p!=NULL){if((p->tot_len)>=LWIP_UDP_RECV_BUF){ //如果收的的數據大于緩存((char*)p->payload)[199] = 0; memcpy(lwip_udp_recv_buf,p->payload,200);}else{ memcpy(lwip_udp_recv_buf,p->payload,p->tot_len);lwip_udp_recv_buf[p->tot_len] = 0; }lwip_udp_recv_flag |= LWIP_NEW_DATA; //收到了新的數據udp_server_pcb->remote_ip = *addr; //記錄遠程主機的IP和端口號udp_server_pcb->remote_port = port;pbuf_free(p);} }//發送數據 void Udp_Server_Send_Data(void) {err_t err;if((lwip_udp_recv_flag&LWIP_SEND_DATA)==LWIP_SEND_DATA){ubuf = pbuf_alloc(PBUF_TRANSPORT, strlen((char *)lwip_udp_recv_buf), PBUF_RAM); ubuf->payload = lwip_udp_recv_buf;err=udp_send(udp_server_pcb,ubuf);//發送數據if(err!=ERR_OK){//do something }lwip_udp_recv_flag &=~LWIP_SEND_DATA; //清除發送數據的標志 pbuf_free(ubuf);} }//初始化UDP服務器 void Init_UDP_Server(void) {udp_server_pcb = udp_new(); //新建一個UDP協議控制塊if(udp_server_pcb!=NULL){udp_bind(udp_server_pcb,IP_ADDR_ANY,UDP_RECV_PORT); //監聽一個UDP端口udp_recv(udp_server_pcb,Udp_Server_Recv,NULL); //指定收到數據包時的回調函數 } }可以看到,UDP的服務器在使用的時候沒有主動去連接那個IP,他只是自己綁定了自己的某個端口,算是客戶端和服務器編程的兩種差異
給一個使用文件
#include "mainInclude.h"#include "lwip/init.h" #include "lwip/ip.h" #include "lwip/dhcp.h" #include "lwip/tcp_impl.h" #include "lwip/ip_frag.h" #include "lwip/dns.h" #include "netif/etharp.h" #include "netif/ethernetif.h" #include "arch/sys_arch.h"#define CLOCKTICKS_PER_MS 10 //定義時鐘節拍static ip_addr_t ipaddr, netmask, gw; //定義IP地址 struct netif enc28j60_netif; //定義網絡接口 u32_t input_time; u32_t last_arp_time; u32_t last_tcp_time; u32_t last_ipreass_time;u32_t last_dhcp_fine_time; u32_t last_dhcp_coarse_time; u32 dhcp_ip=0;//LWIP查詢 void LWIP_Polling(void) {if(timer_expired(&input_time,5)) //接收包,周期處理函數 {ethernetif_input(&enc28j60_netif); }if(timer_expired(&last_tcp_time,TCP_TMR_INTERVAL/CLOCKTICKS_PER_MS))//TCP處理定時器處理函數 {tcp_tmr();}if(timer_expired(&last_arp_time,ARP_TMR_INTERVAL/CLOCKTICKS_PER_MS))//ARP處理定時器 {etharp_tmr();}if(timer_expired(&last_ipreass_time,IP_TMR_INTERVAL/CLOCKTICKS_PER_MS))//IP重新組裝定時器 { ip_reass_tmr();} }int main(void) {u8 t_client_cnt = 0;u8 t_server_cnt = 0;u8 t_send_cnt = 0;u8 t_recv_cnt = 0;NVIC_Group_Init();//系統默認中斷分組Debug_Serial_Init(115200);Delay_Init();Led_Init();Key_Exti_Init();LCD_Init();LCD_Clear(LCD_BLACK);IP4_ADDR(&ipaddr, 192, 168, 1, 110); //設置本地ip地址IP4_ADDR(&gw, 192, 168, 1, 1); //網關IP4_ADDR(&netmask, 255, 255, 255, 0); //子網掩碼 //初始化LWIP定時器 init_lwip_timer(); //初始化LWIP協議棧,執行檢查用戶所有可配置的值,初始化所有的模塊 lwip_init();//添加網絡接口while((netif_add(&enc28j60_netif, &ipaddr, &netmask, &gw, NULL, ðernetif_init, ðernet_input)==NULL)){LCD_ShowString(0,0,240,320,(u8*)"ENC28J60 Init Failed ",LCD_BLACK);Delay_Ms(200);LCD_ShowString(0,0,240,320,(u8*)" ",LCD_BLACK);Delay_Ms(200);}LCD_ShowString(0,0,240,320,(u8*)"ENC28J60 Init OK ",LCD_BLACK);//注冊默認的網絡接口netif_set_default(&enc28j60_netif);//建立網絡接口用于處理通信netif_set_up(&enc28j60_netif); Tcp_Client_Init();//初始化tcp客戶端 LCD_ShowString(0,12,240,320,(u8*)"TCP CLIENT INIT ",LCD_BLACK);LCD_ShowString(0,24,240,320,(u8*)"TCP CLIENT Disconnect ",LCD_BLACK);LCD_ShowString(0,36,240,320,(u8*)"RX: ",LCD_BLACK);LCD_ShowString(0,48,240,320,(u8*)"TX: ",LCD_BLACK);Init_TCP_Server();LCD_ShowString(0,60,240,320,(u8*)"TCP SERVER INIT ",LCD_BLACK);LCD_ShowString(0,72,240,320,(u8*)"TCP SERVER Disconnect ",LCD_BLACK);LCD_ShowString(0,84,240,320,(u8*)"RX: ",LCD_BLACK);LCD_ShowString(0,96,240,320,(u8*)"TX: ",LCD_BLACK);Init_UDP_Client();LCD_ShowString(0,108,240,320,(u8*)"UDP SEND INIT ",LCD_BLACK);LCD_ShowString(0,120,240,320,(u8*)"RX: ",LCD_BLACK);LCD_ShowString(0,132,240,320,(u8*)"TX: ",LCD_BLACK);Init_UDP_Server();//初始化UDP接收端LCD_ShowString(0,144,240,320,(u8*)"UDP RECV INIT ",LCD_BLACK);LCD_ShowString(0,156,240,320,(u8*)"RX: ",LCD_BLACK);LCD_ShowString(0,168,240,320,(u8*)"TX: ",LCD_BLACK);while(1){LWIP_Polling();if((lwip_tcp_client_flag&LWIP_CONNECTED)==LWIP_CONNECTED){LCD_ShowString(0,24,240,320,(u8*)"TCP CLIENT Connect ",LCD_BLACK);if(keyValue == KEY_RIGHT){t_client_cnt++;sprintf((char*)lwip_client_buf,"tcp_client send %d\r\n",t_client_cnt); LCD_ShowString(18,48,240,320,(u8*)lwip_client_buf,LCD_BLACK);//顯示當前發送數據lwip_tcp_client_flag |= LWIP_SEND_DATA; //標記有數據需要發送keyValue = 0;}}else{ // Tcp_Client_Connect_Remotehost();//沒有連接上,此時處于TCP客戶端模式,則嘗試重新連接 }if((lwip_tcp_client_flag&LWIP_NEW_DATA)==LWIP_NEW_DATA){LCD_ShowString(18,36,240,320,(u8*)lwip_client_buf,LCD_BLACK);lwip_tcp_client_flag &=~LWIP_NEW_DATA; //清除接受數據的標志 }if((lwip_tcp_service_flag&LWIP_CONNECTED)==LWIP_CONNECTED){LCD_ShowString(0,72,240,320,(u8*)"TCP SERVER Connect ",LCD_BLACK);if(keyValue == KEY_LEFT){t_server_cnt++;sprintf((char*)lwip_service_buf,"tcp_service send %d\r\n",t_server_cnt); LCD_ShowString(18,96,240,320,(u8*)lwip_service_buf,LCD_BLACK);//顯示當前發送數據lwip_tcp_service_flag |= LWIP_SEND_DATA; //標記有數據需要發送keyValue = 0;}}if((lwip_tcp_service_flag&LWIP_NEW_DATA)==LWIP_NEW_DATA){LCD_ShowString(18,84,240,320,(u8*)lwip_service_buf,LCD_BLACK);lwip_tcp_service_flag &=~LWIP_NEW_DATA; //清除接受數據的標志 }if(keyValue == KEY_UP){t_send_cnt++;sprintf((char*)lwip_udp_send_buf,"udp_send send %d\r\n",t_send_cnt); LCD_ShowString(18,132,240,320,(u8*)lwip_udp_send_buf,LCD_BLACK);//顯示當前發送數據lwip_udp_send_flag |= LWIP_SEND_DATA; //標記有數據需要發送keyValue = 0;udp_client_send_data();}if((lwip_udp_send_flag&LWIP_NEW_DATA)==LWIP_NEW_DATA){LCD_ShowString(18,120,240,320,(u8*)lwip_udp_send_buf,LCD_BLACK);lwip_udp_send_flag &=~LWIP_NEW_DATA; //清除接受數據的標志 }if(keyValue == KEY_DOWN){t_recv_cnt++;sprintf((char*)lwip_udp_recv_buf,"udp_recv send %d\r\n",t_send_cnt); LCD_ShowString(18,168,240,320,(u8*)lwip_udp_recv_buf,LCD_BLACK);//顯示當前發送數據lwip_udp_recv_flag |= LWIP_SEND_DATA; //標記有數據需要發送keyValue = 0;Udp_Server_Send_Data();}if((lwip_udp_recv_flag&LWIP_NEW_DATA)==LWIP_NEW_DATA){LCD_ShowString(18,156,240,320,(u8*)lwip_udp_recv_buf,LCD_BLACK);lwip_udp_recv_flag &=~LWIP_NEW_DATA; //清除接受數據的標志 }Delay_Ms(1);} }void USB_LP_CAN1_RX0_IRQHandler(void) {}void USBWakeUp_IRQHandler(void) { // EXTI->PR|=1<<18;//清除USB喚醒中斷掛起位 }
完整的工程路徑如下
?
轉載于:https://www.cnblogs.com/dengxiaojun/p/4385491.html
總結
以上是生活随笔為你收集整理的LWIP裸机环境下实现TCP与UDP通讯的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: (数据库系统概论|王珊)第二章关系数据库
- 下一篇: 计组之总线:1、总线(分类、特性、定义、