UEFI开发探索54 – UEFI与网络4(IPv4)

请保留-> 【原文:  https://blog.csdn.net/luobing4365http://yiiyee.cn/blog/author/luobing/】

本篇主要讨论怎么使用StdLib的库函数编写TCP4的代码。

上一篇中直接使用UEFI提供的TCP4 Protocol编写代码,比较复杂,需要自己重新构建所有代码。我们还是希望能够直接使用标准的Socket库来编程,这样可以直接复用以前编写的Socket代码,减少编程的工作量。

1 BSD Socket接口

StdLib中提供的是标准BSD Socket接口,允许不同主机或同一计算机的不同进程之间的通信,是事实上的连接互联网的标准接口。

BSD Socket中要求的API接口,在UEFI的实现中基本都提供了。主要的头文件如下(StdLib\Include下):

<sys/socket.h>  socket 的核心函数和数据结构;
<netinet/in.h>  互联网地址族,AF_INET 和AF_INET6 地址家族和他们对应的协议家族 PF_INET 和 PF_INET6,包括IP地址以及TCP和UDP端口号;
<arpa/inet.h> 和IP地址相关的一些函数,比如地址转换的inet_pton()等;

由于UEFI并非完整的操作系统,也有很多BSD Socket中的API没有实现,比如针对计算机上程序间的本地通信的un.h。这些没有实现的函数,大部分是与网络通信无关,对我们的UEFI网络编程影响不大。

大部分的程序员应该都编写过Socket的代码,UEFI下编写的方法其实差不多。如果有需要,是可以把以前编写的代码直接移植过来的。

2 函数介绍

Socket中提供的库函数,介绍如下(以下内容只针对UEFI平台有效)。

int socket(IN INT32 domain, IN INT32 type, IN INT32 protocol);
  ●功能  创造某种类型的套接字,分配系统资源。
  ●参数  domain:确定协议族,PF_INET为IPv4,PF_INET6为IPv6
          type: 类型,支持SOCK_STREAM、SOCK_DGRAM和SOCK_RAW;
          protocol: 指定传输层所用协议。IPPROTO_TCP,type必须指定为SOCK_STREAM; IPPROTO_UDP,type必须指定为SOCK_DGRAM;0-254,type为SOCK_RAW,值的含义可参考:https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xml
  ●返回值  返回socket句柄,如果返回-1,则表明有错误发生

int bind ( IN int s, IN const struct sockaddr * name, IN socklen_t namelen );
●功能  将socket句柄和套接字地址结构相连。
●参数  s: scoket句柄,由socket()函数返回;
          name: 指向sockaddr 结构体的指针,代表要绑定的地址;
          namelen: sockaddr结构体的大小
 ●返回值  0表示操作成功,-1表示失败

int listen (IN int s, IN int backlog );
●功能  监听连接,适用于面向连接的模式,比如类型为SOCK_STREAM的套接字。
  ●参数  s: scoket句柄,由socket()函数返回;
          backlog: 能够等待的最大连接数目;  
  ●返回值  0表示操作成功,-1表示失败

int connect ( int s, const struct sockaddr * address, socklen_t address_len );
●功能  通过网络建立连接
  ●参数  s: scoket句柄,由socket()函数返回;
          address: 网络地址;
          namelen: 网络地址的字节长度
  ●返回值  0表示操作成功,-1表示失败

int accept ( int s, struct sockaddr * address, socklen_t * address_len );
●功能  接收网络连接,一般用于服务端程序
  ●参数  s: scoket句柄,由socket()函数返回;
          address: 网络地址;
          namelen: 网络地址的字节长度
  ●返回值  0表示操作成功,-1表示失败

ssize_t  recv ( int s, void * buffer, size_t length, int flags );
●功能  接收数据
  ●参数  s: scoket句柄,由socket()函数返回;
          buffer: 用来接收数据的缓冲区;
          flags: 用来控制接收方式的标志,比如接收OOB(带外数据)。一般设为0;
  ●返回值  -1表示失败,其他大于-1的整数值表示接收到的数据个数

ssize_t  send ( int s, CONST void * buffer, size_t length, int flags );
●功能  发送数据
  ●参数  s: scoket句柄,由socket()函数返回;
          buffer: 用来发送数据的缓冲区;
          flags: 用来控制发送方式的标志,比如发送OOB(带外数据)。一般设为0;
  ●返回值  -1表示失败,其他大于-1的整数值表示发送的数据个数

其他函数,包括recvfrom()、sendto()、setsockopt()等,在本篇的例程中没有用到,可以查询相关的资料。

3 编写程序

我准备的例子中,服务端用网络调试助手,客户端用UEFI的NT32模拟环境。当然,也可以写个服务端的代码,能更好地展示网络编程的过程,找时间再写吧。

TCP的网络服务端代码的编写流程一般为:

1) 使用socket()创建Socket;
2) 使用bind(),把Socket和本机的IP、TCP端口绑定;
3) 为客户端的连接创建等待队列;
4) 循环处理连接,通过accept()接收客户端的连接;
5) 使用recv()或send()接收、发送数据;
6) 使用close()关闭Socket,终止通信。

TCP客户端编写流程为:

1) 使用socket()创建Socket;
2) 使用connect()向服务端发送连接请求,等待回应,并向下执行;
3) 循环处理连接,使用recv ()或send()接收、发送数据;
4) 使用close()关闭Socket,终止通信。

UEFI的代码编写,基本按照上述的流程来写的,具体可以查看提供的例程。TCP通信的函数代码中,全部使用了StdLib中的函数。

编译命令如下:

C:\MyWorkspace> build -p RobinPkg\RobinPkg.dsc -a IA32 -m RobinPkg\Applications\stdEchoTcp4\stdEchoTcp4.inf

意外的是,编译的时候会报错误。查看了下报错位置,是由\StdLib\BsdSocketLib\ns_addr.c中的line 83行语句引起的:

if ((cp = strchr(buf, ‘:’)) && 

因为在条件表达式内赋值,引发了C4706的警告,修正起来也很简单。不过,我不想动StdLib的源码,采用了修改编译选项的方式来解决它。

在conf\tool_def.txt中,找到DEBUG_VS2015x86_IA32_CC_FLAGS的赋值行(我一般编译的时候是用debug编译的,也就是使用-b DEBUG选项。如果是Release,则修改DEBUG_VS2015x86_IA32_CC_FLAGS的赋值语句),把/W4改为/W3即可。

4 测试程序

搭建网络测试环境的方法,在之前的博客中已经讨论过了,这里不再重复。

1) 启动服务端程序

图1 服务端

2 启动UEFI客户端程序

图2 客户端

3 通讯测试

图3 发送接收测试

百度云链接:https://pan.baidu.com/s/1gccSosw8_UAGTI5gZPnLCA
提取码:dx23
文件在 FF RobinPkg/ RobinPkg /Applications/stdEchoTCP4

3,828 total views, 4 views today

《UEFI开发探索54 – UEFI与网络4(IPv4)》有3个想法

发表评论

电子邮件地址不会被公开。