1. UDP介绍
UDP是一个简略的面向数据报的运送层协议:进程的每个输出操作都正好产生一个 UDP数据报,并拼装成一份待发送的IP数据报。这与面向流字符的协议不同,如TCP,应用程序产生的整体数据与真实发送的单个IP数据报或许没有什么联络。
UDP数据报封装成一份 IP数据报的格局如图11 – 1所示。
RFC 768 [Postel 1980] 是UDP的正式规范。
UDP不供给牢靠性:它把应用程序传给IP层的数据发送出去,可是并不确保它们能抵达意图地。由于缺少牢靠性,咱们好像觉得要防止运用UDP而运用一种牢靠协议如TCP。在评论完TCP后将再回到这个论题,看看什么样的应用程序能够运用UDP。
2. UDP首部
UDP首部的各字段如图11 – 2所示。
端口号表明发送进程和接纳进程。在图 1 – 8中,咱们画出了TCP和UDP用意图端口号来分用来自IP层的数据的进程。
由于IP层现已把IP数据报分配给TCP或UDP(依据I P首部中协议字段值) ,因而TCP端口号由TCP来查看,而UDP端口号由UDP来查看。TCP端口号与UDP端口号是彼此独立的。
虽然彼此独立,假如TCP和UDP一起供给某种闻名服务,两个协议一般挑选相同的端口号。这朴实是为了运用方便,而不是协议自身的要求。
UDP长度字段指的是UDP首部和UDP数据的字节长度。该字段的最小值为 8字节(发送一份0字节的UDP数据报是OK) 。这个UDP长度是有冗余的。 IP数据报长度指的是数据报全长(图3 – 1) ,因而UDP数据报长度是全长减去IP首部的长度(该值在首部长度字段中指定,如图3 – 1所示)
UDP查验和掩盖UDP首部和UDP数据。回想IP首部的查验和,它只掩盖IP的首部—并不掩盖IP数据报中的任何数据。
UDP和TCP在首部中都有掩盖它们首部和数据的查验和。UDP的查验和是可选的,而TCP的查验和是必需的。
虽然UDP查验和的根本核算方法与咱们在描绘的IP首部查验和核算方法相相似(16 bit字的二进制反码和,可是略微有所不同,在依据字段类型判定为UDP或许TCP时加入了一些处理,看代码就晓得了) ,可是它们之间存在不同的当地。首要, UDP数据报的长度能够为奇数字节,可是查验和算法是把若干个 16 bit字相加。解决方法是必要时在最终添加填充字节0,这只是为了查验和的核算(也就是说,或许添加的填充字节不被传送) 。
其次,UDP数据报和TCP段都包括一个1 2字节长的伪首部(本TCP/IP协议栈有所不同,只加入了4字节源IP地址和4字节意图IP地址,即运用IP首部的尾巴,完成了空间上的复用,看代码就晓得了),它是为了核算查验和而设置的。伪首部包括IP首部一些字段。其意图是让 UDP两次查看数据是否现已正确抵达意图地(例如,IP没有承受地址不是本主机的数据报,以及IP没有把应传给另一高层的数据报传给UDP) 。UDP数据报中的伪首部格局如图11 – 3所示。
在该图中,咱们特别举了一个奇数长度的数据报比如,因而在核算查验和时需求加上填充字节(0)。留意,UDP数据报的长度在查验和核算进程中呈现两次。
假如查验和的核算结果为 0,则存入的值为全1(65535) ,这在二进制反码核算中是等效的。假如传送的查验和为0,阐明发送端没有核算查验和。(由于协议要求如此,故代码需求完成之。)假如发送端没有核算查验和而接纳端检测到查验和有过失,那么 UDP数据报就要被悄悄地丢掉。不产生任何过失报文(当IP层检测到IP首部查验和有过失时也这样做)。
UDP查验和是一个端到端的查验和。它由发送端核算,然后由接纳端验证。其意图是为了发现UDP首部和数据在发送端到接纳端之间产生的任何改动。
/*下面论述UDP校验和的一些前史和必要性*/
虽然UDP查验和是可选的,可是它们应该总是在用。在 80年代,一些核算机产商在默许条件下封闭UDP查验和的功用,以进步运用UDP协议的NFS(Network File System)的速度。
在单个局域网中这或许是能够承受的,可是在数据报经过路由器时,经过对链路层数据帧进行循环冗余查验(如以太网或令牌环数据帧)能够检测到大多数的过失,导致传输失利。不论信任与否,路由器中也存在软件和硬件过失,以致于修正数据报中的数据。假如封闭端到端的UDP查验和功用,那么这些过失在UDP数据报中就不能被检测出来。别的,一些数据链路层协议(如SLIP)没有任何方式的数据链路查验和。
Host Requirements RFC声明,UDP查验和选项在默许条件下是翻开的。它还声明,假如发送端现已核算了查验和,那么接纳端有必要查验接纳到的查验和(如接纳到查验和不为0) 。可是,许多体系没有恪守这一点,只是在出口查验和选项被翻开时才验证接纳到的查验和。
别的需求解说几个术语: IP数据报是指IP层端到端的传输单元(在分片之前和从头拼装之后) ,分组是指在IP层和链路层之间传送的数据单元。一个分组能够是一个完好的 IP数据报,也能够是IP数据报的一个分片。(这儿有怎么分片的阐明,书里介绍的具体,简而言之,超越MTU就需求分,可是榜首片和接下来的片是有差异的:榜首个有UDP首部,其他没有,可是能够经过IP的flags来组合起来。下面的图很形象的阐明晰。)
——————————————以下内容产生于代码及剖析————————————–
3. UDP宏界说完成
// ******* UDP *******#define UDP_HEADER_LEN 8//源端口方位#define UDP_SRC_PORT_H_P 0x22#define UDP_SRC_PORT_L_P 0x23//方针端口方位#define UDP_DST_PORT_H_P 0x24#define UDP_DST_PORT_L_P 0x25//UDP数据长度方位#define UDP_LEN_H_P 0x26#define UDP_LEN_L_P 0x27//UDP校验和方位#define UDP_CHECKSUM_H_P 0x28#define UDP_CHECKSUM_L_P 0x29//UDP数据开始地址#define UDP_DATA_P 0x2a
4. UDP函数完成
本TCP/IP协议栈中的UDP完成只一个make_udp_reply_from_request函数——udp服务器,能够呼应其他udp的恳求。在衔接的次序看来,在stm32板子上面的为服务器,等候pc机客户端的恳求,当恳求到来的时分,回来由程序员自行设定的呼应,如本文中将做出3个呼应的比如(当然udp一旦树立之后,就部分客户端和服务器端,位置是对等的,可是以为发起者为clien比较契合认知罢了)。
这儿说以下输入吧:buf为缓冲区,data为要传输的数据,datalen即为sizeof(data),port即为pc端的udp端口号
void make_udp_reply_from_request(unsigned char *buf, char *data, unsigned int datalen, unsigned int port)
{
unsigned int i = 0, tol_len;
unsigned int ck;
//如前面的ARP和ICMP相同的
make_eth(buf);
// total length field in the IP header must be set:
//如IP Header
tol_len = IP_HEADER_LEN + UDP_HEADER_LEN + datalen;
buf[IP_TOTLEN_H_P] = tol_len >> 8;
buf[IP_TOTLEN_L_P] = tol_len;
//如ICMP
make_ip(buf);
//本地UDP的端口号
buf[UDP_DST_PORT_H_P] = port >> 8;
buf[UDP_DST_PORT_L_P] = port & 0xff;
// source port does not matter and is what the sender used.
// calculte the udp length:最大16bit长度,即65535-14-20-8,但一般会设置的较小,原因么,上文里边讲过。
buf[UDP_LEN_H_P] = datalen >> 8;
buf[UDP_LEN_L_P] = UDP_HEADER_LEN + datalen;
// zero the checksum
buf[UDP_CHECKSUM_H_P] = 0;
buf[UDP_CHECKSUM_L_P] = 0;// copy the data:
while(i < datalen)
{
buf[UDP_DATA_P + i] = data[i];
i++;
}//UDP_DEBUG刺进此处
//这儿的16字节是UDP的伪首部,即IP的源地址-0x1a+方针地址-0x1e(和规范的有差异),
//+UDP首部=4+4+8=16
ck = checksum(&buf[IP_SRC_P], 16 + datalen, 1);
buf[UDP_CHECKSUM_H_P] = ck >> 8;
buf[UDP_CHECKSUM_L_P] = ck & 0xff;
enc28j60PacketSend(UDP_HEADER_LEN + IP_HEADER_LEN + ETH_HEADER_LEN + datalen, buf);
}
5. UDP试验
在有了以上的UDP完成之后,你还需求有UDP的恳求进来,如下代码所示:
下面的代码放在一个while(1)或许RTOS进程里边,作为服务器来等候客户端的呼应
/*——————— udp server start, we listen on udp port 1200=0x4B0 —————————–*/
if (buf[IP_PROTO_P]==IP_PROTO_UDP_V&&buf[UDP_DST_PORT_H_P]==4&&buf[UDP_DST_PORT_L_P]==0xb0)
{
//UDP数据长度
udpdatalen=buf[UDP_LEN_H_P];
udpdatalen=udpdatalen<<8;
udpdatalen=(udpdatalen+buf[UDP_LEN_L_P])-UDP_HEADER_LEN;
//udpdatalen=buf[UDP_LEN_L_P]-UDP_HEADER_LEN;
//获取pc端的udp port
pcudpport=buf[UDP_SRC_PORT_H_P]<<8 | buf[UDP_SRC_PORT_L_P];
//将udp客户端得到的数据buf写入buf1,由于下面的试验需求输入的信息来做出相应的动作
for(i1=0; i1buf1[i1]=buf[UDP_DATA_P+i1]; make_udp_reply_from_request(buf,buf1,udpdatalen,pcudpport);
}
/*—————————————-udp end ———————————————–*/
ps:本试验中板子udp的port为1200,pc机的port为4001
试验部分完成了三个简略的试验:
1.经过串口输出UDP客户端的IP地址及端口号
2.经过串口和UDP输出UDP的输入数据,即USART ECHO和UDP ECHO
3.完成UDP指令操控STM32“>STM32板子上面的LED
void make_udp_reply_from_request(unsigned char *buf, char *data, unsigned int datalen, unsigned int port)
{
unsigned int i = 0, tol_len;
unsigned int ck;
//如前面的ARP和ICMP相同的
make_eth(buf);
// total length field in the IP header must be set:
//如IP Header
tol_len = IP_HEADER_LEN + UDP_HEADER_LEN + datalen;
buf[IP_TOTLEN_H_P] = tol_len >> 8;
buf[IP_TOTLEN_L_P] = tol_len;
//如%&&&&&%MP
make_ip(buf);
//本地UDP的端口号
buf[UDP_DST_PORT_H_P] = port >> 8;
buf[UDP_DST_PORT_L_P] = port & 0xff;
// source port does not matter and is what the sender used.
// calculte the udp length:最大16bit长度,即65535-14-20-8,但一般会设置的较小,原因么,上文里边讲过。
buf[UDP_LEN_H_P] = datalen >> 8;
buf[UDP_LEN_L_P] = UDP_HEADER_LEN + datalen;
// zero the checksum
buf[UDP_CHECKSUM_H_P] = 0;
buf[UDP_CHECKSUM_L_P] = 0;// copy the data:
while(i < datalen)
{
buf[UDP_DATA_P + i] = data[i];
i++;
}#ifdef UDP_DEBUG
i = 0;
printf(“UDP Server Test. \r\n”);
printf(“udp客户端的IP地址及端口号 : \r\n”);while(i < sizeof(ipv4_addr))
{
//留意这儿咱们树立的是UDP Server,输出UDP Client的IP地址
printf(“%d”, buf[IP_DST_P + i]);if(i != sizeof(ipv4_addr) – 1)
{
printf(“.”);
}i++;
}i = 0;
//输出pc端的udp port
printf(“:%d \r\n”, port);//串口打印UDP Client发过来的数据
printf(“udp客户端发送的数据 : \r\n”);
printf(“%s \r\n”, data);//完成UDP Server来呼应UDP Client的操控LED指令
//如:led1=on,led1=off
if(strcmp(data, “led1=on”) == 0)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_8);
}if(strcmp(data, “led1=off”) == 0)
{
GPIO_SetBits(GPIOA, GPIO_Pin_8);
}//如:led2=on,led2=off
if(strcmp(data, “led2=on”) == 0)
{
GPIO_ResetBits(GPIOD, GPIO_Pin_2);
}if(strcmp(data, “led2=off”) == 0)
{
GPIO_SetBits(GPIOD, GPIO_Pin_2);
}#endif
//这儿的16字节是UDP的伪首部,即IP的源地址-0x1a+方针地址-0x1e(和规范的有差异),
//+UDP首部=4+4+8=16
ck = checksum(&buf[IP_SRC_P], 16 + datalen, 1);
buf[UDP_CHECKSUM_H_P] = ck >> 8;
buf[UDP_CHECKSUM_L_P] = ck & 0xff;
enc28j60PacketSend(UDP_HEADER_LEN + IP_HEADER_LEN + ETH_HEADER_LEN + datalen, buf);
}
TCP&UDP测验东西现象:echo完成
串口现象:契合预期
注:封闭翻开UDP重连才能够看到随机分配的不同udp port。
WireShark现象:顺畅抓到包~~~
开发板现象:
LED2亮了,开始打通了原子国际和数字国际了,可是体会很糟糕,O(∩_∩)O