非阻塞 Socket 是高併發網路程式設計的基石。本文詳細說明 Windows 與 Linux 設定 Socket 非阻塞模式的標準方法,並深入解析 Linux fcntl 系統呼叫的完整功能。
使用 ioctlsocket 配合 FIONBIO 指令:
unsigned long ul = 1; // 1 = 非阻塞,0 = 阻塞
SOCKET s = socket(AF_INET, SOCK_STREAM, 0);
int ret = ioctlsocket(s, FIONBIO, &ul);
if (ret == SOCKET_ERROR) {
// 錯誤處理
int err = WSAGetLastError();
}
關鍵點:參數為指向 unsigned long 的指標,非零值啟用非阻塞模式。
使用 fcntl 修改檔案狀態旗標 O_NONBLOCK:
// 設為非阻塞 int flags = fcntl(socket, F_GETFL, 0); fcntl(socket, F_SETFL, flags | O_NONBLOCK); // 恢復阻塞模式 int flags = fcntl(socket, F_GETFL, 0); fcntl(socket, F_SETFL, flags & ~O_NONBLOCK);
核心邏輯:先取得目前旗標,再透過位元運算增減 O_NONBLOCK,最後寫回。
fcntl 依據 cmd 參數提供豐富的檔案描述符控制功能:
F_DUPFD:複製檔案描述符FD_CLOEXEC:exec 時自動關閉 (配合 F_SETFD/F_GETFD)F_GETFL/F_SETFL)可修改旗標:O_APPEND、O_ASYNC、O_DIRECT、O_NOATIME、O_NONBLOCK
不可修改:O_RDONLY、O_WRONLY、O_RDWR、O_CREAT、O_EXCL、O_NOCTTY、O_TRUNC
F_GETLK/F_SETLK/F_SETLKW)struct flock {
short l_type; // F_RDLCK(讀鎖), F_WRLCK(寫鎖), F_UNLCK(解鎖)
short l_whence; // SEEK_SET, SEEK_CUR, SEEK_END
off_t l_start; // 相對偏移
off_t l_len; // 鎖定長度 (0=到檔案結尾)
pid_t l_pid; // 持有鎖的進程 ID (F_GETLK 回傳)
};
F_SETLK:非阻塞加/解鎖,衝突即返回 EACCES/EAGAINF_SETLKW:阻塞等待鎖,可被訊號中斷 (EINTR)F_GETLK:檢測鎖衝突前置條件:讀鎖需以讀模式開啟檔案,寫鎖需寫模式,讀寫鎖需讀寫模式。
F_GETOWN/F_SETOWN/F_GETSIG/F_SETSIG)設定接收 SIGIO/SIGURG 的進程/進程組,配合 F_SETSIG 可指定自訂訊號,實現完整非同步 I/O,無需 select/poll。
F_SETLEASE/F_GETLEASE)F_RDLCK:讀租約 — 其他進程寫入/截斷時通知F_WRLCK:寫租約 — 其他進程讀取/寫入/截斷時通知F_UNLCK:移除租約參數為位元組合:DN_ACCESS、DN_MODIFY、DN_CREATE、DN_DELETE、DN_RENAME、DN_ATTRIB。監控目錄下檔案變更事件。
| 命令 | 成功返回 |
|---|---|
| F_DUPFD | 新檔案描述符 |
| F_GETFD/F_GETFL | 旗標值 |
| F_GETOWN | 擁有者 PID/PGID |
| F_GETSIG | 訊號編號 |
| 其他 | 0 |
失敗統一返回 -1,errno 可能值:EACCES/EAGAIN、EBADF、EDEADLK、EFAULT、EINTR、EINVAL、EMFILE、ENOLCK、EPERM。
bool SetNonBlocking(SOCKET sock) {
#ifdef _WIN32
unsigned long mode = 1;
return ioctlsocket(sock, FIONBIO, &mode) != SOCKET_ERROR;
#else
int flags = fcntl(sock, F_GETFL, 0);
if (flags == -1) return false;
return fcntl(sock, F_SETFL, flags | O_NONBLOCK) != -1;
#endif
}
Windows 使用 ioctlsocket、Linux 使用 fcntl 設定非阻塞模式,兩者機制不同但目的一致。fcntl 更是 Linux 檔案描述符控制的瑞士刀,掌握其完整功能對系統級程式設計至關重要。建議封裝跨平台介面,統一上層呼叫介面。