libssh2非阻塞SFTP示例疑问:为何仅部分函数调用waitsocket()
问题描述
我研究了libssh2的非阻塞SFTP写入实现示例,发现处理非阻塞函数调用返回值时存在如下差异:
waitsocket()未被用于libssh2_session_handshake()、libssh2_userauth_password()、libssh2_userauth_publickey_fromfile()、libssh2_sftp_init()、libssh2_sftp_open()等函数,例如:
while((rc = libssh2_session_handshake(session, sock)) == LIBSSH2_ERROR_EAGAIN); if(rc) { fprintf(stderr, "Failure establishing SSH session: %d\n", rc); goto shutdown; }
但该函数被用于libssh2_sftp_write():
while((nwritten = libssh2_sftp_write(sftp_handle, ptr, nread)) == LIBSSH2_ERROR_EAGAIN) { waitsocket(sock, session); }
以下是waitsocket()的实现代码:
static int waitsocket(libssh2_socket_t socket_fd, LIBSSH2_SESSION *session) { struct timeval timeout; int rc; fd_set fd; fd_set *writefd = NULL; fd_set *readfd = NULL; int dir; timeout.tv_sec = 10; timeout.tv_usec = 0; FD_ZERO(&fd); FD_SET(socket_fd, &fd); /* now make sure we wait in the correct direction */ dir = libssh2_session_block_directions(session); if(dir & LIBSSH2_SESSION_BLOCK_INBOUND) readfd = &fd; if(dir & LIBSSH2_SESSION_BLOCK_OUTBOUND) writefd = &fd; rc = select((int)(socket_fd + 1), readfd, writefd, NULL, &timeout); return rc; }
为何waitsocket()未在所有非阻塞函数调用场景中被使用?例如SSH握手成功难道不需要等待Socket可读或可写吗?
解答
这其实是示例代码的简化写法,并非libssh2非阻塞模式的标准正确实现,核心原因如下:
1. 示例的侧重点不同
该示例主要聚焦于演示非阻塞模式下SFTP写入的核心逻辑,对于握手、认证等前期流程,采用了空循环自旋的方式快速带过,目的是减少代码复杂度,让读者把注意力放在SFTP写入的非阻塞处理上。但这种写法在生产环境中完全不可取——空循环会持续占用100%CPU,完全浪费了IO多路复用的优势。
2. 非阻塞模式的本质要求
实际上,在非阻塞模式下,所有返回LIBSSH2_ERROR_EAGAIN的libssh2函数,都应该调用waitsocket()(或类似的IO等待逻辑)来等待Socket进入可读/可写状态后再重试。包括握手、认证、SFTP初始化这些步骤:这些操作需要和SSH服务器进行多轮数据交互,必然会遇到Socket缓冲区已满或需要等待服务器响应的情况,此时返回LIBSSH2_ERROR_EAGAIN是合理的,必须等待IO就绪后再重试,否则会陷入无效的空循环。
3. 示例代码的疏漏
libssh2官方示例有时会为了简洁性牺牲严谨性,这种省略waitsocket()的写法是不规范的。在实际生产代码中,必须统一处理所有非阻塞函数的LIBSSH2_ERROR_EAGAIN返回值,正确写法如下:
int rc; do { rc = libssh2_session_handshake(session, sock); if(rc == LIBSSH2_ERROR_EAGAIN) { waitsocket(sock, session); } } while(rc == LIBSSH2_ERROR_EAGAIN);
这样才能避免CPU空转,同时保证非阻塞模式的正确性和性能。
内容的提问来源于stack exchange,提问作者asdf

