WinSCP Session.GetFile获取SFTP流时Length抛出NotSupportedException异常
问题根因
Session.GetFile()返回的WinSCP自定义PipeStream是单向顺序读取的管道流,不支持Length、Seek这类随机访问属性,原有上传逻辑强依赖读取流的Length属性提前初始化Azure文件,因此抛出异常:
'((WinSCP.PipeStream)stream).Length' threw an exception of type 'System.NotSupportedException'.
解决方案
方案1:提前获取SFTP文件元数据(无本地IO、低内存占用,适配大文件)
该方案不需要写入本地临时磁盘,也不需要将全量文件加载到内存,数据从SFTP连接读出后直接写入Azure,是最适配现有业务逻辑的实现:
- 打开SFTP会话后,先调用
Session.GetFileInfo()拉取远端文件元数据,直接获取服务器返回的准确文件长度,不需要从流对象读取Length属性 - 调整上传方法,支持外部传入已知文件长度,保留原有逻辑对本地文件流的兼容性
- 保持二进制传输模式,避免编码转换导致长度不匹配
修改后的SFTP侧代码:
using (Session session = new Session()) { session.Open(sessionOptions); TransferOptions transferOptions = new TransferOptions(); transferOptions.TransferMode = TransferMode.Binary; // 提前拉取远端文件元数据获取准确长度 RemoteFileInfo remoteFileInfo = session.GetFileInfo(FilePath); long fileLength = remoteFileInfo.Length; using (Stream stream = session.GetFile(FilePath, transferOptions)) { // 传入已知长度,不再访问流的Length属性 UploadToAzure(stream, Filename, Foldername, fileLength); } }
调整后的Azure上传方法:
// 增加knownLength可选参数,兼容原有本地文件流调用场景 public static string UploadToAzure(Stream attachment, string Filename, string Foldername, long? knownLength = null) { System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12; var connectionString = ConfigurationManager.AppSettings["AzureFileShareConnectionString"]; string shareName = ConfigurationManager.AppSettings["AzureFileShareFolderName"]; string dirName = $"files\\{Foldername}"; string fileName = Filename; try { // 优先使用外部传入的已知长度,未传入时再读取流自带的Length属性 long uploadLength = knownLength ?? attachment.Length; ShareClient share = new ShareClient(connectionString, shareName); share.CreateIfNotExists(); ShareDirectoryClient directory = share.GetDirectoryClient(dirName); directory.CreateIfNotExists(); ShareFileClient file = directory.GetFileClient(fileName); file.Create(uploadLength); // PipeStream为顺序读流,可直接传入UploadRange,不需要重置流位置 file.UploadRange( new HttpRange(0, uploadLength), attachment); } catch (Exception e) { return $"Uploaded {Filename} failed : {e.ToString()}"; } return $"{Filename} Uploaded"; }
该方案注意事项:
- 全程无本地磁盘IO,内存仅占用SDK上传分片的缓冲区大小,支持GB级大文件传输
- 必须保持
TransferMode.Binary配置,避免文本模式下换行符转换导致实际传输字节数和元数据长度不一致 - 传输中断时
UploadRange会直接抛出异常,不会写入不完整的脏文件,和原有本地上传逻辑的一致性表现一致
方案2:使用Azure SDK的无长度流式上传(适配中小文件)
如果使用的Azure.Storage.Files.Shares SDK版本支持(.NET 4.6环境请使用12.1x及以下兼容版本),可以直接调用不需要提前指定文件长度的Upload重载,SDK会自动处理流读取和文件创建逻辑:
// 省略ShareClient、DirectoryClient初始化逻辑 ShareFileClient file = directory.GetFileClient(fileName); // 直接传入不可Seek的流,SDK自动分块上传 file.Upload(attachment);
注意:该重载对于不可Seek的流会在SDK内部做部分缓冲,大文件场景下内存占用高于方案1。
避坑提示
- 不要为了获取
Length将PipeStream全量复制到MemoryStream后上传,大文件场景下会直接触发OOM - 不要尝试给
PipeStream包装流位置回退逻辑,该流是单向前进管道,数据读取后即从缓冲区移除,不支持Seek操作
内容的提问来源于stack exchange,提问作者Ahmad Pujianto
相关产品推荐
相关产品推荐

