PHP websocket 应用
因为公司有这个需求,记录一下
创建两个数组来记录连接的客户端以及握手状态
private $master = null; //服务端
private $connectPool = []; //客户端连接池
private $handShakePool = []; //握手连接池
运行服务的方法
public function startServer($ip, $port) {
$this->connectPool[] = $this->master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);//创建服务端
socket_bind($this->master, $ip, $port);//绑定服务端ip,端口
socket_listen($this->master, 1000);//监听,最多同时监听1000个客户端
while(true) {
$sockets = $this->connectPool;
$write = $excpet = null;
socket_select($sockets, $write, $excpet, 60);//阻塞模式
foreach($sockets as $socket) {//监听消息
if($socket == $this->master) {//添加新客户端
$this->connectPool[] = $client = socket_accept($this->master);
$keyArr = array_keys($this->connectPool, $client);
$key = end($keyArr);
$this->handShakePool[$key] = false;
} else {
$length = socket_recv($socket, $buffer, 1024, 0);
if($length<1) {//当长度小于7,说明当前客户端连接已断开
$this->close($socket);//服务端关闭断开连接的客户端
} else {
$key = array_search($this->master, $this->connectPool);
if($this->handShakePool[$key] == false) {//进行握手
$this->handShake($socket, $buffer, $key);
} else {//发送消息,可根据具体业务进行调整
$msg = $this->deFrame($buffer);
$msg = $this->enFrame($msg);
$this->send($socket, $msg);
}
}
}
}
}
}
以下是某些必需的操作
关闭连接
// 客户端断开连接
public function close($socket) {
$key = array_search($socket, $this->connectPool);
unset($this->connectPool[$key]);
unset($this->handShakePool[$key]);
socket_close($socket);
echo("有客户端断开连接\n");
}
握手操作
//握手
public function handShake($socket ,$buffer, $key) {
if(preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $buffer, $match)) {
$responseKey = base64_encode(sha1($match[1].'258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
$upgrade = "HTTP/1.1 101 Switching Protocol\r\n".
"Upgrade: websocket\r\n".
"Connection: Upgrade\r\n".
"Sec-WebSocket-Accept: ".$responseKey."\r\n\r\n";
socket_write($socket, $upgrade, strlen($upgrade));
$this->handShakePool[$key] = true;
}
echo("新建握手,当前客户端数量=>".(sizeof($this->handShakePool)-1)."\n");
}
数据的加密与解密,以及发送信息
//数据封帧
public function enFrame($msg) {
$len = strlen($msg);
if($len<=125) {
return "\x81".chr($len).$msg;
} else if($len <= 65535) {
return "\x81".chr(126).pack("n",$len).$msg;
} else {
return "\x81".chr(127).pack("xxxxN",$len).$msg;
}
}
//数据解帧
public function deFrame($buffer) {
$len = $masks = $data = $decode = null;
$len = ord($buffer[1]) & 127;
if($len === 126) {
$masks = substr($buffer, 4, 4);
$data = substr($buffer, 8);
} else if($len === 127) {
$masks = substr($buffer, 10, 4);
$data = substr($buffer, 14);
} else {
$masks = substr($buffer, 2, 4);
$data = substr($buffer, 6);
}
for($index = 0; $index<strlen($data); $index++) {
$decode .= $data[$index]^$masks[$index*4];
}
return $decode;
}
//发送信息
public function send($socket, $msg) {
socket_write($socket, $msg);
echo("给{$socket}发送{$msg}\n");
}
写完以上代码,运行 startServer 方法即可
本作品采用《CC 协议》,转载必须注明作者和本文链接
单进程非阻塞的
select
模型,偏内存消耗,默认最大连接数1024
。在
socket_listen
后可以指定为非阻塞connectPool
的key
值其实可以关闭时
这里 false 是出错,但也有可能长度是0吧,我也不确定
是否可以这样
swoole 他不香么?