2.6 与Berkeley套接口的不同 |
有一些很有限的地方,Windows Sockets API必须与从严格地坚持Berkeley传统风格中解放出来。通常这么做是因为在Windows环境中实现的难度。 |
2.6.1 套接口数据类型和错误数值 |
Windows Sockets规范中定义了一个新的数据类型SOCKET,这一类型的定义对于将来Windows Sockets规范的升级是必要的。例如在Windows NT中把套接口作为文件句柄来使用。这一类型的定义也保证了应用程序向Win/32环境的可移植性。因为这一类型会自动地从16位升级到32位。 |
在UNIX中所有句柄包括套接口句柄,都是非负的短整数,而且一些应用程序把这一假设视为真理。Windows Sockets句柄则没有这一限制,除了INVALID_SOCKET不是一个有效的套接口外,套接口可以取从0到INVALID_SOCKET-1之间的任意值。 因为SOCKET类型是unsigned,所以编译已经存在于UNIX环境中的应用程序的源代码可能会导致signed/unsigned数据类型不匹配的警告。 |
这还意味着,在socket()例程和accept()例程返回时,检查是否有错误发生就不应该再使用把返回值和-1比较的方法,或判断返回值是否为负(这两种方法在BSD中都是很普通,很合法的途径)。取而代之的是,一个应用程序应该使用常量INVALID_SOCKET,该常量已在WINSOCK.H中定义。 |
例如:
典型的BSD风格:
s = socket(...);
if (s == -1) /* of s<0 */
{...}
更优良的风格:
s = socket(...);
if (s == INVALID_SOCKET)
{...} |
2.6.2 select()函数和FD_*宏 |
由于一个套接口不再表示了UNIX风格的小的非负的整数,select()函数在Windows Sockets API中的实现有一些变化:每一组套接口仍然用fd_set类型来代表,但是它并不是一个位掩码。整个组的套接口是用了一个套接口的数组来实现的。为了避免潜在的危险,应用程序应该坚持用FD_XXX宏来设置,初始化,清除和检查fd_set结构。 |
2.6.3 错误代码-errno,h_errno,WSAGetLastError() |
Windows Sockets实现所设置的错误代码是无法通过errno变量得到的。另外对于getXbyY()这一类的函数,错误代码无法从h_errno变量得到。错误代码可以使用WSAGetLastError()调用得到。这一函数在5.3.11中讨论。这个函数在Windows Sockets实现中是作为WIN/32函数GetLastError()的先导函数(最终是一个别名)。这样做是为了在多线程的进程中为每一线程得到自己的错误信息提供可靠的保障。 |
为了保持与BSD的兼容性,应用程序可以加入以下一行代码: |
#define errno WSAGetLastError() |
这就保证了用全程的errno变量所写的网络程序代码在单线程环境中可以正确使用。当然,这样做有许多明显的缺点:如果一个原程序包含了一段代码对套接口和非套接口函数都用errno变量来检查错误,那么这种机制将无法工作。此外,一个应用程序不可能为errno赋一个新的值(在Windows Sockets中,WSASetLastError()函数可以做到这一点)。 |
例如:
典型的BSD风格:
r = recv(...);
if (r == -1 /* 但请见下文 */
&& errno == EWOULDBLOCK)
{...}
更优良的风格:
r = recv(...);
if (r == -1 /* 但请见下文 */
&& WSAGetLastError() == EWOULDBLOCK)
{...} |
虽然为了兼容性原因,错误常量与4.3BSD所提供的一致;应用程序应该尽可能地使用"WSA"系列错误代码定义。例如,一个更准确的上面程序片断的版本应该是: |
r = recv(...);
if (r == -1 /* 但请见下文 */
&& WSAGetLastError() == WSAEWOULDBLOCK)
{...} |
2.6.4 指针 |
所有应用程序与Windows Sockets使用的指针都必须是FAR指针,为了方便应用程序开发者使用,Windows Sockets规范定义了数据类型LPHOSTENT。 |
2.6.5 重命名的函数 |
有两种原因Berkeley套接口中的函数必须重命名以避免与其他的API冲突: |
2.6.5.1 close()和closesocket() |
在Berkeley套接口中,套接口出现的形式与标准文件描述字相同,所以close()函数可以用来和关闭正规文件一样来关闭套接口。虽然在Windows Sockets API中,没有任何规定阻碍Windows Sockets实现用文件句柄来标识套接口,但是也没有任何规定要求这么做。套接口描述字并不认为是和正常文件句柄对应的,而且并不能认为文件操作,例如read(),write()和close()在应用于套接口后不能保证正确工作。套接口必须使用closesocket()例程来关闭,用close()例程来关闭套接口是不正确的,这样做的效果对于Windows Sockets规范说来也是未知的。 |
2.6.5.2 ioctl()和iooctlsocket() |
许多C语言的运行时系统出于与Windows Sockets无关的目的使用ioctl()例程,所以Windows Sockets定义ioctlsocket()例程。它被用于实现BSD中用ioctl()和fcntl()实现的功能。 |
2.6.6 阻塞例程和EINPROGRESS宏 |
虽然Windows Sockets支持关于套接口的阻塞操作,但是这种应用是被强烈反对的.如果程序员被迫使用阻塞模式(例如一个准备移植的已有的程序),那么他应该清楚地知道Windows Sockets中阻塞操作的语义。有关细节请参见4.1.1 |
2.6.7 Windows Sockets支持的最大套接口数目 |
一个特定的Windows Sockets提供者所支持的套接口的最大数目是由实现确定的。任何一个应用程序都不应假设某个待定数目的套接口可用。这一点在4.3.15 WSAStartup()中会被重申。而且一个应用程序可以真正使用的套接口的数目和某一特定的实现所支持的数目是完全无关的。 |
一个Windows Sockets应用程序可以使用的套接口的最大数目是在编译时由常量FD_SETSIZE决定的。这个常量在select()函数(参见4.1.18)中被用来组建fd_set结构。在WINSOCK.H中缺省值是64。如果一个应用程序希望能够使用超过64个套接口,则编程人员必须在每一个源文件包含WINSOCK.H前定义确切的FD_SET值。有一种方法可以完成这项工作,就是在工程项目文件中的编译器选项上加入这一定义。例如在使用Microsoft C时加入-D FD_SETSIZE=128作为编译命令的一个命令行参数.要强调的是:FD_SET定义的值对Windows Sockets实现所支持的套接口的数目并无任何影响。 |
2.6.8 头文件 |
为了方便基于Berkeley套接口的已有的源代码的移植,Windows Sockets支持许多Berkeley头文件。这些Berkeley头文件被包含在WINSOCK.H中。所以一个Windows Sockets应用程序只需简单的包含WINSOCK.H就足够了(这也是一种被推荐使用的方法)。 |
2.6.9 API调用失败时的返回值 |
常量SOCKET_ERROR是被用来检查API调用失败的。虽然对这一常量的使用并不是强制性的,但这是推荐的。如下的例子表明了如何使用SOCKET_ERROR常量 |
典型的BSD风格:
r = recv(...);
if (r == -1 /* or r < 0 */
&& errno == EWOULDBLOCK)
{...}
更优良的风格:
r = recv(...);
if (r == SOCKET_ERROR && WSAGetLastError == WSAEWOULDBLOCK)
{...} |
2.6.10 原始套接口 |
Windows Sockets规范并没有规定Windows Sockets DLL必须支持原始套接口-用SOCK_RAW打开的套接口。然而Windows Sockets规范鼓励Windows Sockets DLL提供原始套接口支持。一个Windows Sockets兼容的应用程序在希望使用原始套接口时应该试图用socket()调用(参见5.1.23节)来打开套接口。如果这么做失败了,应用程序则应该使用其他类型的套接口或向用户报告错误。 |