我最终使用了
getpeername()
,但最近我们代码的一个用户报告说
getpeername()
似乎会卡住。这让我感到惊讶,因为例如
一些相关文档中写道:“即使在阻塞套接字上,某些函数(例如getpeername)也会立即完成”(这是在实际套接字和Windows Sockets 1.1的上下文中,但仍然如此)。
问题在于,如果另一个线程已经在同一个输入流上进行阻塞的
ReadFile()
调用,那么对该输入流的
getpeername()
调用将会卡住。
因此,我进行了一个实验,使用了下面的程序。当另一个线程已经在同一个输入句柄上进行阻塞的
ReadFile()
调用时,得出的结论是:
(更新:添加了套接字句柄的结果)
getpeername()
会阻塞。
GetNamedPipeInfo()
会阻塞。更糟糕的是,在某些情况下,它似乎会导致ReadFile()
提前返回,成功返回零字节。
GetNamedPipeHandleState()
所有可选参数为NULL(即不请求有关句柄的任何信息)在我的实验中没有阻塞但对于套接字而言无法失败,因此毫无用处。
GetNamedPipeHandleState()
任一可选参数非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;
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;
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";
}
int foo = 1;
if (foo) {
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
fprintf(stderr, "WSAStartup failed\n");
return 1;
}
}
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);
hThread = CreateThread(NULL, 0, readingThreadFunction, &hInput, 0, NULL);
if (hThread == NULL) {
fprintf(stderr, "Failed to create thread. Error %lu\n", GetLastError());
return 1;
}
Sleep(1000);
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);
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;
}