如何区分套接字句柄和文件句柄

How to differentiate between socket handle and file handle

我需要检查 windows 中进程创建事件的某些行为,我需要实施一个规则来检查传递给 createprocess api 调用的 startupinfo 结构并提取 std input/std 输出处理创建过程的值。 然后我必须检查这个句柄是否属于 tcp 套接字。 是否有任何 api 函数可以帮助我获取有关我拥有的句柄号(无论是文件句柄还是套接字句柄)的任何信息?

使用GetFileType()函数

Retrieves the file type of the specified file.

Syntax

DWORD WINAPI GetFileType( _In_ HANDLE hFile ); 

Parameters

hFile [in]

A handle to the file.

Return value

The function returns one of the following values.

FILE_TYPE_CHAR

The specified file is a character file, typically an LPT device or a console.

FILE_TYPE_DISK

The specified file is a disk file.

FILE_TYPE_PIPE

The specified file is a socket, a named pipe, or an anonymous pipe.

FILE_TYPE_REMOTE

Unused.

FILE_TYPE_UNKNOWN

Either the type of the specified file is unknown, or the function failed

基于@RemyLebeau 的回答,我想看看我是否能找到一种可靠的方法来区分套接字和管道(GetFileType() 做不到),我想出了以下方法,似乎有效并且没有明显的缺点。

它的要点是如果传递任何不是 SOCKET 的东西,getsockopt() 将 return WSAENOTSOCK (= 10038)。因此,这个测试本身就足够了,只要 传递给它的任何句柄是 SOCKET 或文件或管道 HANDLE。不要只传递任何旧的 HANDLE(有各种各样的),否则它可能会根据下面@HansPassant 的第一条评论感到困惑。

示例代码:

#include <winsock2.h>                   // * before* windows.h (!)
#include <windows.h>
#include <assert.h>
#include <iostream>

int main ()
{
    WSADATA wsa_data;
    int err = WSAStartup (2, &wsa_data);
    assert (err == 0);

    // Pipe

    HANDLE hReadPipe, hWritePipe;
    BOOL ok = CreatePipe (&hReadPipe, &hWritePipe, NULL, 2048);
    assert (ok);

    int opt;
    int optlen = sizeof (opt);
    err = getsockopt ((SOCKET) hReadPipe, IPPROTO_TCP, TCP_NODELAY, (char *) &opt, &optlen);

    if (err)
    {
        DWORD dwErr = GetLastError ();
        std::cout << "Pipe: " << dwErr << std::endl;
    }
    else
        std::cout << "Pipe: OK" << std::endl;

    CloseHandle (hReadPipe);
    CloseHandle (hWritePipe);

    // Socket

    SOCKET skt = WSASocket (AF_INET, SOCK_STREAM, 0, NULL, 0, 0);
    assert (skt != INVALID_SOCKET);

    optlen = sizeof (opt);
    err = getsockopt (skt, IPPROTO_TCP, TCP_NODELAY, (char *) &opt, &optlen);

    if (err)
    {
        DWORD dwErr = GetLastError ();
        std::cout << "Socket: " << dwErr << std::endl;
    }
    else
        std::cout << "Socket: OK" << std::endl;

    closesocket (skt);
    return 0;
}

输出:

Pipe: 10038
Socket: OK

编辑: 如果您阅读下面的评论,您会看到有一些关于此代码是否可以导致相信文件或管道 HANDLE 实际上是 SOCKET 的讨论.好吧,它不能。我们可以知道这一点,因为像 ReadFile()WriteFile() 这样的函数在文件/管道 HANDLE 和 SOCKET 上工作得同样好,如果有任何可能将一个误认为另一个,那将不会工作。

因此,此代码 (a) 安全,(b) 简单,并且 (c) 在所有情况下都有效(包括重定向输出,Remy 的代码会将其视为套接字)。因此我推荐它。请确保在执行任何其他操作之前调用 WSAStartup()

感谢@HansPassant 和@eryksun 对此做出的重要贡献post。

使用 GetNamedPipeInfo(s, NULL, NULL, NULL, NULL) 区分管道和套接字。

bool is_socket(LPVOID s)
{
  if (GetFileType(s) != FILE_TYPE_PIPE) return false;
  return !GetNamedPipeInfo(s, NULL, NULL, NULL, NULL);
}