一、明确背景
uboot中有许多通信协议,像TFTP、NFS等,这些协议底层都是基于UDP协议来实现的,由于有一个板子在 uboot 段进行固件下载更新的需求,本来想基于TCP协议来实现自定义通信协议(TCP有自带的拥塞控制和重传机制)。但是,uboot底层并不支持TCP协议栈,且TCP协议栈的默认超时重传时间过长,移植TCP协议栈到uboot中难度也是相当大。最后结合需求考虑下来,决定基于UDP协议来实现自定义通信协议。第一步,要先验证UDP协议能否走通(不用想大概率是可以的,因为uboot中支持的其他协议底层也是基于UDP的,但还是走一遍验证一下,也是为后面的自定义通信协议打好基础)
抓住一个思想:TFTP底层就是基于UDP的,有什么不清楚的地方就看uboot中的TFTP源码,往往会指出些方向。
二、代码实现
首先在common/cmd_net.c中添加如下代码,这一步的目的是在uboot的命令中添加一个名为udp的命令,当我们在控制台输入udp后便会执行do_udp函数,然后逐层往下调用其他函数
/*my UDP*/ int do_udp(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) {int ret;ret = netboot_common_udp(UDP, cmdtp, argc, argv);return ret; }U_BOOT_CMD(udp, 6, 1, do_udp,"send or recv message to from server using UDP protocol","[udp]" );
接着继续在该文件中定义这样一个函数,该函数是用来处理uboot控制台输入的具体命令参数的
/*my netboot_common*/ static int netboot_common_udp(enum proto_t proto, cmd_tbl_t *cmdtp, int argc, char *argv[]) {int rcode = 0;int size;switch (argc){case 1:break;case 2:pkt_data = argv[1];break;default:bootstage_error(BOOTSTAGE_ID_NET_START);return CMD_RET_USAGE;}bootstage_mark(BOOTSTAGE_ID_NET_START);if ((size = NetLoop(proto)) < 0) {bootstage_error(BOOTSTAGE_ID_NET_NETLOOP_OK);return 1;}bootstage_mark(BOOTSTAGE_ID_NET_NETLOOP_OK);/* NetLoop ok, update environment */netboot_update_env();/* done if no file was loaded (no errors though) */if (size == 0) {bootstage_error(BOOTSTAGE_ID_NET_LOADED);return 0;}if (rcode < 0)bootstage_error(BOOTSTAGE_ID_NET_DONE_ERR);elsebootstage_mark(BOOTSTAGE_ID_NET_DONE);return rcode; }
这个函数调用了net/net.c中的net_loop()函数,到这里本路径下的修改工作已完成。跳转到net/net.c中,在netloop函数的switch中添加case:UDP
#ifdef CONFIG_CMD_TFTPSRVcase TFTPSRV:TftpStartServer();break; #endifcase UDP:UdpStart(); //my udpbreak;#if defined(CONFIG_CMD_DHCP)case DHCP:BootpReset();NetOurIP = 0;DhcpRequest(); /* Basically same as BOOTP */break; #endif
还需要在include/net.h中添加UDP协议的声明
注意在net.h中要声明该变量是定义在别处的,这样编译器就能知道pkt_data(用来接终端输入的待传数据的)是一个在其他地方定义的全局变量,从而允许在common.c中正确地访问和使用这个变量。
至此UDP协议支持的相关配置工作已经完成,接下来要自己在net目录下写一个UDP协议的服务函数udp.c和udp.h
#include <common.h> #include <command.h> #include <net.h> #include "tftp.h" #include "bootp.h" #include <flash.h> #include "udp.h"uchar *pkt_data; static int UdpPktLen;static IPaddr_t UdpServerIP; static int UdpServerPort; /* The UDP port at their end */ static int UdpOurPort; /* The UDP port at our end */ static int UdpTimeoutCount;static void UdpSend (void);/**********************************************************************/static void UdpSend (void) {uchar *pkt; //指向待发送数据包的指针int len = 0; //数据包长度pkt = NetTxPacket + NetEthHdrSize() + IP_UDP_HDR_SIZE; // printf("NetEthHdrSize() = %d\n", NetEthHdrSize()); // printf("IP_UDP_HDR_SIZE = %d\n", IP_UDP_HDR_SIZE);len = strlen(pkt_data);memcpy(pkt, pkt_data, len);printf("pkt_data = %s,len = %d\n", pkt_data, len); // printf("pkt = %s\n", pkt);NetSendUDPPacket(NetServerEther, UdpServerIP, UdpServerPort, UdpOurPort, len); }static void UdpHandler (uchar *pkt, unsigned dest, IPaddr_t UdpServerIP, unsigned src, unsigned len) {printf("---- receive udp packet ----\n");printf("len = %d\n",len);printf("data = %s\n", pkt);UdpSendAck(); }void UdpStart (void) {char *ep; /* Environment pointer */if ((ep = getenv("serverip")) != NULL){printf("ep = %s\n", ep);UdpServerIP = string_to_ip((const char *)ep);}#if defined(CONFIG_NET_MULTI)printf ("Using %s device\n", eth_get_name()); #endifnet_set_udp_handler(UdpHandler); // 设置UDP数据包处理函数为UdpHandlerUdpServerPort = 50000; //主机端口号;UdpOurPort = 30000;memset(NetServerEther, 0, 6); //如果服务器IP地址发生了变化,清零服务器以太网地址(MAC地址)UdpSend (); }
#ifndef __UDP_H__ #define __UDP_H__extern void UdpStart (void);#endif
别忘了在net目录下的Makefile中添加编译依赖项,保证udp.c被编译进镜像文件
至此uboot源码中的相关工作已经完成,接下来进行调试验证
写两个上位机程序来进行数据的收和发
收
#include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <string.h> #include <arpa/inet.h> #include <unistd.h> int main(void) {int sockfd = 0;char tmpbuff[1024] = {0};struct sockaddr_in recvaddr;size_t nsize = 0;int ret = 0;sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (-1 == sockfd){perror("fail to socket\n");return -1;}recvaddr.sin_family = AF_INET;recvaddr.sin_port = htons(50000);recvaddr.sin_addr.s_addr = INADDR_ANY;ret = bind(sockfd, (struct sockaddr *)&recvaddr, sizeof(recvaddr));if (ret == -1){perror("fail to bind");return -1;}nsize = recvfrom(sockfd, tmpbuff, sizeof(tmpbuff), 0, NULL, NULL);if (nsize == -1){perror("fail to recvfrom");return -1;}printf("接收 %ld 个字节\n", nsize);printf("Recv: %s\n", tmpbuff);close(sockfd); }
发
#include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <string.h> #include <arpa/inet.h> #include <unistd.h> int main(void) {int sockfd = 0;char tmpbuff[1024] = {0};struct sockaddr_in recvaddr;size_t nsize = 0;sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (-1 == sockfd){perror("fail to socket\n");return -1;}gets(tmpbuff, strlen(tmpbuff), stdin);recvaddr.sin_family = AF_INET;recvaddr.sin_port = htons(30000);recvaddr.sin_addr.s_addr = inet_addr("172.31.13.207");nsize = sendto(sockfd, tmpbuff, strlen(tmpbuff), 0, (struct sockaddr *)&recvaddr, sizeof(recvaddr));if (nsize == -1){perror("fail to sendto");return -1;}printf("发送 %ld 个字节\n", nsize);close(sockfd); }
三、测试
在uboot中发,上位机来收
在上位机中发,uboot中来收
极限性能测试
测试验证发发现,在不经过网络转发设备的前提下能跑满UDP协议理论最大包长1472字节。
四、补充说明
以上只是简单在uboot中走通了UDP协议,故代码中不涉及待传数据的帧格式设计、重传机制、超时机制等,若要实现基于UDP协议自定义通信协议,还需要设计好以上几点。这个后面再说。
注:uboot版本为2017年