如何在未知文件大小的情况下判断libssh2的SCP传输完成?
问题
用libssh2做SCP文件下载,碰到一台特殊SSH服务器,没法提前获取文件元数据,所以不知道要下载的文件实际大小。之前我靠接收数据末尾的空字节(0x00)判断传输结束,这对文本文件没问题,但二进制文件里本身可能有0x00,很容易误判;试了libssh2_channel_eof(),它一直返回false,根本没法用。
原测试代码是这样的:
LIBSSH2_CHANNEL* ch = libssh2_scp_recv2(session, remote_file_path, NULL); ssize_t bytes_received = 0; bool eof_received = false; if (ch) { do { char buf[1024] = {0}; bytes_received = libssh2_channel_read(ch, buf, sizeof(buf)); eof_received = (buf[bytes_received - 1] == 0x00); printf("Received %d bytes.\n", bytes_received); } while (bytes_received > 0 && !eof_received); }
现在想知道,不知道文件大小的情况下,怎么正确判断SCP文件已经完全下载?
解决方法
1. 靠libssh2_channel_read的返回值判断结束才是正确姿势
SCP传输完成时,libssh2_channel_read()会返回0,这才是没有更多数据可读的标志,完全不用管末尾的空字节。要分清这个函数返回值的几种情况:
- 返回值大于0:成功读到了对应字节数的数据,直接写到本地文件就行。
- 返回值等于0:通道已经没数据了,传输完成,可以退出循环。
- 返回值小于0:出错误了,比如
LIBSSH2_ERROR_EAGAIN是需要重试,其他错误就直接处理异常。
2. 修正后的代码示例
LIBSSH2_CHANNEL* ch = libssh2_scp_recv2(session, remote_file_path, NULL); if (ch) { ssize_t bytes_received; char buf[1024]; do { // 不用每次把buf初始化全0,不然会覆盖有效数据 bytes_received = libssh2_channel_read(ch, buf, sizeof(buf)); if (bytes_received > 0) { // 把buf里的bytes_received字节写入本地文件 // fwrite(buf, 1, bytes_received, local_file); printf("Received %zd bytes.\n", bytes_received); } else if (bytes_received == 0) { printf("传输完成。\n"); break; } else { // 处理错误,EAGAIN的话就重试 if (bytes_received == LIBSSH2_ERROR_EAGAIN) { // 等套接字可读后再重试,这里可以加个延时或者用select监听 continue; } else { printf("读取出错:%zd\n", bytes_received); break; } } } while (1); // 记得用完释放通道 libssh2_channel_free(ch); }
3. 为什么原方法不靠谱
libssh2在SCP末尾加的空字节是协议本身的一部分,但它不是用来判断传输结束的——这个空字节是SCP用来标记文件内容结束的分隔符,但如果文件本身就有0x00,肯定会搞混。正确的做法是看通道读取的返回值,当返回0时,说明服务器已经发完所有数据并关闭了数据通道。
4. 关于libssh2_channel_eof()的说明
它一直返回false是因为SCP通道的EOF状态要等libssh2_channel_read()返回0之后才会被设置,所以别依赖它,以libssh2_channel_read()的返回值为准就行。
内容的提问来源于stack exchange,提问作者signpainter
相关产品推荐
相关产品推荐

