AVRNET 學(xué)習(xí)筆記UDP部分
1前言
UDP協(xié)議全稱為用戶數(shù)據(jù)協(xié)議,是一種簡(jiǎn)單有效的運(yùn)輸協(xié)議。和以太網(wǎng)首部和IP首部相似,UDP首部也有自身的數(shù)據(jù)結(jié)構(gòu)定義。從運(yùn)輸協(xié)議開始引入端口的概念,端口相當(dāng)于一個(gè)應(yīng)用程序的標(biāo)識(shí)符。相對(duì)于TCP協(xié)議而言,UDP協(xié)議簡(jiǎn)單的多。本文將實(shí)現(xiàn)UDP協(xié)議,并通過(guò)幾個(gè)簡(jiǎn)單的案例說(shuō)明UDP的使用。
1.2 相關(guān)資料
ENC28J60學(xué)習(xí)筆記鏈接
http://www.amobbs.com/thread-5519381-1-1.html
AVRNET學(xué)習(xí)筆記 ETHERNET和ARP部分
http://www.amobbs.com/thread-5519452-1-1.html
AVRNET學(xué)習(xí)筆記 IP和ICMP部分
http://www.amobbs.com/thread-5519494-1-1.html
AVRNET項(xiàng)目(國(guó)外)
http://www.avrportal.com/?page=avrnet
AVR webserver項(xiàng)目(國(guó)外)
http://www.tuxgraphics.org/electronics/200611/article06111.shtml#0lfindex0
2 UDP部分實(shí)現(xiàn)
UDP功能的實(shí)現(xiàn)可分為UDP首部填充,UDP緩沖區(qū)填充和UDP報(bào)文查詢。UDP首部填充是一個(gè)按部就班的過(guò)程,即填充源端口,目標(biāo)端口,長(zhǎng)度和校驗(yàn)和。UDP緩沖區(qū)填充即往UDP負(fù)載部分逐個(gè)填充數(shù)據(jù)。UDP報(bào)文查詢功能即匹配本機(jī)UDP端口號(hào),并進(jìn)行函數(shù)處理。為了實(shí)現(xiàn)這些功能,首先需要以下宏定義。需要注意的是以太網(wǎng)傳輸協(xié)議中數(shù)據(jù)被以大端的形式保存,即低地址存放了高字節(jié)。例如端口號(hào)的高字節(jié)存放在了0x22的位置,而端口號(hào)的低字節(jié)存放在了0x23的位置。
// UDP默認(rèn)端口號(hào)
#define UDP_AVR_PORT_V 3000
#define UDP_AVR_PORT_H_V(UDP_AVR_PORT_V>>8)
#define UDP_AVR_PORT_L_V(UDP_AVR_PORT_V&0xff)
// 源端口
#define UDP_SRC_PORT_H_P0x22
#define UDP_SRC_PORT_L_P0x23
// 目標(biāo)端口
#define UDP_DST_PORT_H_P0x24
#define UDP_DST_PORT_L_P0x25
// UDP負(fù)載長(zhǎng)度
#define UDP_LENGTH_H_P 0x26
#define UDP_LENGTH_L_P 0x27
// UDP校驗(yàn)和
#define UDP_CHECKSUM_H_P0x28
#define UDP_CHECKSUM_L_P0x29
// UDP負(fù)載起始地址
#define UDP_DATA_P 0x2A
2.1 UDP首部填充
UDP首部填充中需要明確UDP的端口號(hào),AVRNET項(xiàng)目中通過(guò)常數(shù)宏定義實(shí)現(xiàn)
#define UDP_AVR_PORT_V 3000
#define UDP_AVR_PORT_H_V(UDP_AVR_PORT_V>>8)
#define UDP_AVR_PORT_L_V(UDP_AVR_PORT_V&0xff)
從這段代碼中可以看出,AVRNET的UDP端口號(hào)為3000。
void udp_generate_header ( BYTE *rxtx_buffer, WORD_BYTES dest_port, WORD_BYTES length )
{
WORD_BYTES ck;
// 默認(rèn)端口號(hào) 3000
rxtx_buffer[UDP_SRC_PORT_H_P] = UDP_AVR_PORT_H_V;
rxtx_buffer[UDP_SRC_PORT_L_P] = UDP_AVR_PORT_L_V;
// 目標(biāo)端口地址
rxtx_buffer[UDP_DST_PORT_H_P] = dest_port.byte.high;
rxtx_buffer[UDP_DST_PORT_L_P] = dest_port.byte.low;
// 負(fù)載長(zhǎng)度
rxtx_buffer[UDP_LENGTH_H_P] = length.byte.high;
rxtx_buffer[UDP_LENGTH_L_P] = length.byte.low;
// 計(jì)算校驗(yàn)和
rxtx_buffer[UDP_CHECKSUM_H_P] = 0;
rxtx_buffer[UDP_CHECKSUM_L_P] = 0;
// length+8 for source/destination IP address length (8-bytes)
ck.word = software_checksum ( (BYTE*)&rxtx_buffer[IP_SRC_IP_P], length.word+8, length.word+IP_PROTO_UDP_V);
rxtx_buffer[UDP_CHECKSUM_H_P] = ck.byte.high;
rxtx_buffer[UDP_CHECKSUM_L_P] = ck.byte.low;
}
復(fù)制代碼
2.2 UDP負(fù)載長(zhǎng)度查詢
UDP首部中包含UDP長(zhǎng)度的描述字節(jié),長(zhǎng)度占有兩個(gè)字節(jié)并以大端格式保存,由于宏定義的提示作用,弱化了大端格式的影響。長(zhǎng)度中也包括了UDP首部的長(zhǎng)度,UDP首部的長(zhǎng)度為固定的8字節(jié)。
WORD udp_get_dlength( BYTE *rxtx_buffer )
{
WORD length = 0;
// 獲得UDP長(zhǎng)度
length = rxtx_buffer[UDP_LENGTH_H_P] << 8 | rxtx_buffer[UDP_LENGTH_L_P];
// 去除首部長(zhǎng)度
length = length - 8;
return length;
}
2.3 UDP負(fù)載區(qū)填充
UDP負(fù)載去填充即在UDP首部之后填充有用的數(shù)據(jù)。在這段真實(shí)負(fù)載之前包括了UDP首部,IP首部和以太網(wǎng)首部,分別占用了8字節(jié),20字節(jié)和14字節(jié)。UDP負(fù)載的起始地址通過(guò)宏由UDP_DATA_P定義。對(duì)于AVR單片機(jī)的特點(diǎn),為了盡一切可能節(jié)約內(nèi)存使用率,在向負(fù)載區(qū)填充數(shù)據(jù)時(shí)用到了兩個(gè)函數(shù),udp_puts_data函數(shù)操作的是BYTE*類型的數(shù)據(jù),而udp_puts_data_p操作的為PGM_P類型數(shù)據(jù),即位于FLASH中的數(shù)據(jù),需要通過(guò)pgm_read_byte取出。而其他類型的CPU,例如STM卻沒有該功能,則使用udp_puts_data即可。
WORD udp_puts_data ( BYTE *rxtx_buffer, BYTE *data, WORD offset )
{
while( *data )
{
rxtx_buffer[ UDP_DATA_P + offset ] = *data++;
offset++;
}
return offset;
}
WORD udp_puts_data_p ( BYTE *rxtx_buffer, PGM_P data, WORD offset )
{
BYTE ch;
while( (ch = pgm_read_byte(data++)) )
{
rxtx_buffer[ UDP_DATA_P + offset ] = ch;
offset++;
}
return offset;
}
2.4 UDP報(bào)文查詢
UDP報(bào)文的查詢需要匹配接收數(shù)據(jù)包中的UDP端口號(hào),若匹配成功則可對(duì)輸入數(shù)據(jù)包進(jìn)行處理,這些處理包括解析數(shù)據(jù)包的數(shù)據(jù)格式,分析出控制命令或查詢命令。也可以通過(guò)udp_puts_data向發(fā)送緩沖區(qū)中填寫響應(yīng)數(shù)據(jù)。接著逐步生成以太網(wǎng)首部,IP首部和UDP首部,以太網(wǎng)首部中包含目標(biāo)MAC地址,IP首部中包含目標(biāo)IP地址,UDP首部中包含目標(biāo)端口號(hào)。
BYTE udp_receive ( BYTE *rxtx_buffer, BYTE *dest_mac, BYTE *dest_ip )
{
WORD dlength = 0;
// udp負(fù)載長(zhǎng)度
WORD udp_loadlen = 0;
// 匹配UDP協(xié)議 UDP端口號(hào)
if ( rxtx_buffer[IP_PROTO_P] != IP_PROTO_UDP_V || rxtx_buffer[UDP_DST_PORT_H_P] != UDP_AVR_PORT_H_V || rxtx_buffer[ UDP_DST_PORT_L_P ] != UDP_AVR_PORT_L_V )
return 0;
// 加入處理函數(shù)
// 生成以太網(wǎng)首部
eth_generate_header (rxtx_buffer, (WORD_BYTES){ETH_TYPE_IP_V}, dest_mac );
// 生成IP首部
ip_generate_header (rxtx_buffer, (WORD_BYTES){sizeof(IP_HEADER)+sizeof(UDP_HEADER)+dlength}, IP_PROTO_UDP_V, dest_ip );
// 生成UDP首部
udp_generate_header (rxtx_buffer, (WORD_BYTES){(rxtx_buffer[UDP_SRC_PORT_H_P]<<8)|rxtx_buffer[UDP_SRC_PORT_L_P]}, (WORD_BYTES){sizeof(UDP_HEADER)+dlength});
// 發(fā)送所有首部和UDP負(fù)載數(shù)據(jù)
enc28j60_packet_send ( rxtx_buffer, sizeof(ETH_HEADER)+sizeof(IP_HEADER)+sizeof(UDP_HEADER)+dlength );
return 1;
}
3 實(shí)驗(yàn)
實(shí)驗(yàn)部分主要是為了驗(yàn)證UDP協(xié)議,通過(guò)PC機(jī)上的網(wǎng)絡(luò)調(diào)試軟件開辟一個(gè)PC機(jī)的UDP端口,假定端口號(hào)為3001,IP地址為192.168.1.102;AVR的UDP默認(rèn)端口號(hào)為3000,IP地址為192.168.1.105。
3.1 程序結(jié)構(gòu)
在運(yùn)行UDP程序之前,需要運(yùn)行ARP,IP和ICMP各部分,并保存發(fā)起發(fā)的MAC地址和IP地址。
/* 獲得新的IP報(bào)文 */
plen = enc28j60_packet_receive( (BYTE*)&rxtx_buffer, MAX_RXTX_BUFFER );
if(plen==0) return;
/* 保存客服端的MAC地址 */
memcpy ( (BYTE*)&client_mac, &rxtx_buffer[ ETH_SRC_MAC_P ], sizeof(MAC_ADDR) );
/* 檢查該報(bào)文是不是ARP報(bào)文 */
if ( arp_packet_is_arp( rxtx_buffer, (WORD_BYTES){ARP_OPCODE_REQUEST_V} ) )
{
/* 向客戶端返回ARP報(bào)文 */
arp_send_reply ( (BYTE*)&rxtx_buffer, (BYTE*)&client_mac );
return;
}
/* 保存客服端的IP地址 */
memcpy ( (BYTE*)&client_ip, &rxtx_buffer[ IP_SRC_IP_P ], sizeof(IP_ADDR) );
/* 檢查該報(bào)文是否為IP報(bào)文 */
if ( ip_packet_is_ip ( (BYTE*)&rxtx_buffer ) == 0 )
{
return;
}
/* 如果是ICMP報(bào)文 向發(fā)起方返回?cái)?shù)據(jù) */
if ( icmp_send_reply ( (BYTE*)&rxtx_buffer, (BYTE*)&client_mac, (BYTE*)&client_ip ) )
{
return;
}
// 進(jìn)行UDP處理
if (udp_receive ( (BYTE *)&rxtx_buffer, (BYTE *)&client_mac, (BYTE *)&client_ip ))
{
return;
}
3.2 源IP和源端口
使用網(wǎng)路調(diào)試助手發(fā)送一個(gè)名稱,例如UDP。在仿真環(huán)境中通過(guò)串口打印出發(fā)起方的IP地址和端口號(hào),例如PC機(jī)的端口號(hào)設(shè)定為3001,PC機(jī)的IP地址為192.168.1.102。在udp_receive函數(shù)中需要判斷UDP端口號(hào)和目標(biāo)IP地址是否匹配,若匹配即可加入以下代碼。首先獲得UDP的負(fù)載長(zhǎng)度,使用memcpy命令復(fù)制到udp_recbuf中,接著通過(guò)串口打印源IP地址,該參數(shù)位于IP首部,源端口號(hào),該參數(shù)位于UDP首部中。
// 獲得UDP負(fù)載長(zhǎng)度
udp_loadlen = udp_get_dlength(rxtx_buffer);
// 復(fù)制UDP負(fù)載
memcpy(udp_recbuf,(char*)&rxtx_buffer[UDP_DATA_P],udp_loadlen);
#ifdef UDP_DEBUG
printf("UDP Message!n");
printf("Send Form:%d.%d.%d.%d ",
rxtx_buffer[IP_SRC_IP_P+0],rxtx_buffer[IP_SRC_IP_P+1],
rxtx_buffer[IP_SRC_IP_P+2],rxtx_buffer[IP_SRC_IP_P+3]);
printf("Port:%dn",(rxt