uip-udp-demo分析---基于contiki
demo
#define PORT 12345 //通信端口設置 //udp數據結構 static struct udp_socket s; //uip的ip地址 static uip_ipaddr_t addr;static struct uip_ds6_notification n; //定義變量i static uint8_t i=0; //設置發送時間間隔 #define SEND_INTERVAL (10 * CLOCK_SECOND) //定義周期事件定時器,發送事件定時器 static struct etimer periodic_timer, send_timer;/*---------------------------------------------------------------------------*/ //聲明unicast_example_process 進程 PROCESS(unicast_example_process, "Link local unicast example process"); //系統初始化啟動unicast_example_process進程 AUTOSTART_PROCESSES(&unicast_example_process);/*---------------------------------------------------------------------------*/ //路由回調函數,處理路由事件 static void route_callback(int event, uip_ipaddr_t *route, uip_ipaddr_t *ipaddr,int numroutes) {if(event == UIP_DS6_NOTIFICATION_DEFRT_ADD) {leds_off(LEDS_ALL);printf("Got a RPL route\n");} }/*---------------------------------------------------------------------------*/ //接收函數 static void receiver(struct udp_socket *c,void *ptr,const uip_ipaddr_t *sender_addr,uint16_t sender_port,const uip_ipaddr_t *receiver_addr,uint16_t receiver_port,const uint8_t *data,uint16_t datalen) {printf("Data received on port %d from port %d with length %d, '%s'\n",receiver_port, sender_port, datalen, data); } /*---------------------------------------------------------------------------*/ //unicast_example_process進程實現 PROCESS_THREAD(unicast_example_process, ev, data) {//定義變量保存ipv6的地址uip_ip6addr_t ip6addr;//定義變量保存ipv4的地址uip_ip4addr_t ip4addr;//進程開始PROCESS_BEGIN(); #if 0/* Create a linkl-local multicast addresses. */uip_ip6addr(&addr, 0xff02, 0, 0, 0, 0, 0, 0x1337, 0x0001);/* Join local group. */if(uip_ds6_maddr_add(&addr) == NULL) {printf("Error: could not join local multicast group.\n");} #endif//調用回調函數leds_on(LEDS_ALL);//調用回調函數uip_ds6_notification_add(&n, route_callback);/* Register UDP socket callback *///注冊udp接收回調函數udp_socket_register(&s, NULL, receiver);/* Bind UDP socket to local port *///端口綁定udp_socket_bind(&s, PORT);/* Connect UDP socket to remote port *///連接服務器udp_socket_connect(&s, NULL, PORT);while(1) {/* Set up two timers, one for keeping track of the send interval,which is periodic, and one for setting up a randomized send timewithin that interval. */etimer_set(&periodic_timer, SEND_INTERVAL);//etimer_set(&send_timer, (random_rand() % SEND_INTERVAL));PROCESS_WAIT_UNTIL(etimer_expired(&periodic_timer));uip_ipaddr(&ip4addr, 192,168,18,86);ip64_addr_4to6(&ip4addr, &ip6addr);printf("Sending unicast %d\n",i);i++;//發送數據udp_socket_sendto(&s,&i, 1,&ip6addr, PORT);//PROCESS_WAIT_UNTIL(etimer_expired(&periodic_timer));}PROCESS_END(); }1、static struct udp_socket s;
這里是定義了uip內的一個UDP結構體,深入udp_socket;
struct udp_socket {udp_socket_input_callback_t input_callback;void *ptr;struct process *p;struct uip_udp_conn *udp_conn;};這里又涉及到結構體內第一個 callback 函數,為了弄清input_callback函數,這里有必要去了解dup_socket_input_callback_t 這個的定義
typedef void (* udp_socket_input_callback_t)(struct udp_socket *c,void *ptr,const uip_ipaddr_t *source_addr,uint16_t source_port,const uip_ipaddr_t *dest_addr,uint16_t dest_port,const uint8_t *data,uint16_t datalen); /*typedef定義一種新類型 udp_socket_input_callback_t,并定義這種類型為指向某種函數的指針, 這種函數以后面的8個數據為參數并返回void類型。后面就可以像使用int,char一樣使用udp_socket_input_callback_t了*/返回上一層,那么input_callback變量就是指向一個函數A,函數A返回值為void,形參為上面的8個參數;
繼續udp_socket的研究,第二行定義了一個指針ptr,可以指向任意類型的數據,先不討論指向的是哪,繼續下一行;
struct process *p :這里p指向進程結構體,保存進程信息,這也是一個鏈表保存;
struct uip_udp_conn *udp_conn :這里需要了解uip_udp_conn,給出的注釋是Representation of a uIP UDP connection.具體看代碼:
struct uip_udp_conn {uip_ipaddr_t ripaddr; /**< The IP address of the remote peer.這個uip_ipaddr_t在配置使用ipv4還是ipv6時,就定了這個地址類型 * /uint16_t lport; /**< The local port number in network byte order. */uint16_t rport; /**< The remote port number in network byte order. */uint8_t ttl; /**< Default time-to-live. *//** The application state. */uip_udp_appstate_t appstate; };對于上面代碼中的uip_udp_appstate_t 類型,進行查看,給出的定義是
typedef struct tcpip_uipstate uip_udp_appstate_t;也就是說,uip_udp_appstate_t 是一個tcpip_uipstate類型的結構體變量
而繼續研究tcpip_uipstate結構體
struct tcpip_uipstate { struct process *p; void *state; };這里又一個process ,指向進程結構體,保存進程信息,那么這個進程會是什么進程呢??? 同時定義了一個空類型指針;
以上,結構體 udp_socket分析完畢,這里遺留了一個問題,,udp_socket中 *p指向的process進程,在udp_conn中,也有一個指針指向該porcess。如下圖
2、static uip_ipaddr_t addr;
uip地址變量
3、static struct uip_ds6_notification n;
這里定義一個靜態結構體n,類型為uip_ds6_notification;
這里研究uip_ds6_notification這個結構體類型
同樣,一個next指針,然后這里又一個callback,callback指向一個函數(指向下文定義的route_callback())
1 typedef void (* uip_ds6_notification_callback)(int event, 2 uip_ipaddr_t *route, 3 uip_ipaddr_t *nexthop, 4 int num_routes);這里回調函數有四個參數,對于該回調,深入一層代碼
1 static void2 call_route_callback(int event, uip_ipaddr_t *route,3 uip_ipaddr_t *nexthop)4 {5 int num;6 struct uip_ds6_notification *n;7 for(n = list_head(notificationlist);8 n != NULL;9 n = list_item_next(n)) { 10 if(event == UIP_DS6_NOTIFICATION_DEFRT_ADD || 11 event == UIP_DS6_NOTIFICATION_DEFRT_RM) { 12 num = list_length(defaultrouterlist); 13 } else { 14 num = num_routes; 15 } 16 n->callback(event, route, nexthop, num); 17 } 18 }給出的注釋 大致意思是當有路由加入或者退出時,上層可以通過注冊回調函數,然后uip_ds6_route(uip_ds6_notification定義在uip_ds6_route.h內)就會通知上層發生了路由改變事件。這里還不是很理解回調函數,看了一個大佬對于回調的通俗解釋——比如QQ聊天窗口的發送按鈕,預先綁定某個函數OnSendClicked,你點擊了發送按鈕,函數就會被調用,這就是回調函數。
4、接下來看route_callback()
static void route_callback(int event, uip_ipaddr_t *route, uip_ipaddr_t *ipaddr,int numroutes) {if(event == UIP_DS6_NOTIFICATION_DEFRT_ADD) {leds_off(LEDS_ALL);printf("Got a RPL route\n");} }這個函數功能很簡單,只是這個節點連接上一個路由節點后,燈暗,并且打印調試信息;
不過,這個函數的參數與之前的結構體n的成員相同,為什么???;接著向下看;
5、receiver()
static void receiver(struct udp_socket *c,void *ptr,const uip_ipaddr_t *sender_addr,uint16_t sender_port,const uip_ipaddr_t *receiver_addr,uint16_t receiver_port,const uint8_t *data,uint16_t datalen) {printf("Data received on port %d from port %d with length %d, '%s'\n",receiver_port, sender_port, datalen, data); }這兩個函數都很溫柔,至少看起來功能明了,繼續看下去;
PROCESS BEGIN()之后
6、uip_ds6_notification_add(&n, route_callback)
static struct uip_ds6_notification n;route_callback,就是上面的函數;
這里看看uip_ds6_notification_add()
1 void 2 uip_ds6_notification_add(struct uip_ds6_notification *n, 3 uip_ds6_notification_callback c) 4 { 5 if(n != NULL && c != NULL) { 6 n->callback = c; 7 list_add(notificationlist, n); 8 } 9 }來分析這個函數吧,如果結構體n存在并且c存在,這里
到了這里,就可以理解了 前面的結構體n
struct uip_ds6_notification {struct uip_ds6_notification *next;uip_ds6_notification_callback callback; };里面的callback,后面會被賦予一個執行一個動作的函數,這里也就能理解4后面的疑問了,因為route_callback會被賦予給n->callback;
接著分析 list_add(notificationlist, n)
這里重點是要弄清楚notification 的定義
查看nitificationlist的定義,有以下代碼:
既然都看到這里了,硬著頭皮繼續去看LIST()的定義吧
#define LIST(name) \static void *LIST_CONCAT(name,_list) = NULL; \static list_t name = (list_t)&LIST_CONCAT(name,_list)暈(((φ(◎ロ◎;)φ)))
#define LIST_CONCAT2(s1, s2) s1##s2 #define LIST_CONCAT(s1, s2) LIST_CONCAT2(s1, s2)這個s1##s2,實在不理解,沒辦法 有道了一下“concat”是合并數組的意思,這s1##s2,我實在沒有找到注釋,那么暫且理解為合并s1與s2.
繼續暈
那么這里把LIST(notification)替換掉,應該是如下所示的代碼了
這里的list_t是一個鏈表類型指針 ,聲明如下:
typedef void ** list_t;ok,還有不理解的地方,但是大體意思明白了,就是利用了一個鏈表,其實從函數名就比較能直觀的了解函數功能。
ok,uip_ds6_notification_add(&n, route_callback)這個函數算是看明白了,當接入一個路由時,底層需要通知上層,有一個節點加入啦,同時,上層還要執行之前定義的route_callback()函數。
7、udp_socket_register(&s, NULL, receiver);
int udp_socket_register(struct udp_socket *c,void *ptr,udp_socket_input_callback_t input_callback) {init();if(c == NULL) {return -1;}c->ptr = ptr;c->input_callback = input_callback;c->p = PROCESS_CURRENT();PROCESS_CONTEXT_BEGIN(&udp_socket_process);c->udp_conn = udp_new(NULL, 0, c);PROCESS_CONTEXT_END();if(c->udp_conn == NULL) {return -1;}return 1; }當udp_socket或者udp_conn == NULL時,返回-1;
c-p = PROCESS_CURRENT();
這里看原文注釋
get a pointer to the currently running process.
前面已經提過,這里udp_socket中的p 指向的是進程信息,以保護進程;畢竟一個udp_socket來臨,加入,進程可能打亂,有點進棧出棧以保護進程的意思哈!來看下一句
PROCESS_CONTEXT_BEGIN(&udp_socket_process);
/*** Switch context to another process** This function switch context to the specified process and executes* the code as if run by that process. Typical use of this function is* to switch context in services, called by other processes. Each* PROCESS_CONTEXT_BEGIN() must be followed by the* PROCESS_CONTEXT_END() macro to end the context switch.** Example:\codePROCESS_CONTEXT_BEGIN(&test_process);etimer_set(&timer, CLOCK_SECOND);PROCESS_CONTEXT_END(&test_process);\endcode** \param p The process to use as context** \sa PROCESS_CONTEXT_END()* \sa PROCESS_CURRENT()*/#define PROCESS_CONTEXT_BEGIN(p) {\ struct process *tmp_current = PROCESS_CURRENT();\ process_current = p這里注釋就很明了了,進出棧以交換進程,不妨理解成中斷,開始進行udp_socket_process;
下一句:
c->udp_conn = udp_new(NULL, 0, c);
這就比較好理解了,上面畫有udp_conn的圖解,這里是加入了一個udp_conn;
PROCESS_CONTEXT_END();
這個比較好理解了,就是進程切換回去,為了提高專業性,貼上代碼
#define PROCESS_CONTEXT_END(p) process_current = tmp_current; }不得不說,這里的編程風格真是我輩楷模,上面兩個宏定義,實在厲害!
總結來看udp_socket_register(&s, NULL, receiver);
無非是進程中斷,來執行udp_socket的注冊,有進程切換操作,也有一個回調函數,上面的分析里面沒有具體提及,因為在分析uip_ds6_notification_add(&n, route_callback)時,已經分析了,這里無非是,當接收到udp_socket時,執行這個receiver()函數;
8、udp_socket_bind(&s, PORT);
這個函數功能比較好理解,進行端口綁定
int udp_socket_bind(struct udp_socket *c,uint16_t local_port) {if(c == NULL || c->udp_conn == NULL) {return -1;}udp_bind(c->udp_conn, UIP_HTONS(local_port));return 1; }為了分析這個函數,這里需要查看udp_bind()和UIP_HTONS();
先看udp_bind()
udp_bind()
/*** Bind a UDP connection to a local port.** This function binds a UDP connection to a specified local port.** When a connection is created with udp_new(), it gets a local port* number assigned automatically. If the application needs to bind the* connection to a specified local port, this function should be used.** \note The port number must be provided in network byte order so a* conversion with UIP_HTONS() usually is necessary.** \param conn A pointer to the UDP connection that is to be bound.* \param port The port number in network byte order to which to bind* the connection.*/ #define udp_bind(conn, port) uip_udp_bind(conn, port)。。。
其實功能好理解,但是還想繼續到底層看
那就,看一看
可以,這個函數抽絲剝繭之后,就是很簡單一句話。
UIP_HTONS();
/*** Convert 16-bit quantity from host byte order to network byte order.** This macro is primarily used for converting constants from host* byte order to network byte order. For converting variables to* network byte order, use the uip_htons() function instead.** \hideinitializer*/ #ifndef UIP_HTONS # if UIP_BYTE_ORDER == UIP_BIG_ENDIAN # define UIP_HTONS(n) (n) # define UIP_HTONL(n) (n) # else /* UIP_BYTE_ORDER == UIP_BIG_ENDIAN */ # define UIP_HTONS(n) (uint16_t)((((uint16_t) (n)) << 8) | (((uint16_t) (n)) >> 8)) # define UIP_HTONL(n) (((uint32_t)UIP_HTONS(n) << 16) | UIP_HTONS((uint32_t)(n) >> 16)) # endif /* UIP_BYTE_ORDER == UIP_BIG_ENDIAN */ #else #error "UIP_HTONS already defined!" #endif /* UIP_HTONS */這個就比較好理解了,其實加個函數,就是為了port值滿足一下uint16_t這個類型;
果然分析這么一大段,其實就一個東西,綁定一下端口。
9、udp_socket_connect(&s, NULL, PORT)
emmmm,先看代碼
int udp_socket_connect(struct udp_socket *c,uip_ipaddr_t *remote_addr,uint16_t remote_port) {if(c == NULL || c->udp_conn == NULL) {return -1;}if(remote_addr != NULL) {uip_ipaddr_copy(&c->udp_conn->ripaddr, remote_addr);}c->udp_conn->rport = UIP_HTONS(remote_port);return 1; }這里居然沒有一個回調函數,哈哈哈哈,應該只要看一個函數就夠理解這個東西了,uip_ipaddr_copy()
uip_ipaddr_copy()
/*** Copy an IP address from one place to another.** Copies an IP address from one place to another.** Example:\codeuip_ipaddr_t ipaddr1, ipaddr2;uip_ipaddr(&ipaddr1, 192,16,1,2);uip_ipaddr_copy(&ipaddr2, &ipaddr1);\endcode** \param dest The destination for the copy.* \param src The source from where to copy.** \hideinitializer*/ #ifndef uip_ipaddr_copy #define uip_ipaddr_copy(dest, src) (*(dest) = *(src)) #endif #ifndef uip_ip4addr_copy #define uip_ip4addr_copy(dest, src) (*((uip_ip4addr_t *)dest) = *((uip_ip4addr_t *)src)) #endif #ifndef uip_ip6addr_copy #define uip_ip6addr_copy(dest, src) (*((uip_ip6addr_t *)dest) = *((uip_ip6addr_t *)src)) #endif這里其實是個宏定義,我就說這里怎么沒個回調函數呢,果然adam的代碼就是讓人驚奇!
這里有個坑,這里remote_addr是NULL的,也就是說還不能連接上遠程服務器,撐死叫做連接遠程服務器端口;
不過這個函數功能的確是連接遠程服務器的
通過從6~~~~~9這幾個函數,udp已經半連接上了
10、uip_ipaddr(&ip4addr, 192,168,0,114)
/*** Construct an IP address from four bytes.** This function constructs an IP address of the type that uIP handles* internally from four bytes. The function is handy for specifying IP* addresses to use with e.g. the uip_connect() function.** Example:\codeuip_ipaddr_t ipaddr;struct uip_conn *c;uip_ipaddr(&ipaddr, 192,168,1,2);c = uip_connect(&ipaddr, UIP_HTONS(80));\endcode** \param addr A pointer to a uip_ipaddr_t variable that will be* filled in with the IP address.** \param addr0 The first octet of the IP address.* \param addr1 The second octet of the IP address.* \param addr2 The third octet of the IP address.* \param addr3 The forth octet of the IP address.** \hideinitializer*/ #define uip_ipaddr(addr, addr0,addr1,addr2,addr3) do { \(addr)->u8[0] = addr0; \(addr)->u8[1] = addr1; \(addr)->u8[2] = addr2; \(addr)->u8[3] = addr3; \} while(0) /** \brief 16 bit 802.15.4 address */ typedef struct uip_802154_shortaddr {uint8_t addr[2]; } uip_802154_shortaddr; /** \brief 64 bit 802.15.4 address */ typedef struct uip_802154_longaddr {uint8_t addr[8]; } uip_802154_longaddr;/** \brief 802.11 address */ typedef struct uip_80211_addr {uint8_t addr[6]; } uip_80211_addr;/** \brief 802.3 address */ typedef struct uip_eth_addr {uint8_t addr[6]; } uip_eth_addr;沒什么好說 的 規范IP格式,包括下面的ip64_addr_4to6(&ip4addr, &ip6addr)
ip64_addr_4to6(&ip4addr, &ip6addr)
這里想看看addr轉換方式
int ip64_addr_4to6(const uip_ip4addr_t *ipv4addr,uip_ip6addr_t *ipv6addr) {/* This function converts an IPv4 addresses into an IPv6addresses. It returns 0 if it failed to convert the address andnon-zero if it could successfully convert the address. *//* The IPv4 address is encoded as an IPv6-encoded IPv4 address inthe ::ffff:0000/24 prefix.*/ipv6addr->u8[0] = 0;ipv6addr->u8[1] = 0;ipv6addr->u8[2] = 0;ipv6addr->u8[3] = 0;ipv6addr->u8[4] = 0;ipv6addr->u8[5] = 0;ipv6addr->u8[6] = 0;ipv6addr->u8[7] = 0;ipv6addr->u8[8] = 0;ipv6addr->u8[9] = 0;ipv6addr->u8[10] = 0xff;ipv6addr->u8[11] = 0xff;ipv6addr->u8[12] = ipv4addr->u8[0];ipv6addr->u8[13] = ipv4addr->u8[1];ipv6addr->u8[14] = ipv4addr->u8[2];ipv6addr->u8[15] = ipv4addr->u8[3];printf("ip64_addr_4to6: IPv6-encoded IPv4 address %d.%d.%d.%d\n",ipv4addr->u8[0], ipv4addr->u8[1],ipv4addr->u8[2], ipv4addr->u8[3]);/* Conversion succeeded, we return non-zero. */return 1; }研究一下這里的ipv4轉換為ipv6的規則
ipv4是32個字節;ipv6是128個字節
由于ipv4點分成了四段,因此每段8字節;
根據代碼;
前十段都是0000h;地十一段是0ffffh;后面還剩四段,給ipv4地址來填入,這里數值如實填入,沒有十進制與16進制的轉換,不太明白,估計是把數值當符號了 只有指代作用,沒有數值作用。
11、udp_socket_sendto(&s,&i, 1,&ip6addr, PORT)
int udp_socket_sendto(struct udp_socket *c,const void *data, uint16_t datalen,const uip_ipaddr_t *to,uint16_t port) {if(c == NULL || c->udp_conn == NULL) {return -1;}if(c->udp_conn != NULL) {uip_udp_packet_sendto(c->udp_conn, data, datalen,to, UIP_HTONS(port));return datalen;}return -1; }這里要看uip_udp_packet_sendto()
uip_udp_packet-sendto()
void uip_udp_packet_sendto(struct uip_udp_conn *c, const void *data, int len,const uip_ipaddr_t *toaddr, uint16_t toport) {uip_ipaddr_t curaddr;uint16_t curport;if(toaddr != NULL) {/* Save current IP addr/port. */uip_ipaddr_copy(&curaddr, &c->ripaddr);curport = c->rport;/* Load new IP addr/port */uip_ipaddr_copy(&c->ripaddr, toaddr);c->rport = toport;uip_udp_packet_send(c, data, len);/* Restore old IP addr/port */uip_ipaddr_copy(&c->ripaddr, &curaddr);c->rport = curport;} }這里需要去看其中的uip_udp_packet_send()
/*---------------------------------------------------------------------------*/ void uip_udp_packet_send(struct uip_udp_conn *c, const void *data, int len) { #if UIP_UDP //UIP_UDP == 1if(data != NULL) {uip_udp_conn = c;uip_slen = len;memcpy(&uip_buf[UIP_LLH_LEN + UIP_IPUDPH_LEN], data,len > UIP_BUFSIZE - UIP_LLH_LEN - UIP_IPUDPH_LEN?UIP_BUFSIZE - UIP_LLH_LEN - UIP_IPUDPH_LEN: len);uip_process(UIP_UDP_SEND_CONN);#if UIP_CONF_IPV6_MULTICAST //UIP_CONF_IPV6_MUTICAST == 0/* Let the multicast engine process the datagram before we send it */if(uip_is_addr_mcast_routable(&uip_udp_conn->ripaddr)) {UIP_MCAST6.out();} #endif /* UIP_IPV6_MULTICAST */#if NETSTACK_CONF_WITH_IPV6 //NETSTACK_CONF_WITH_IPV6 == 0tcpip_ipv6_output(); #elseif(uip_len > 0) {tcpip_output();} #endif}uip_slen = 0; #endif /* UIP_UDP */ }這里面又有幾個函數需要分析
1、memcpy();memcpy指的是C和C++使用的內存拷貝函數,函數原型為void *memcpy(void *destin, void *source, unsigned n);函數的功能是從源內存地址的起始位置開始拷貝若干個字節到目標內存地址中,即從源source中拷貝n個字節到目標destin中。
memcpy(&uip_buf[UIP_LLH_LEN + UIP_IPUDPH_LEN], data,len > UIP_BUFSIZE - UIP_LLH_LEN - UIP_IPUDPH_LEN?UIP_BUFSIZE - UIP_LLH_LEN - UIP_IPUDPH_LEN: len);把數據拷貝到uip_buf區 即,數據緩沖區。
分別查看以下定義
2、uip_process();
uip_process(UIP_UDP_SEND_CONN);
下次解讀 ,這個實在太多
總結
以上是生活随笔為你收集整理的uip-udp-demo分析---基于contiki的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据分析A/BTest之APP页面
- 下一篇: linux编写自动运行一串命令的脚本