生产环境发送二进制数据应优先使用curl:直接传原始二进制字符串、显式设置Content-Type、禁用Expect: 100-continue、手动添加必要header;file_get_contents+stream_context_create易因编码、NULL截断、chunking等导致数据损坏。

用 curl 发送二进制数据最可靠
PHP 原生 file_get_contents 配 stream_context_create 虽能发 POST,但对二进制流支持脆弱,容易因编码、换行、自动 chunking 导致数据损坏。生产环境应优先用 curl,它直接控制底层 socket 行为,可禁用自动 header 修饰、关闭 body 编码转换。
关键点:
-
curl_setopt($ch, CURLOPT_POSTFIELDS, $binary_data):直接传入原始二进制字符串(如file_get_contents('image.jpg')),不加任何编码或边界处理 - 必须显式设置
Content-Type,例如application/octet-stream或image/jpeg,不能依赖 curl 自动推断 - 禁用
CURLOPT_HTTPHEADER中的Expect: 100-continue(默认开启),否则某些服务端会卡在等待确认,导致超时 - 若目标接口要求特定 header(如
X-Upload-Mode: raw),必须手动添加,curl 不会自动补全
file_get_contents + stream_context_create 的坑在哪
这个组合看似轻量,实际在二进制场景下极易出错:
- 默认会把
http_build_query式的 key-value 当作 body 处理;若强行传二进制字符串,PHP 可能悄悄做 UTF-8 检查或截断 NULL 字节 -
Content-Length由 PHP 自动计算,但若传入的是资源句柄(fopen返回)而非字符串,长度可能算错 - 无法关闭 HTTP/1.1 的分块传输(chunked encoding),某些老旧服务端不兼容
- 错误信息模糊:
file_get_contents失败时只报 “failed to open stream”,看不出是 DNS、SSL 还是 body 被篡改
示例问题代码:
$ctx = stream_context_create(['http' => [
'method' => 'POST',
'header' => "Content-Type: image/png\r\n",
'content' => file_get_contents('logo.png') // ✅ 看似对,但 PHP 可能重编码
]]);
——这种写法在 PHP 7.4+ 中已知会导致 PNG 文件头部被污染。
立即学习“PHP免费学习笔记(深入)”;
如何验证发送的二进制是否完整
别只看 HTTP 状态码 200,重点核对服务端收到的原始字节:
- 本地先算 MD5:
md5_file('logo.png'),再让接口返回它接收到的数据的 MD5(需后端配合) - 用
tcpdump或Wireshark抓包,过滤出该请求的 TCP payload,导出为文件并用xxd对比十六进制 - 如果服务端是你可控的,临时加一段调试逻辑:
file_put_contents('/tmp/received.bin', file_get_contents('php://input')),再比对 - 注意:不要用
$_POST接收二进制,它只处理表单编码,原始流在php://input
上传大文件时的内存与超时控制
二进制 POST 不等于“上传”,但行为类似,必须主动设限:
-
curl_setopt($ch, CURLOPT_TIMEOUT, 60):避免网络抖动导致无限挂起 -
curl_setopt($ch, CURLOPT_INFILESIZE, filesize($path)):显式告知大小,防止 curl 内部缓存策略误判 - 大文件别一次性
file_get_contents加载进内存,改用fopen+curl_setopt($ch, CURLOPT_UPLOAD, true)+curl_setopt($ch, CURLOPT_INFILE, $fp) - PHP 的
memory_limit和post_max_size对curl无效,但会影响你读取源文件的过程,要单独调大
真正难的不是发出去,而是确保每个字节都按原样抵达。尤其当服务端是 Go 或 Rust 写的裸 HTTP handler 时,任何多余的换行、BOM、自动 base64 包装都会让整个请求失效。











