clamav必须部署服务端(clamd守护进程或windows服务),c#通过socket通信调用,不可直接nuget引用扫描;推荐tcp/unix域套接字分块上传、临时落盘再扫描,避免流不可重入问题。

ClamAV 服务端部署是前提,别直接在 C# 进程里调用 clamdscan
ClamAV 不是纯 .NET 库,不能通过 NuGet 引入后直接 ScanFile()。它依赖本地运行的 clamd 守护进程(Linux/macOS)或 Windows 服务(clamd.exe),C# 只能走 socket 或命令行与其通信。跳过服务部署直接写 C# 调用,99% 会卡在连接拒绝或权限错误。
实操建议:
- Linux:用包管理器安装
clamav-daemon,确认clamd正在监听/var/run/clamav/clamd.ctl或127.0.0.1:3310 - Windows:下载官方
clamav-x.x.x-win-x64.zip,运行clamd.exe --install后启动服务,检查端口3310是否开放 - 别用
clamscan命令行模式做生产扫描——每次调用都 fork 新进程,上传并发高时 CPU 和文件句柄直接打满
C# 调用 clamd 的推荐方式:TCP socket + Unix domain socket 兼容封装
clamd 协议极简(文本协议),自己写 socket 比引入第三方库更可控、更轻量。关键不是“怎么连”,而是“怎么防粘包+怎么判响应结束”。官方协议要求每条命令以
结尾,响应以 STREAM 开头表示流式扫描,以 OK 或 FOUND 结尾。
实操建议:
- 用
TcpClient连接127.0.0.1:3310(Windows/Linux TCP 模式),或UnixDomainSocket(.NET 6+ Linux/macOS 推荐,性能更好) - 发送命令前必须先发
VERSION确认服务可用,避免上传后才报连接失败 - 扫描大文件时,别一次性把整个
byte[]写进 socket——改用NetworkStream.WriteAsync()分块推送,否则可能触发 clamd 的StreamMaxLength限制(默认 25MB) - 响应解析不要用
ReadLine(),而要按字节读取直到遇到,再判断是否含FOUND或ERROR
上传文件落地前扫描,别扫 IFormFile.OpenReadStream() 返回的未缓冲流
ASP.NET Core 的 IFormFile 流默认是非缓冲、不可重入的。一旦你用 stream.CopyToAsync(clamdStream) 扫描一次,后续想保存到磁盘或计算哈希就会失败——流已 EOF 或被关闭。
实操建议:
- 必须先将上传文件临时写入磁盘(如
Path.GetTempFileName()),再把该路径传给 clamd 的SCAN命令(注意:clamd 必须配置AllowSupplementaryGroups yes且运行用户有读权限) - 或者用内存流双写:创建
MemoryStream,用file.CopyToAsync(memStream),然后分别用memStream推送扫描、用memStream.Position = 0再保存——但仅限小文件( - 千万别在
Controller里直接await file.OpenReadStream().CopyToAsync(...)后又想读一遍——.NET 不支持流回溯,除非显式CanSeek == true且底层是可寻址流
常见错误:SCAN 命令返回 ERROR: Can't open file 或超时
这不是 C# 代码问题,而是 clamd 权限或配置没对齐。尤其在容器或 Linux systemd 环境下,clamd 默认以 clamav 用户运行,根本读不了你 ASP.NET 进程临时写的文件。
实操建议:
- 检查 clamd 日志:
tail -f /var/log/clamav/clamd.log(Linux)或 Windows 事件查看器里Application日志,真实错误比 C# 报的SocketException有用得多 - 临时测试时,在 clamd.conf 中加
LocalSocketGroup www-data(Ubuntu)或LocalSocketMode 666,并确保上传临时目录属组包含该组 - 超时不是网络问题,而是 clamd 的
StreamMaxLength或MaxFileSize配置太小(默认 25MB/30MB),上传 100MB 视频必失败——需同步调大这两个值并重启服务 - Windows 上若用命名管道(
LocalSocket \.pipeclamd),.NET 的NamedPipeClientStream要设useAsync = true,否则阻塞主线程
真正麻烦的从来不是连上 clamd,而是让它的权限、路径、超时和你的上传生命周期严丝合缝。多看日志,少猜代码。










