正点原子开拓者NiosII资料连载第二十二章UDP实验

1)实验平台:正点原子开拓者FPGA 开发板

3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/index.html

第二十二章基于NicheStack的UDP实验

上一章我们使用三速以太 IP核搭建了NicheStack的底层硬件框架,并使用Nios II SBT

for Eclipse自带的“Simple Socket Server”例程演示了使用telnet服务控制FPGA开发板上

的led灯。本章我们将进一步了解NicheStack,并实现一个简单的UDP服务。本章分为以下几个

部分:

22.1 简介

22.2 实验任务

22.3 硬件设计

22.4 软件设计

22.5 下载验证

简介

UDP是User Datagram Protocol的简称,中文名是用户数据 协议。是OSI(Open System

Interconnection,开放式系统互联) 参考模型中一种无连接的传输层协议,提供面向事务的

简单不可靠信息传送服务。UDP 用来支持那些需要在计算机之间传输数据的 络应用,包括

络视频会议系统在内的众多的客户/服务器模式的 络应用都需要使用UDP协议。UDP协议从问

世至今已经被使用了很多年,虽然其最初的光彩已经被一些类似协议所掩盖,但是即使是在今

天UDP仍然不失为一项非常实用和可行的 络传输层协议。

UDP 数据 格式如下图:

图 22.1.1 UDP数据 格式

端口 表示发送和接收进程,UDP协议使用端口 为不同的应用保留各自的数据传输通道,

UDP和TCP协议都是采用端口 对同一时刻内多项应用同时发送和接收数据,而数据接收方则通

过目标端口接收数据。有的 络应用只能使用预先为其预留或注册的静态端口;而另外一些

络应用则可以使用未被注册的动态端口。因为UDP 头使用两个字节存放端口 ,所以端口

的有效范围是从0到65535。一般来说,大于49151的端口 都代表动态端口。

数据 的长度是指包括 头和数据部分在内的总字节数。因为 头的长度是固定的,所以

该域主要被用来计算可变长度的数据部分(又称为数据负载)。数据 的最大长度根据操作环

境的不同而各异。从理论上说,包含 头在内的数据 的最大长度为 65535 字节。

UDP协议使用 头中的校验和来保证数据的安全。校验和首先在数据发送方通过特殊的算

法计算得出,在传递到接收方之后,还需要再重新计算。如果某个数据 在传输过程中被第三

方篡改或者由于线路噪音等原因受到损坏,发送和接收方的校验计算和将不会相符,由此UDP

协议可以检测是否出错。关于UDP协议及其与IP协议的关系的详细介绍可参考《开拓者FPGA开

发指南》第四十二章的《以太 通信实验》。

下面我们来看看如何用NicheStack进行UDP通信。使用NicheStack进行UPD通信的过程大致

如下图所示:

图 22.1.2 UDP通信过程

整个过程可分为以下几个步骤:

UDP服务端:

1) 创建UDP套接字(使用socket()函数)

2) 将套接字与服务器地址绑定(使用bind()函数)

3) 接收客户端发送的数据(使用recvfrom()函数)

4) 向客户端发送数据(使用sendto()函数)

5) 回到第3步(如果继续服务)

6) 关闭UDP服务(如果需要关闭服务),关闭socket描述符并退出(使用close()函数)

UDP客户端:

1) 创建UDP套接字(使用socket()函数)

2) 发送数据或请求给服务器(使用sendto()函数)

3) 接收来自服务器的数据或响应(使用recvfrom()函数)

4) 处理回复并在必要时返回步骤2

5) 关闭套接字描述符并退出(使用close()函数)

涉及到的函数说明如下:

socket()函数原型:

long socket(int family, int type, int proto)

作用:在指定的域中创建未绑定的套接字,返回套接字文件描述符

参数family:用于设置 络通信的域,socket根据这个参数选择信息协议的族,常用的值

为AF_INET(用于IPv4)和AF_INET6(用于IPv6)

参数type:指定创建的socket类型,值SOCK_STREAM用于TCP通信、值SOCK_DGRAM用于UDP

通信。

参数proto:指明套接字使用的协议,0表示使用地址系列的默认协议,置0即可。

返回值:成功:非负的文件描述符

失败:-1

bind()函数原型:

int bind (long s, struct sockaddr * addr, int addrlen)

作用:将地址分配给未绑定的套接字。

参数s:套接字文件描述符,通过socket()函数获得

参数addr:需要绑定的IP和端口

参数addrlen:addr结构体的大小

返回值:成功:0

失败:-1

recvfrom()函数原型:

int recvfrom(BSD_SOCKET s, void * buf, BSD_SIZE_T len, int flags,

struct sockaddr * from, int * fromlen)

作用:从套接字接收消息。

参数s:套接字文件描述符,通过socket()函数获得

参数buf:用于接收数据的应用程序缓冲区

参数len:接收缓冲区的大小

参数flags:用于修改套接字行为的位或标志,一般填0即可

参数from:返回包含源地址的结构

参数from len:表示第五个参数所指向内容的长度

返回值:成功:返回接收成功的数据长度

失败:-1

sendto()函数原型:

int sendto (long s, char * buf, int len, int flags,

struct sockaddr * to, int tolen)

参数s:套接字文件描述符,通过socket()函数获得

参数buf:包含要发送的数据的应用程序缓冲区

参数len:发送缓冲区的大小

参数flags:用于修改套接字行为的位或标志,一般填0即可

参数to:包含目的地址的结构

参数tolen:目的地址结构的大小

返回值:成功:返回发送成功的数据长度

失败:-1

close()函数原型:

int close(long s)

close函数比较简单,只要填入socket()函数返回的文件描述符即可。

需要说明的是这些函数在NicheStack中基本上都是宏定义到另一个函数的,这里的函数原

型以最终使用的函数为准。

实验任务

本章的实验任务是使用NicheStack实现简单的UDP服务器,其功能是将 络调试助手发送

给开发板的数据环回至 络调试助手。

硬件设计

本章的UDP实验硬件部分可以基于《基于NicheStack的简单socket服务器实验》,无需修

改。

软件设计

软件设计部分与上一章《基于NicheStack的简单socket服务器实验》的区别不大,可以在

上一章的软件设计部分的基础上修改。

我们打开《基于NicheStack的简单socket服务器实验》的软件工程,在qsys_eth_bsp的

iniche目录下有如图 22.4.1所示的目录层。inc目录包含系统调用宏定义文件alt_syscall.h

和Altera InterNiche器件服务源文件alt_iniche_dev.h。src目录主要包含NicheStack的协议

栈实现源文件,其中ip目录具有完整大小的IP系列堆栈协议(ARP、ICMP、UDP)源文件、tcp

目录包含TCP和套接字源文件、net目录具有NicheStack和NicheLite共有的 络支持软件(包

括pktalloc、queue、macloop、slip和dhcp)源文件、misclib目录包含面向字符的用户界面

(CUI)与IP地址解析代码以及其它类似的额外功能、nios2目录则包含用于支持在Altera的

Nios-II平台上运行NicheStack协议栈的代码。

图 22.4.1 NicheStack目录层次

当购买了除TCP/IP堆栈之外的其它InterNiche产品时,将出现其它目录,如ftp(FTP客户

端和服务器代码)、tftp(TFTP客户端和服务器代码)、telnet(Telnet服务器代码)等,这

些目录都是Altera的NicheStack组件自带的。

知道NicheStack的目录层次后,我们可以从这些目录中了解其底层的实现,有兴趣的可以

研究,这里我们重点在于如何使用。

现在我们将该工程修改为UDP服务器工程。

由于本实验不需要led,所以我们将led.c文件删除。重命名simple_socket_server.c为

udp_server.c。并将其内容替换如下:

1 #include <stdio.h>

2 #include <string.h>

3 #include <ctype.h>

4

5 /* MicroC/OS-II definitions */

6 #include “includes.h”

7

8 /* Simple Socket Server definitions */

9 #include “simple_socket_server.h”

10 #include “alt_error_handler.h”

11

12 /* Nichestack definitions */

13 #include “ipport.h”

14 #include “tcpport.h”

15

16 /*

17 * sss_handle_msg()

18 *

19 * 接收UDP客户端发送过来的信息, 并将接收到的信息环回给UDP客户端 ,同时打印信息到控制台

20 *

21 */

22 void sss_handle_msg(SSSConn* conn)

23 {

24 int len, rx_code;

25 struct sockaddr_in incoming_addr;

26

27 while(1){

28 memset(conn->rx_buffer, 0, SSS_RX_BUF_SIZE);

29 len = sizeof(incoming_addr);

30 rx_code = recvfrom(conn->fd, conn->rx_buffer, SSS_RX_BUF_SIZE, 0,

31 (struct sockaddr *)&incoming_addr ,&len); //接收客户端发送过来的信息

32 if(rx_code == –1){

33 printf(“recieve data fail!n”);

34 return;

35 } //判断是否接收错误

36 conn->rx_wr_pos = conn->rx_buffer; //将接收到的信息传递给rx_wr_pos指针

37 printf(“client ip : %sn”, inet_ntoa(incoming_addr.sin_addr)); //打印客户端IP

38 printf(“client msg: %sn”,conn->rx_buffer); //打印客户端发过来的信息

39 sendto(conn->fd, conn->rx_wr_pos, strlen(conn->rx_wr_pos), 0,

40 (struct sockaddr *)&incoming_addr ,len); //发送信息给客户端

41 }

42 }

43

44 /*

45 * SSSSimpleSocketServerTask()

46 *

47 * 定义SSSSimpleSocketServerTask任务函数,即UDP服务任务函数

48 *

49 */

50 void SSSSimpleSocketServerTask()

51 {

52 int socketfd;

53 struct sockaddr_in addr;

54 static SSSConn conn;

55

56 if ((socketfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) //创建socket接口

57 alt_NetworkErrorHandler(EXPANDED_DIAGNOSIS_CODE,“[sss_task] Socket creation failed”);

58

59 addr.sin_family = AF_INET; //地址家族:IPv4

60 addr.sin_port = htons(SSS_PORT); //端口由SSS_PORT宏定义,并转换为 络字节序

61 addr.sin_addr.s_addr = INADDR_ANY; //通配地址,由内核指定

62

63 if ((bind(socketfd,(struct sockaddr *)&addr,sizeof(addr))) < 0) //绑定socket

64 alt_NetworkErrorHandler(EXPANDED_DIAGNOSIS_CODE,“[sss_task] Bind failed”);

65

66 printf(“[sss_task] UDP Server on port %dn”, SSS_PORT);

67

68 conn.fd = socketfd;

69

70 sss_handle_msg(&conn);

71 close(conn.fd);

72 }

SSSConn为结构体,用于管理SSS的每一个连接,定义如下:

图 22.4.2 SSSConn为结构体

其中的enum用于TCP连接的状态指示,这里我们未使用到,在TCP客户服务中会使用。

从 udp_server.c 中 我 们 看 到 , 创 建 UDP 服 务 器 的 过 程 同 图 22.1.2 表 示 的 一 致 。

SSSSimpleSocketServerTask()任务函数即UDP服务任务函数,需要注意的是recvfrom()函数

是阻塞调用的,直到接收到数据后才往下执行。inet_ntoa()函数是将IP地址转换为标准的

点分十进制显示。第60行的宏htons调用是因为不同机器的字节排列方式不一样,也就是俗称

的大端和小端排序,所以为了方便 络通信,统一先转换成 络字节序。

更改完成后,我们找到SSS_PORT宏定义的地方,也就是simple_socket_server.h文件的第

131行,将其值改为8080,即使用8080端口进行UDP服务。

最后我们打开iniche_init.c文件,删除第94行和第97行的两个函数(这两个函数在

udp_server.c中因未用到已经删除),函数名如下:

图 22.4.3 删除不需要的函数

经过以上修改后,软件设计部分就完成了。

下载验证

讲完了软件工程,接下来我们就将该实验下载至我们的开拓者开发板进行验证。

首先我们用一根 线将开发板和电脑进行连接,然后连接JTAG和电源,开发板上电后我们

在Quartus II软件中将qsys_eth.sof文件下载至我们的开拓者开发板,qsys_eth.sof下载完成

后,我们就将qsys_eth.elf文件系统下载至我们的开拓者开发板,下载完成后,控制台打印如

下信息:

图 22.5.1 启动UDP服务信息

现在,我们打开 络调试助手,设置如下图所示:

图 22.5.2 打开 络调试助手

打开 络调试助手后,协议类型选择:UDP;本地主机地址选择:本地连接的IP地址(在

这里是192.168.1.89);本地主机端口 :8080;设置完成后点击【打开】按钮。如下图所示:

图 22.5.3 设置发送相关信息

远程主机选择:192.168.1.234 : 8080(UDP服务器的IP地址和端口 ), 络调试助手

打开后,在发送文本框中输入数据“正点原子”并点击发送,如下图所示:

图 22.5.4 环回结果

可以看到 络调试助手接收到了UDP服务端返回的信息。与此同时,控制台打印如下信息:

图 22.5.5 控制台打印接收到的信息

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2020年7月12日
下一篇 2020年7月12日

相关推荐