如何区分Win32套接字句柄和其他管道句柄?

5
我需要确定一个非由我的代码创建的句柄,其GetFileType()==FILE_TYPE_PIPE,是否为套接字。目前似乎没有相应的API。
我尝试了以下方法。总体思路是使用套接字特定函数,并将失败视为非套接字。
  • getsockopt() -- 这是我的第一次尝试。不幸的是,当许多线程在同一个(非套接字)句柄上调用时,它似乎会挂起。
  • WSAEnumNetworkEvents() -- 这是Gnulib所做的,但如果该句柄是套接字,则会产生不良影响。
  • getpeername() -- 这是cygwin所做的,但这也会对某些套接字失败。猜测错误是否意味着套接字不太可靠且未来安全。
如果解决方案只适用于某些Windows版本(例如Vista),我并不介意,在一般情况下我可以退而求其次使用其他方法。
4个回答

2

我认为你可以尝试在句柄上调用GetNamedPipeInfo()。如果调用成功,你就知道这个句柄是一个管道句柄,否则它必须是一个套接字。


谢谢。这可能比使用特定于套接字的函数更安全(已知对于非套接字会挂起)。 - Per Mildner
GetNamedPipeInfo()可能会阻塞。请参考我的答案。 - undefined

1

你也可以使用GetNamedPipeHandleState(),并用GetLastError()评估结果。


GetNamedPipeHandleState()可能会阻塞。请查看我的答案。 - undefined

1
你尝试过使用WSADuplicateSocket吗?然后只需检查WSAPROTOCOL_INFO,以确定它是否实际上是一个命名管道...

我没有尝试这个,因为文档上说:“WSADuplicateSocket函数不能用于启用了QOS的套接字”,所以对于某些套接字来说,它可能会失败。 - undefined

0
我最终使用了getpeername(),但最近我们代码的一个用户报告说getpeername()似乎会卡住。这让我感到惊讶,因为例如一些相关文档中写道:“即使在阻塞套接字上,某些函数(例如getpeername)也会立即完成”(这是在实际套接字和Windows Sockets 1.1的上下文中,但仍然如此)。
问题在于,如果另一个线程已经在同一个输入流上进行阻塞的ReadFile()调用,那么对该输入流的getpeername()调用将会卡住。
因此,我进行了一个实验,使用了下面的程序。当另一个线程已经在同一个输入句柄上进行阻塞的ReadFile()调用时,得出的结论是:(更新:添加了套接字句柄的结果)
  • getpeername()会阻塞。
  • GetNamedPipeInfo()会阻塞。更糟糕的是,在某些情况下,它似乎会导致ReadFile()提前返回,成功返回零字节。
  • GetNamedPipeHandleState()所有可选参数为NULL(即不请求有关句柄的任何信息)在我的实验中没有阻塞但对于套接字而言无法失败,因此毫无用处。
  • GetNamedPipeHandleState()任一可选参数非NULL(即请求有关句柄的某些信息)。会阻塞。

因此,这些方法都无法以非阻塞的方式区分套接字和管道。

/*

Demonstrate various ways to distinguish between socket and pipe for an
input stream classified as FILE_TYPE_PIPE. Some of them hang if some
other thread is doing a blocking ReadFile() on the same input handle
at the same time.

  cl.exe getpeername_hang.c Ws2_32.lib /MD /Z7 /MD /WX  /W3

In a cmd.exe window (we use "powershell ... | " just to make a pipe as STD_INPUT_HANDLE input for getpeername_hang.exe) :

Test using getpeername(). This hangs.
  powershell -nop -c "& {sleep 30}" | .\getpeername_hang.exe getpeername

Test using GetNamedPipeInfo(). This hangs. Note: In cygwin Emacs shell the call to GetNamedPipeInfo() makes ReadFile() return success with zero bytes read, which is really scary.
  powershell -nop -c "& {sleep 30}" | .\getpeername_hang.exe getnamedpipeinfo

Test using GetNamedPipeHandleStateA(h, &state, NULL...). Either optioanal argument non-NULL (lpState or lpCurInstances). This hangs.
  powershell -nop -c "& {sleep 30}" | .\getpeername_hang.exe getnamedpipehandlestatea

Test using GetNamedPipeHandleStateA(h, NULL...). All optional arguments NULL. This does not hang but it does not fail for sockets, so it is useless.
  powershell -nop -c "& {sleep 30}" | .\getpeername_hang.exe getnamedpipehandlestatea_null


*/

#include <winsock2.h>
#include <windows.h>
#include <stdio.h>

static DWORD WINAPI readingThreadFunction(LPVOID lpParam)
{
  HANDLE hInput = *(HANDLE*)lpParam;
  CHAR buffer[256];
  DWORD bytesRead;

  // Read from the input (blocking read)
  if (ReadFile(hInput, buffer, sizeof(buffer), &bytesRead, NULL)) {
    fprintf(stdout, "Read %lu bytes from input\n", (unsigned long) bytesRead);
    fflush(stdout);
  } else {
    fprintf(stderr, "ReadFile failed. Error %lu\n", (unsigned long) GetLastError());
    fflush(stderr);
  }

  return 0;
}

static int isSocketGetpeername(HANDLE h)
{
  SOCKADDR_STORAGE peerAddr;
  int peerAddrLen = (int) sizeof(peerAddr);

  if (getpeername((SOCKET)h, (struct sockaddr*)&peerAddr, &peerAddrLen) == 0) {
    fprintf(stdout, "Success from getpeername()\n");
    return 1;
  } else {
    fprintf(stderr, "getpeername failed. Error %lu\n", (unsigned long)WSAGetLastError());
    return 0;
  }
}

static int isSocketGetNamedPipeInfo(HANDLE h)
{
  if (GetNamedPipeInfo(h, NULL, NULL, NULL, NULL)) {
    fprintf(stdout, "Success from GetNamedPipeInfo()\n");
    return 0;
  } else {
    fprintf(stderr, "GetNamedPipeInfo() failed. Error %lu\n", (unsigned long)GetLastError());
    return 1;
  }
}

static int isSocketGetNamedPipeHandleStateA_NULL(HANDLE h)
{
  if (GetNamedPipeHandleStateA(h, NULL, NULL, NULL, NULL, NULL, 0)) {
    fprintf(stdout, "Success from GetNamedPipeHandleStateA()\n");
    return 0;
  } else {
    fprintf(stderr, "GetNamedPipeHandleStateA() failed. Error %lu\n", (unsigned long)GetLastError());
    return 1;
  }
}

static int isSocketGetNamedPipeHandleStateA(HANDLE h)
{
  DWORD dummy;
  // Hangs: GetNamedPipeHandleStateA(h, &dummy, NULL, NULL, NULL, NULL, 0)
  // Hangs: GetNamedPipeHandleStateA(h, NULL, &dummy, NULL, NULL, NULL, 0)
  // Hangs: GetNamedPipeHandleStateW(h, &dummy, NULL, NULL, NULL, NULL, 0)
  // Hangs: GetNamedPipeHandleStateW(h, NULL, &dummy, NULL, NULL, NULL, 0)
  if (GetNamedPipeHandleStateA(h, &dummy, NULL, NULL, NULL, NULL, 0)) {
    fprintf(stdout, "Success from GetNamedPipeHandleStateA()\n");
    return 0;
  } else {
    fprintf(stderr, "GetNamedPipeHandleStateA() failed. Error %lu\n", (unsigned long)GetLastError());
    return 1;
  }
}

static char *getFileType(HANDLE h)
{
  DWORD fileType = GetFileType(h);
  switch (fileType)
    {
    case FILE_TYPE_DISK:
      return "FILE_TYPE_DISK";
    case FILE_TYPE_PIPE:
      return "FILE_TYPE_PIPE";
    case FILE_TYPE_CHAR:
      return "FILE_TYPE_CHAR";
    case  FILE_TYPE_UNKNOWN:
      return "FILE_TYPE_UNKNOWN";
    case FILE_TYPE_REMOTE:
      return "FILE_TYPE_REMOTE";
    default:
      return "GetFileType_IMPOSSIBLE_RESULT";
    }
}

int main(int argc, char *argv[])
{
  HANDLE hInput;
  HANDLE hThread;
  char *how;

  if (argc > 1) {
    how = argv[1];
  } else {
    how = "getpeername";
  }

  // Initialize Winsock
  int foo = 1;
  if (foo) {
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
      fprintf(stderr, "WSAStartup failed\n");
      return 1;
    }
  }

  // Get a handle to the standard input stream
  hInput = GetStdHandle(STD_INPUT_HANDLE);
  if (hInput == INVALID_HANDLE_VALUE) {
    fprintf(stderr, "Failed to get the input handle. Error %lu\n", GetLastError());
    return 1;
  }
  char *fileType = getFileType(hInput);
  fprintf(stdout, "Input is a %s\n", fileType);
  fflush(stdout);

  // Create a thread that reads from the input
  hThread = CreateThread(NULL, 0, readingThreadFunction, &hInput, 0, NULL);
  if (hThread == NULL) {
    fprintf(stderr, "Failed to create thread. Error %lu\n", GetLastError());
    return 1;
  }

  // Wait for a while to ensure readingThreadFunction() starts blocking in ReadFile().
  Sleep(1000);
  // At this point, the readingThreadFunction() should be blocked in ReadFile (and read *hInput)

  if (strcmp(fileType, "FILE_TYPE_PIPE") == 0) {
    fprintf(stdout, "Using method %s to determine whether input is a socket\n", how);
    fflush(stdout);

    int isSocket;
    ULONGLONG startTick = GetTickCount64();

    if (strcmp("getpeername", how) == 0) {
      isSocket = isSocketGetpeername(hInput);
    } else if (strcmp("getnamedpipeinfo", how) == 0) {
      isSocket = isSocketGetNamedPipeInfo(hInput);
    } else if (strcmp("getnamedpipehandlestatea", how) == 0) {
      isSocket = isSocketGetNamedPipeHandleStateA(hInput);
    } else if (strcmp("getnamedpipehandlestatea_null", how) == 0) {
      isSocket = isSocketGetNamedPipeHandleStateA_NULL(hInput);
    } else {
      fprintf(stderr, "Unknown variant %s\n", how);
      return 1;
    }

    ULONGLONG elapsedSeconds = (GetTickCount64() - startTick) / 1000;
    int did_block = (elapsedSeconds > 5); // plenty

    if (isSocket) {
      fprintf(stdout, "Input is a socket %s\n", (did_block ? "BLOCKED" : "(quickly determined)"));
    } else {
      fprintf(stdout, "Input is NOT a socket %s\n", (did_block ? "BLOCKED" : "(quickly determined)"));
    }
  } else {
    fprintf(stdout, "Input is neither a pipe nor a socket (%s)\n", fileType);
  }
  fflush(stdout);

  int sleep_before_exit = 1;
  if (sleep_before_exit) {
    fprintf(stdout, "Sleeping a while before exiting\n");
    fflush(stdout);
    Sleep(5000);
  }

  int do_proper_cleanup = 1;
  if (do_proper_cleanup) {
    fprintf(stdout, "Wait for ReadFile()-thread to exit\n");
    fflush(stdout);

    WaitForSingleObject(hThread, INFINITE);
    CloseHandle(hThread);
    WSACleanup();
  } else {
    (void)CancelSynchronousIo(hThread);
  }

  fprintf(stdout, "Successful exit\n");
  fflush(stdout);

  return 0;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接