跨服务器访问共享文档UNC路径 WebClient空引用异常排查
同一局域网内部署两台独立服务器:
- 服务器A:存储所有文档的文件目录部署在该机器
- 服务器B:部署业务代码,需访问服务器A上存储的文档并执行压缩操作
报错触发代码行:using (WebClient webClient = new WebClient())
抛出异常信息:System.NullReferenceException: Object reference not set to an instance of an object.
当前跨服务器访问使用的UNC路径:\\ServerName\E$\ClientFiles\SHAH\SHAH_103696_20220531160805_2600.jpg
待排查问题:上述路径配置是否正确、是否需要调整路径格式,当前实现代码如下:
public void ImageCompressionStream(ShahImages fileImage) { try { using (WebClient webClient = new WebClient()) { byte[] data = webClient.DownloadData(fileImage.filePath); using (MemoryStream mem = new MemoryStream(data)) { using (var yourImage = Image.FromStream(mem)) { yourImage.Save(fileImage.filePath, ImageFormat.Jpeg); } } } } catch (Exception ex) { } }
1. UNC路径配置错误
当前使用的E$是Windows系统默认的管理共享,这类共享默认仅允许本地管理员组成员访问,且微软明确不建议业务场景直接使用管理共享做跨机器文件访问,存在权限管控不灵活、默认安全策略会阻断远程访问的问题。
正确配置方式:
- 在服务器A上单独创建业务专用共享:例如将
E:\ClientFiles目录设置为共享,自定义共享名(比如ClientFiles),同时配置共享权限和NTFS安全权限,授予服务器B上运行业务进程的账号(IIS应用池账号、Windows服务运行账号)对应的读、写权限 - 调整UNC路径为专用共享格式:
\\服务器A主机名或IP\自定义共享名\后续目录路径\文件名,对应场景路径应改为\\ServerName\ClientFiles\SHAH\SHAH_103696_20220531160805_2600.jpg - 配置完成后,先在服务器B上用业务进程的运行账号登录系统,直接在资源管理器输入UNC路径验证可正常打开、修改、保存文件,提前排除权限问题。
2. 代码逻辑缺陷
触发的空引用异常,根本不是WebClient初始化导致的——WebClient无参构造函数不会返回null,空引用来自以下几个高概率位置:
- 入参
fileImage本身为null,或者fileImage.filePath为null,传入DownloadData时触发异常 - 路径权限不足、路径不存在导致DownloadData执行失败,返回空字节数组,后续传入MemoryStream、Image.FromStream时触发空引用
- 现有代码的catch块完全吞掉了所有异常,没有记录任何错误堆栈、入参信息,根本无法定位真实报错点,首先要把catch块内的异常完整信息写入日志,禁止空吞异常。
除此之外现有代码还有严重的文件占用问题:读取完文件流后,WebClient持有的文件资源还没释放,就直接往同一路径执行Save写操作,大概率会触发文件占用报错。另外访问SMB协议的UNC共享文件,完全不需要用WebClient——WebClient是为HTTP/FTP等网络协议设计的,读取局域网磁盘文件直接用System.IO命名空间下的原生文件接口即可,性能更好、权限继承逻辑更清晰。
修复后的参考代码:
public void ImageCompressionStream(ShahImages fileImage) { // 入参前置校验,从源头避免空引用 if (fileImage == null || string.IsNullOrWhiteSpace(fileImage.filePath)) { throw new ArgumentNullException("入参fileImage或文件路径不能为空"); } if (!File.Exists(fileImage.filePath)) { throw new FileNotFoundException("指定路径的图片文件不存在", fileImage.filePath); } byte[] fileBuffer; // 先读取完整文件内容到内存,释放读文件锁 using (FileStream readStream = new FileStream(fileImage.filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) { using (MemoryStream tempMs = new MemoryStream()) { readStream.CopyTo(tempMs); fileBuffer = tempMs.ToArray(); } } // 内存中处理图片压缩 using (MemoryStream imgReadMs = new MemoryStream(fileBuffer)) { using (Image sourceImg = Image.FromStream(imgReadMs)) { using (MemoryStream compressMs = new MemoryStream()) { // 可在此处自定义JPEG压缩质量参数,示例使用默认格式保存 sourceImg.Save(compressMs, ImageFormat.Jpeg); compressMs.Position = 0; // 所有读资源释放完成后,再打开文件流写入覆盖源文件 using (FileStream writeStream = new FileStream(fileImage.filePath, FileMode.Create, FileAccess.Write)) { compressMs.CopyTo(writeStream); } } } } }
3. 额外配置注意事项
- 如果两台服务器不在同一个域环境,需要在两台机器上创建相同用户名、相同密码的本地账号,将业务进程的运行账号设置为该账号,保证跨机访问的身份认证通过
- 服务器端的Windows防火墙要放开文件和打印机共享(SMB协议对应的445端口),否则会出现路径能ping通但无法访问共享的问题
- 生产环境不要在代码中硬编码UNC路径,建议把文件存储根路径写到配置文件中,后续迁移存储时不需要重新编译代码。
内容的提问来源于stack exchange,提问作者user2147447

