STM32NET學(xué)習(xí)筆記 UDP部分
1.前言
嵌入式以太網(wǎng)開發(fā)是一個(gè)很有挑戰(zhàn)性的工作。通過幾個(gè)月的學(xué)習(xí),我個(gè)人覺得大致有兩條途徑。第一條途徑,先通過高級(jí)語言熟悉socket編程,例如C#或C++,對(duì)bind,listen,connect,accept等函數(shù)熟悉之后,應(yīng)用 lwIP。第二種途徑,通過分析嵌入式以太網(wǎng)代碼,結(jié)合TCPIP協(xié)議棧規(guī)范逐步實(shí)踐代碼。第一種途徑效率高,開發(fā)周期短,編寫出來的代碼性能穩(wěn)定,第二種途徑花的時(shí)間長,開發(fā)出來的代碼功能不完善,但是由于緊緊結(jié)合TCPIP規(guī)范,可以了解的內(nèi)容較多,適合學(xué)習(xí)。本文通過分析和修改AVRNET源碼,逐步實(shí)現(xiàn)TCPIP協(xié)議棧的各個(gè)子部分,包括ETHERNET部分,ARP部分,IP部分,ICMP部分,UDP部分,TCP部分和HTTP部分?!?strong>STM32NET學(xué)習(xí)筆記——索引】【代碼倉庫】
本文將實(shí)現(xiàn)UDP部分。
UDP協(xié)議全稱為用戶數(shù)據(jù)協(xié)議,是一種簡單有效的運(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é)議簡單的多。本文將實(shí)現(xiàn)UDP協(xié)議,并通過幾個(gè)簡單的案例說明UDP的使用。
1.2 相關(guān)資料
【ENC28J60學(xué)習(xí)筆記】
【AVRNET項(xiàng)目(國外)】
【AVR webserver項(xiàng)目(國外)】
1.3 代碼倉庫
【代碼倉庫】——CSDN Code代碼倉庫。
2 UDP部分實(shí)現(xiàn)
UDP功能的實(shí)現(xiàn)可分為UDP首部填充,UDP緩沖區(qū)填充和UDP報(bào)文查詢。UDP首部填充是一個(gè)按部就班的過程,即填充源端口、目標(biāo)端口、長度和校驗(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é)內(nèi)容。
//UDP默認(rèn)端口號(hào)
#defineUDP_AVR_PORT_V3000
#defineUDP_AVR_PORT_H_V(UDP_AVR_PORT_V>>8)
#defineUDP_AVR_PORT_L_V(UDP_AVR_PORT_V&0xff)
//源端口
#defineUDP_SRC_PORT_H_P0x22
#defineUDP_SRC_PORT_L_P0x23
//目標(biāo)端口
#defineUDP_DST_PORT_H_P0x24
#defineUDP_DST_PORT_L_P0x25
//UDP負(fù)載長度
#defineUDP_LENGTH_H_P0x26
#defineUDP_LENGTH_L_P0x27
//UDP校驗(yàn)和
#defineUDP_CHECKSUM_H_P0x28
#defineUDP_CHECKSUM_L_P0x29
//UDP負(fù)載起始地址
#defineUDP_DATA_P0x2A
2.1 UDP首部填充
UDP首部填充中需要明確UDP的端口號(hào),STMNET項(xiàng)目中通過常數(shù)宏定義實(shí)現(xiàn)。
#defineUDP_AVR_PORT_V3000
#defineUDP_AVR_PORT_H_V(UDP_AVR_PORT_V>>8)
#defineUDP_AVR_PORT_L_V(UDP_AVR_PORT_V&0xff)
從這段代碼中可以看出,STMNET的UDP端口號(hào)為3000。
voidudp_generate_header(BYTE*rxtx_buffer,WORD_BYTESdest_port,WORD_BYTESlength)
{
WORD_BYTESck;
//默認(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ù)載長度
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+8forsource/destinationIPaddresslength(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;
}
2.2 UDP負(fù)載長度查詢
UDP首部中包含UDP長度描述字節(jié),長度占有兩個(gè)字節(jié)并以大端格式保存,由于宏定義的提示作用,弱化了大端格式的影響。長度中也包括了UDP首部的長度,UDP首部的長度為固定的8字節(jié)。
WORDudp_get_dlength(BYTE*rxtx_buffer)
{
WORDlength=0;
//獲得UDP長度
length=rxtx_buffer[UDP_LENGTH_H_P]<<8|rxtx_buffer[UDP_LENGTH_L_P];
//去除首部長度
length=length-8;
returnlength;
}
2.3 UDP負(fù)載區(qū)填充
UDP負(fù)載去填充即在UDP首部之后填充有用的數(shù)據(jù)。在這段真實(shí)負(fù)載之前包括了UDP首部,IP首部和以太網(wǎng)首部,分別占用了8字節(jié),20字節(jié)和14字節(jié)。UDP負(fù)載的起始地址通過宏由UDP_DATA_P定義。
WORDudp_puts_data(BYTE*rxtx_buffer,BYTE*data,WORDoffset)
{
while(*data)
{
rxtx_buffer[UDP_DATA_P+offset]=*data++;
offset++;
}
returnoffset;
}
2.4 UDP報(bào)文查詢
UDP報(bào)文查詢需要匹配接收數(shù)據(jù)包中的UDP端口號(hào),若匹配成功則可對(duì)輸入數(shù)據(jù)包進(jìn)行處理,這些處理包括解析數(shù)據(jù)包格式,分析出控制命令或查詢命令。也可以通過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)。
BYTEudp_receive(BYTE*rxtx_buffer,BYTE*dest_mac,BYTE*dest_ip)
{
WORDdlength=0;
//udp負(fù)載長度
WORDudp_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)
{
//獲得UDP負(fù)載長度
udp_loadlen=udp_get_dlength(rxtx_buffer);
//復(fù)制UDP接收
memcpy(udp_recbuf,(char*)&rxtx_buffer[UDP_DATA_P],udp_loadlen);
#ifUDP_DEBUG
printf("UDPMessage!rn");
printf("SendForm:%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:%drn",(rxtx_buffer[UDP_SRC_PORT_H_P]<<8)|rxtx_buffer[UDP_SRC_PORT_L_P]);
printf("Reccive:%srn",udp_recbuf);
#endif
//生成以太網(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)+dlen