asp.core api 通过socket和服务器通信发送udp_正点原子开拓者NiosII资料连载第二十二章UDP实验...

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

2)摘自《开拓者 Nios II开发指南》关注官方微信号公众号,获取更多资料:正点原子

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

e8fb583934c7c535d8267eea94d9e221.png

第二十二章基于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 数据报格式如下图:

936ceb07efea688a67cadd1c0c20eb6d.png

图 22.1.1 UDP数据报格式

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

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

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

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

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

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

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

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

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

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

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

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

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

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

如下图所示:

bf39cb2e21a702cb60419cb89fac3b04.png

图 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协议栈的代码。

51863dc71d36112173aaca876bcc4c5a.png

图 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!");

34 return;

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

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

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

38 printf("client msg: %s",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 %d", SSS_PORT);

67

68 conn.fd = socketfd;

69

70 sss_handle_msg(&conn);

71 close(conn.fd);

72 }

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

7c676b42cce5fa4eca157bd79905becb.png

图 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中因未用到已经删除),函数名如下:

90bbc27cc2d0f3b82e4b9ae3460e4941.png

图 22.4.3 删除不需要的函数

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

下载验证

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

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

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

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

下信息:

ba7eb83755ff32faf2c3764dba170483.png

图 22.5.1 启动UDP服务信息

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

7bec707ffca042240ec3f6b2d9c086de.png

图 22.5.2 打开网络调试助手

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

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

e9226b7016bed494b4a7b61df13082c8.png

图 22.5.3 设置发送相关信息

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

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

cbcba2722fecb7641e422648d18fde86.png

图 22.5.4 环回结果

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

a7ade44a905d1714b43a36daafe82480.png

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值