65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 NetLib 库进行网络套接字编程

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (6投票s)

2006年7月17日

CPOL

8分钟阅读

viewsIcon

38031

downloadIcon

267

Palm OS 操作系统提供了一个名为 NetLib 的库,可以方便地访问网络套接字。该库使得为 Palm OS 开发网络应用程序与为类 Unix 系统编写此类程序非常相似(同样简单)。

本文由 Dariusz Paduch 撰写,最初发表于 2005 年 6 月的《软件开发者杂志》。您可以在 SDJ 网站上找到更多文章。

图 1. Progress Manager 正在工作

引言

在接下来的文章中,我将演示如何为 Palm OS 编写一个应用程序,该应用程序利用系统库 NetLib 提供的网络套接字。使用此处描述的方法,可以创建一个功能上类似于 Linux 的 wget 的应用程序,使用 HTTP 协议从互联网下载选定的文件。我将假设读者熟悉 Palm OS 平台编程的基础知识。

Palm OS 下的套接字编程与类 Unix 系统下的编程几乎没有区别。因此,任何以前在 Unix 下进行过套接字编程的人,在 Palm OS 的情况下应用他们的解决方案应该不会遇到任何问题。

初始化 NetLib

为了利用系统库提供的优势,首先必须确定其引用号。使用该库的任何函数都需要此引用号。在函数之间切换时,应小心不要丢失引用号,因为否则程序将无法使用该库的任何函数。我们通过调用 SysLibFind() 来获取引用号,并将指向我们变量 (AppNetRefnum) 的指针作为其参数。当然,要查找的库必须存在于系统中,并且必须已经加载;尽管对于 NetLib 来说这通常不是问题,但最好还是做好防护以应对 SysLibFind() 返回的错误。

获得库的引用号后,就可以使用 NetLibOpen() 函数打开并初始化它;当然,我们必须将引用号作为其参数之一传递。该函数将尝试启动所有已配置的网络接口,因此如果没有可用的接口,它将返回错误。如果 NetLib 已经打开,函数返回的错误将是 netErrAlreadyOpen – 这个错误可以安全地忽略,因为它仅仅告知我们打开库不是必需的。这一点值得记住,以免由于此错误而中止整个操作。

在开始使用套接字之前,还应该做的一件事是测试网络连接。为此,应该使用 NetLibConnectionRefresh() 函数,该函数会检查并(如果我们将其第二个参数设置为 true)打开所有连接。

完成所有必需的套接字操作后,应该使用 NetLibClose() 函数关闭 NetLib 以清理系统;这将防止程序在以后出现 netErrAlreadyOpen 之类的错误。

寻址

Palm OS 下套接字的地址存储在相应的结构中,就像在 Unix 下一样。由于在我们的例子中使用了 Internet 套接字的地址,我们可以使用 NetSocketAddrINType 结构,其中存储三个值。地址族决定了地址的语法。我们将连接到 Internet,因此将使用 IP 地址(我们将此值写为 netSocketAddrINET)。剩余的是端口号和远程主机的 IP 地址。如果用户提供的是主机名而不是 IP 地址,则需要解析它;幸运的是,NetLibGetHostByName() 可以帮助我们。为了使用它,我们需要另一个结构来存储主机信息——NetHostInfoBufType。然后 NetLibGetHostByName() 函数的参数将是包含主机名的字符串和一个指向输出结构的指针。函数执行后,NetHostInfoBufType 将包含一个名为 address 的数组,其中存储了有关该主机的所有 IP 地址。地址数组的大小取决于 netDNSMaxAddresses 常量,因此它可以包含多个 IP 地址。我们只对一个感兴趣,所以我们只引用 address[0] 下存储的值。这样获得主机的 IP 地址后,就可以将其写入前面提到的 NetSocketAddrINType 地址结构中。

然后我们将使用此结构来将我们的套接字连接到远程主机。

列表 1. NetLib 库的初始化

Err err, errcode;
UInt8 allup;
UInt16 AppNetRefnum = 0;

// Look for a system library
err = SysLibFind("Net.lib", &AppNetRefnum);
if (err || !AppNetRefnum) return err;

// Open NetLib
NetLibOpen(AppNetRefnum, &errcode);
if (errcode && errcode != netErrAlreadyOpen)
return errcode;

// Refresh network connections
NetLibConnectionRefresh(AppNetRefnum,
true, &allup, &errcode);
if (errcode) {
 NetLibClose(AppNetRefnum, true);
 return errcode;
}
(...)
NetLibClose(AppNetRefnum, true);

套接字 (Sockets)

现在是时候实际打开网络套接字了。为了做到这一点,我们需要一些信息。首先,我们需要域——我们传递给 NetSocketAddrINType 作为地址族的那个值(netSocketAddrINET)。接下来,我们将定义套接字类型,即连接是使用数据报还是流;在我们的例子中,我们必须指定 netSocketTypeStream。我们还必须根据套接字类型选择合适的协议;对于流套接字,正确的协议是 TCP (netSocketProtoIPTCP)。我们将所有这些信息作为参数传递给 NetLibSocketOpen() 函数,该函数反过来返回一个套接字描述符,我们将将其存储在 NetSocketRef 类型的变量中。在对该套接字的所有操作中,我们将使用此已打开套接字的描述符。

我们已经有了一个打开的网络套接字和一个地址结构,因此是时候建立与远程主机的连接了(除非使用数据报套接字,在这种情况下,无需建立连接,在对套接字进行所有发送或接收操作时,只需提供相应主机的地址数据)。要将套接字连接到远程主机,我们使用 NetLibSocketConnect() 函数,其参数是套接字描述符和指向地址结构的指针。现在我们终于可以使用套接字描述符与远程主机进行通信了。

完成通信后,我们调用 NetLibSocketClose() 函数关闭网络套接字。

与远程主机通信

连接建立后,与服务器的实际对话只是发送查询和接收回复的问题。我们使用两个函数来发送和接收数据——NetLibSend()NetLibReceive()。它们都接受的参数是之前获得的套接字描述符、一个作为待发送数据缓冲区或用于接收数据的字符字符串,以及一个定义缓冲区长度的值。这两个函数都返回传输的字节数——这是一个非常有用的功能,用于验证通信的准确性。当函数返回零时,表示服务器已关闭套接字,意味着所有数据都已发送。

对于数据报套接字,我们还必须提供指向地址结构的指针及其大小。

在应用程序的这一点上,是时候开始考虑与服务器通信的方式,即协议了。我们必须知道要发送给服务器什么以及期望得到什么样的答复。以 wget 为例,使用的协议是 HTTP,因此我们也需要发送和接收 HTTP 头部。HTTP 服务器的响应由头部和数据组成。头部可以告诉我们传输的文件名和大小,为我们提供应该写入文件的字节数以及文件名等信息。头部还可以包含适当的错误消息,这在我们应用程序的错误处理函数中应该予以考虑。

列表 2. 寻址和与远程主机通信

char host[] = "www.palmtop.pl";
char buffer[40]; // A buffer for sent/received data
int bytes = 1;
NetSocketRef sock;
NetSocketAddrINType addr;
NetHostInfoBufType hostinfo;

NetLibGetHostByName(
AppNetRefnum, host, &hostinfo, -1, &errcode);

if (errcode) return errcode;
addr.family = netSocketAddrINET;
addr.port = 80;
addr.addr = hostinfo.address[0];

// Open a network socket
sock = NetLibSocketOpen(
AppNetRefnum, netSocketAddrINET, netSocketTypeStream,
netSocketProtoIPTCP, -1, &errcode);

// Connect the socket to the specifi ed host and port
NetLibSocketConnect(AppNetRefnum, sock, &addr,
sizeof(addr), -1, &errcode);
if (errcode) {
  NetLibSocketClose (AppNetRefnum, sock, -1, &err);
  return errcode;
}

// Transmit the contents of the buffer through
// the socket to the host
NetLibSend(AppNetRefnum, sock, buffer,
StrLen(buffer), 0, 0, 0, -1, &errcode);

// Retrieve data from the socket to the buffer
while(bytes > 0)
bytes = NetLibReceive(AppNetRefnum, sock,
buffer, 40, 0, 0, 0, -1, &errcode);

// Close the network socket
NetLibSocketClose(AppNetRefnum, sock, -1, &errcode)

与用户通信

在数据传输进行期间,让用户了解数据流的进度将很有帮助。为此,Palm OS 的创建者创建了一个名为 Progress Manager 的特殊界面。

使用 Progress Manager,我们可以显示和控制进度窗口的内容。窗口中显示的内容由程序员定义的函数 PrgCallbackFunc 控制。可以声明多种不同的进度状态,例如 ConnectingRetrievingDisconnecting。在连接到远程主机之前,我们会打开进度窗口,调用 PrgStartDialog 函数,并为其提供窗口标题和回调函数的指针;所有后续对窗口内容的进一步操作都应使用 PrgUpdateDialog 函数进行。此函数可以切换不同的进度状态、向回调函数提供附加文本输出或显示错误消息。另一个值得一提的是 PrgUserCancel 宏,它会检查用户是否在操作进行过程中取消了该操作。

下一步?

我们的程序还有很多可以改进的地方,为程序员提供了极好的机会。首先,应该记住加强错误检查。每个函数都可以返回大量的错误,在显示消息时应该考虑到其中的一些。所有这些错误的详细描述可以在 Palm OS API 文档中找到。此外,大多数操作套接字的函数都具有超时属性,值得留意并根据我们的需求进行适当调整,以避免用户不得不永远等待服务器响应。

网络资源

© . All rights reserved.