GetLogicalDrives无法识别VPN断开,如何避免GetFileAttributesEx调用30秒卡顿?
问题解决方案
问题根因
GetLogicalDrives只会读取系统缓存的驱动器列表,映射的网络驱动器断开后,系统不会立刻更新缓存,因此会误判为可用GetFileAttributesEx是同步API,访问断开的SMB共享时,会遵循系统默认的SMB会话超时规则(通常为30秒),导致线程阻塞
可行解决方案
方案1:前置状态/连通性检测(推荐,改造成本最低)
在调用GetFileAttributesEx之前,先执行低成本的状态校验,确认正常后再执行文件属性查询,避免无意义的阻塞:
- 先调用
WNetGetConnectionAPI查询V盘的映射连接状态,如果返回ERROR_NOT_CONNECTED直接判定驱动器不可用,该API的响应速度远快于文件查询 - 额外增加VPN对端主机的连通性检测:优先检测共享盘对应主机的445端口(SMB服务端口),设置1~2秒超时,端口不通直接判定不可用;如果允许ICMP协议也可以用ping检测,超时设置为2秒以内
这样可以过滤掉绝大多数VPN断开场景,不会走到卡顿的文件查询逻辑。
方案2:将检测逻辑移入独立工作线程
如果不想改动现有检测逻辑,直接把整个驱动器检测的代码放到单独的后台线程执行,阻塞只会发生在后台线程,不会影响主程序的响应。检测到状态变化(断开/重连)时,再通过消息、回调等方式通知主线程更新状态即可。
方案3:使用异步文件API替代同步调用
调用CreateFileW时传入FILE_FLAG_OVERLAPPED标志开启异步模式,查询文件属性的请求可以自定义超时时间,超过2秒没有返回就主动调用CancelIoEx取消请求,判定驱动器不可用,完全避免长时间阻塞。
重连检测实现
可以保持原有的10秒轮询逻辑,每次轮询先做前置连通性/状态检测,确认网络恢复后再执行文件属性校验,即可及时捕获VPN重连状态。
改造代码示例
#include <winsock2.h> #include <windows.h> #include <stdio.h> #pragma comment(lib, "ws2_32.lib") #pragma comment(lib, "mpr.lib") // 检测指定IP的445端口是否可通,超时1000毫秒 BOOL CheckSMBPortAvailable(const char* ip) { WSADATA wsa; SOCKET s = INVALID_SOCKET; struct sockaddr_in addr; DWORD timeout = 1000; BOOL ret = FALSE; WSAStartup(MAKEWORD(2,2), &wsa); s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (s == INVALID_SOCKET) goto cleanup; // 设置连接超时 setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof(timeout)); addr.sin_family = AF_INET; addr.sin_port = htons(445); addr.sin_addr.s_addr = inet_addr(ip); // 替换为你家电脑VPN内网的IP if (connect(s, (struct sockaddr*)&addr, sizeof(addr)) == 0) { ret = TRUE; } cleanup: if (s != INVALID_SOCKET) closesocket(s); WSACleanup(); return ret; } int main() { #define DRIVE 'V' // VPN drive #define FILE "V:\\Folder\\File.txt" // file on VPN drive WIN32_FILE_ATTRIBUTE_DATA attribute; char drivePath[4] = {DRIVE, ':', '\\', 0}; char remotePath[MAX_PATH] = {0}; for ( ; ; ) { Sleep(10000); // 10 seconds // 前置1:检测映射盘连接状态 if (WNetGetConnection(drivePath, remotePath, &(DWORD){MAX_PATH}) == ERROR_NOT_CONNECTED) { fprintf(stdout, "Drive %c: is unavailable\n", DRIVE); continue; } // 前置2:检测SMB端口连通性 if (!CheckSMBPortAvailable("192.168.1.100")) { // 替换为实际对端IP fprintf(stdout, "Drive %c: is unavailable\n", DRIVE); continue; } // 再执行文件校验 if (GetLogicalDrives() & (1 << (DRIVE - 'A'))) { fprintf(stdout, "Drive %c: is available\n", DRIVE); if (!GetFileAttributesEx(FILE, GetFileExInfoStandard, &attribute)) { fprintf(stdout, "GetFileAttributesEx() failed, error = %d\n", GetLastError()); continue; } fprintf(stdout, "All is ok\n"); } else fprintf(stdout, "Drive %c: is unavailable\n", DRIVE); } return 0; }
内容的提问来源于stack exchange,提问作者Codybear
相关产品推荐
相关产品推荐

