fsockopen仅支持阻塞式TCP客户端,不处理粘包,需手动用fread+unread_bytes或缓冲累积;UDP必须用socket扩展;TCP服务端需自行管理连接与超时;调试须结合抓包与errno解析。

PHP 中用 fsockopen 发起 TCP 连接,但别指望它处理粘包
PHP 没有原生的“Socket 类”封装,fsockopen 是最轻量、最常用的 TCP 客户端入口。它底层调用系统 socket,但只暴露阻塞式 I/O 接口,不支持非阻塞、select 或事件循环。
常见错误是拿它当长连接“管道”反复 fwrite/fgets,结果读到一半就卡住——因为 fgets 默认按换行截断,而协议可能没换行,或数据被拆成多段(粘包/半包)。这时候必须改用 fread 配合 stream_get_meta_data 查 unread_bytes,或手动循环 fread($fp, 1) 累积缓冲区。
-
fsockopen('127.0.0.1', 8080, $errno, $errstr, 5)第五个参数是超时(秒),别漏设,否则默认无限等待 - 连接成功后立即
stream_set_timeout($fp, 5),否则后续fread可能永久挂起 - 服务端若不主动断连,PHP 脚本结束时
$fp才自动 close;长时间运行需显式fclose($fp)
UDP 通信只能靠 socket_create + socket_sendto
fsockopen 不支持 UDP,必须用底层 socket 扩展。PHP 的 UDP 是无连接、不可靠的,没有握手、重传、顺序保证——发出去就完事,收不收得到全看运气。
典型误用是以为 socket_recvfrom 会等数据来,其实它默认阻塞,但一旦网络丢包或对方没发,就会一直卡住。必须提前设 socket_set_option($sock, SOL_SOCKET, SO_RCVTIMEO, ['sec' => 3, 'usec' => 0])。
立即学习“PHP免费学习笔记(深入)”;
- 创建 socket:
$sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP),第三个参数固定为SOL_UDP,写0或IPPROTO_UDP在某些系统会失败 - 发送前不用
connect,直接socket_sendto($sock, $data, strlen($data), 0, '127.0.0.1', 9999) - 接收时
socket_recvfrom的第 6、7 参数必须传引用:socket_recvfrom($sock, $buf, 65535, 0, $ip, $port),否则收不到地址信息
PHP 做 TCP 服务端?socket_accept 后得自己管连接生命周期
用 socket_create + socket_bind + socket_listen 能搭出简易 TCP 服务端,但 PHP 不是 Node.js,没有内置连接池或自动回收机制。每个 socket_accept 返回的新 socket 都要单独维护。
最常踩的坑是:accept 后直接 while (true) { socket_read(...) },结果一个客户端连上来就把整个进程占死,其他连接全排队。PHP 是单线程模型,不 fork 或不配合 pcntl 就没法并发。
- 简单轮询方案:用
socket_select监听多个 socket(包括主 listen socket 和已 accept 的 client socket),避免阻塞在某一个上 - 别在
socket_read前假设数据一定到达,永远检查返回值是否 false(连接断开)或 0(对端 shutdown) - 客户端断连时,
socket_read返回''(空字符串),不是 false,这点和文件读取不同
调试时先关 SELinux / 防火墙,再查 socket_last_error
PHP socket 报错信息极其简陋,比如 socket_connect(): unable to connect [111] —— 这个 111 是 Linux errno,得查 errno.h 或运行 php -r "echo socket_strerror(111);" 才知道是 “Connection refused”。更麻烦的是,SELinux 或 iptables 可能静默拦截,表现和代码错误一模一样。
- 快速验证:用
telnet 127.0.0.1 8080或nc -u 127.0.0.1 9999手动测试通不通,绕过 PHP 层 - 每次 socket 调用后立刻跟
if ($e = socket_last_error()) { echo socket_strerror($e); },别等整段逻辑跑完才查 - UDP 场景下,
socket_sendto成功只表示数据进了内核发送队列,不代表对方收到;要确认送达得靠应用层 ACK 协议
真正难的不是写通第一行 socket 代码,而是处理超时、断线重连、缓冲区管理、字节序转换这些细节。很多问题只在高并发或弱网环境下暴露,本地测试一切正常,上线就丢包或假死——别急着改逻辑,先抓包看 TCP 三次握手和 FIN 包是不是完整。



















