在Win64下将SOCKET转换为int是否安全?

Ryc*_*yck 23 c++ sockets winapi

我正在开发POSIX C++程序的Windows端口.

问题是标准POSIX函数如accept()或bind()期望'int'作为第一个参数,而其WinSock对应物使用'SOCKET'.
当编译为32位时一切都很好,因为两者都是32位,但在Win64下SOCKET是64位,int仍然是32位,它产生了很多编译器警告,如下所示:

warning C4244: '=' : conversion from 'SOCKET' to 'int', possible loss of data

我尝试使用typedef来解决这个问题:


#ifdef _WIN32
 typedef SOCKET sock_t;
#else
 typedef int sock_t;
#endif
Run Code Online (Sandbox Code Playgroud)

并在适当的位置用'sock_t替换'int'.

在我到达调用OpenSSL API的代码的一部分之前,这很好.
事实证明,OpenSSL甚至在Win64上也使用了套接字.这看起来很奇怪,所以我开始寻找答案,但我发现的唯一一件事是在openssl-dev邮件列表上的一个旧帖子,它引用了一条评论e_os.h:


/*
 * Even though sizeof(SOCKET) is 8, it's safe to cast it to int, because
 * the value constitutes an index in per-process table of limited size
 * and not a real pointer.
 */
Run Code Online (Sandbox Code Playgroud)

所以我的问题是:
将SOCKET转换为int是否真的安全?

我想看一些证明SOCKET值不能大于2 ^ 32的文档.

提前致谢!
Ryck

Pet*_*ham 9

这篇文章似乎是重复msdn 上内核对象的信息:

内核对象句柄是特定于进程的.也就是说,进程必须创建对象或打开现有对象以获取内核对象句柄.内核句柄的每进程限制为2 ^ 24.

该线程继续引用Russinovich和Solomon的Windows Internals作为高位为零的来源.

  • 最后,Winsock文档非常清楚,允许高于2 ^ 32的值:"Windows套接字句柄没有限制,除了值INVALID_SOCKET不是有效套接字.套接字句柄可以采用0到INVALID_SOCKET-范围内的任何值. 1"(http://msdn.microsoft.com/en-us/library/windows/desktop/ms740516(v=vs.85).aspx).唯一的优点是,由于许多糟糕的程序员假设SOCKET == int,微软不太可能改变他们的实现,因为它会打破所有这些程序. (4认同)
  • 这是各种错误的.首先,限制为2 ^ 24个内核句柄并不意味着句柄的值将在[0,2 ^ 24-1]范围内.其次,内核句柄的数量是一个内部实现细节,可以随时更改(因此标题为"Windows _Internals_"). (3认同)
  • 因为虽然高位为零(今天),但这是一个实现细节。这不是契约性的。微软明天可能会改变这一点。还要记住,socket() 函数_不_承诺返回内核句柄,因此即使讨论内核句柄的属性也无关紧要。 (2认同)

Pet*_*man 9

这个问题的简单答案是否定的。看看MSDN[1]上对SOCKET值的描述:

除了值 INVALID_SOCKET 不是有效的套接字之外,Windows 套接字句柄没有任何限制。套接字句柄可以采用 0 到 INVALID_SOCKET–1 范围内的任何值。

很明显,在 64 位 Windows 上,API 允许范围 [0, 2^64 - 1) 内的所有值。如果 API 曾经返回过大于 2^32 - 1 的值,则将其分配给 int 会导致句柄截断。还可以看看 socket() 函数 [2] 的返回值的描述:

如果没有发生错误,套接字返回一个引用新套接字的描述符。

请注意,它最强调的是不承诺返回内核句柄。这使得关于内核句柄的可能值的任何讨论都没有实际意义。

尽管如此,在撰写本文时,socket() 函数确实返回了一个内核句柄(或与内核句柄无法区分的东西)[3],而内核句柄实际上仅限于 32 位 [4]。但请记住,微软明天可以在不破坏其接口合同的情况下更改其中任何一项。

然而,由于无疑有大量应用程序依赖于这些特定的实现细节(更重要的是,OpenSSL 也是如此),微软可能会在做出任何重大更改时三思而后行。因此,继续将 SOCKET 转换为 int。请记住,这是一种固有的危险、糟糕的做法,并且永远不会以权宜之计。

  1. http://msdn.microsoft.com/en-us/library/windows/desktop/ms740516(v=vs.85).aspx
  2. http://msdn.microsoft.com/en-us/library/windows/desktop/ms740506(v=vs.85).aspx
  3. http://msdn.microsoft.com/en-us/library/windows/desktop/ms742295(v=vs.85).aspx
  4. http://msdn.microsoft.com/en-us/library/windows/desktop/aa384267(v=vs.85).aspx

编辑 (2018-01-29)

由于这个主题似乎仍然有些有趣,值得指出的是,在 C++11 中编写可移植套接字代码非常容易,而无需求助于有问题的类型转换:

using socket_t = decltype(socket(0, 0, 0));

socket_t s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
Run Code Online (Sandbox Code Playgroud)