我需要确定我的代码未创建的句柄(
GetFileType()==FILE_TYPE_PIPE
)是否是套接字。似乎没有这方面的API。
我尝试过以下方法。总体思路是使用套接字特定的函数并将失败视为非套接字。
getsockopt()
——这是我的第一次尝试。不幸的是,当同一(非套接字)句柄上的许多线程调用时,它似乎挂起。WSAEnumNetworkEvents()
——这就是 Gnulib 所做的,但如果句柄是套接字,则会产生不良副作用。getpeername()
——这就是 cygwin 所做的,但对于某些套接字来说这也会失败。猜测错误是否意味着套接字性似乎不可靠且未来安全。我不介意该解决方案是否仅适用于某些版本的 Windows,例如Vista,在一般情况下我总是可以回退到其他方法。
我想也许你可以尝试在你的句柄上调用 GetNamedPipeInfo()。如果调用成功你就知道该句柄是一个管道句柄,否则它一定是一个套接字。
你尝试过吗
WSADuplicateSocket
。然后只需检查 WSAPROTOCOL_INFO
看看它是否实际上是一个命名管道...
您也可以使用 GetNamedPipeHandleState(),并使用 GetLastError() 评估结果。
我最终使用了
getpeername()
,但最近我们代码的一位用户报告说 getpeername()
似乎挂起了。这是令人惊讶的,因为例如一些相关文档说“即使在阻塞套接字上,某些功能 - 例如 getpeername - 也会立即完成”(这是在实际套接字和 Windows Sockets 1.1 的上下文中,但仍然如此)。
问题是,如果某个其他线程已经在同一输入流上对
ReadFile()
进行阻塞调用,则该输入流上的 getpeername()
将挂起。
所以,我使用下面的程序做了一个实验。当另一个线程已经在同一个输入句柄上处于阻塞
ReadFile()
时,结论是:
getpeername()
将会挂起。GetNamedPipeInfo()
将挂起。更糟糕的是,在某些情况下,它似乎会导致 ReadFile()
提前成功返回,读取零字节。GetNamedPipeHandleState()
在我的实验中没有挂起,也没有任何不需要的副作用。据我所知,所有这些结果都没有记录,因此它们可能会随着下一个 Windows 更新而改变。另外,到目前为止我只测试了管道输入,没有使用套接字输入流进行测试(!)。
/*
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(). This does not hang.
powershell -nop -c "& {sleep 30}" | .\getpeername_hang.exe getnamedpipehandlestatea
*/
#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(HANDLE h)
{
if (GetNamedPipeHandleStateA(h, NULL, NULL, NULL, NULL, NULL, 0)) {
fprintf(stdout, "Success from GetNamedPipeHandleStateA()\n");
return 0;
} else {
fprintf(stderr, "GetNamedPipeInfo() 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", (unsigned long)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", (unsigned long)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
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 {
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;
}