位运算解析状态码需将各状态定义为2的幂次(1,2,4,8…),确保按位与(&)准确判断独立状态;若用连续数字(如1,2,3,4)会导致逻辑错误。

Workerman 里用位运算解析状态码,核心是把多个布尔状态压缩进一个整数,避免频繁字符串匹配或数组遍历 —— 但必须确保所有状态定义为 2 的幂次(1, 2, 4, 8…),否则按位与(&)会失效。
状态码必须用 2 的幂次定义
常见错误是直接用连续数字(如 1, 2, 3, 4)表示状态,这会导致 & 判断失灵。比如 5 & 3 结果是 1,但你本意可能是“是否同时含状态1和状态2”。
- 正确做法:每个独立状态对应唯一 bit 位,例如
const STATUS_ONLINE = 1 (即 1)、<code>STATUS_AWAY = 1 (即 2)、<code>STATUS_BUSY = 1 (即 4)、<code>STATUS_INVISIBLE = 1 (即 8) - 组合状态直接用按位或(
|):如STATUS_ONLINE | STATUS_BUSY得到 5(二进制0101) - 判断是否含某状态:用
$flags & self::STATUS_BUSY,结果非零即为真
在 onMessage 中解析客户端传来的状态整数
假设客户端发送的是一个 32 位整数(如 PHP 的 pack('N', $status)),服务端需 unpack 后用位运算提取语义:
use Workerman\Connection\TcpConnection;
$worker->onMessage = function (TcpConnection $connection, $data) {
// 假设前 4 字节是状态码(网络字节序)
if (strlen($data) send("BUSY");
return;
}
if ($status & self::STATUS_INVISIBLE) {
// 不更新在线列表,但允许接收私聊
$this->handlePrivateMsg($connection, substr($data, 4));
}
};
注意:不要在判断后直接 return,除非你确定后续状态无需检查 —— 位运算是并行的,多个状态可共存。
避免混淆:& 和 && 的行为差异
& 是按位与,操作整数;&& 是逻辑与,操作布尔值。在状态判断中混用会导致意外跳过条件:
- 错误写法:
if ($status & self::STATUS_ONLINE && $status & self::STATUS_AWAY)—— 逻辑上没问题,但多一次整数转布尔开销,且易被误读 - 推荐写法:
if (($status & self::STATUS_ONLINE) && ($status & self::STATUS_AWAY)),括号明确优先级 - 更高效写法:
if (($status & (self::STATUS_ONLINE | self::STATUS_AWAY)) === (self::STATUS_ONLINE | self::STATUS_AWAY)),一次性校验两个状态是否**同时存在**
实际踩坑点:unpack 端序和 signed/unsigned
PHP 的 unpack 默认返回有符号整数,而状态码通常按无符号处理。若客户端发来 0xFF000000,unpack('N') 可能返回负数,导致位运算结果异常。
- 解决方案一:强制转为无符号,用
unpack('L', ...)(小端)或unpack('N', ...)后加& 0xFFFFFFFF - 解决方案二:统一约定客户端用
uint32_t发送,服务端用unpack('V', ...)(小端)或unpack('N', ...)并做掩码:$status = unpack('N', $raw)[1] & 0xFFFFFFFF; - 调试技巧:打印
sprintf('%032b', $status)查看实际 bit 分布,比十进制更直观
真正容易被忽略的是状态码的生命周期管理 —— 位标志一旦写死在代码里,后续新增状态就必须保证不冲突(比如不能跳着用 1, 2, 4, 16 而漏掉 8),否则旧客户端可能误判。建议用常量 + 注释明确标注每个 bit 位含义,并在协议文档中固化。











