CLI PHP WebSocket服务器
<?php
error_reporting(E_ALL ^ E_NOTICE);
ob_implicit_flush();
$sk = new Sock('127.0.0.1', 2345);
$sk->run();
class Sock{
// socket池
public $sockets;
// 用户socket池
public $users;
// 主socket
public $master;
// 已接收的数据
private $sda = array();
// 数据总长度
private $slen = array();
// 接收数据的长度
private $sjen = array();
// 加密key
private $ar = array();
private $n = array();
/**
* 构造方法
*/
public function __construct($address, $port){
$this->master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($this->master, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind($this->master, $address, $port);
socket_listen($this->master);
// 提示已监听
$this->e('Server Started : '.date('Y-m-d H:i:s'));
$this->e('Listening on : '.$address.' port '.$port);
// 把主socket放入socket池
$this->sockets = [$this->master];
}
/**
* 转码输出后换行
*/
public function e($str){
// GBK转码成UTF8输出
$str = $str."\n";
echo iconv('utf-8', 'gbk//IGNORE', $str);
}
/**
* 启动
*/
function run(){
while (TRUE) {
// 获取socket池
$changes = $this->sockets;
$write = NULL;
$except = NULL;
// 为了socket_accept无阻塞完成
// 当没有可以操作的socket时 返回传入的参数返回空
socket_select($changes, $write, $except, NULL);
foreach($changes as $sock){
if($sock == $this->master){
// 等待连接
$client = socket_accept($this->master);
// 分配唯一ID保存子socket
$key = uniqid();
// 子socket放入socket池
$this->sockets[] = $client;
// 放入用户socket池
$this->users[$key] = [
'socket' => $client,
'shou' => false, // 标记为未握手状态
];
} else {
$len = 0;
$buffer = '';
do {
// 读取当前socket中的内容并放入$buf中 读取长度为1000
$l = socket_recv($sock, $buf, 1000, 0);
$len += $l;
$buffer .= $buf;
// 为何 $l = 1000 继续循环?
} while ($l == 1000);
// 判断是否在用户socket池
$k = $this->search($sock);
// $len < 7 用户退出
if($len < 7){
$this->send2($k);
continue;
}
// 得到唯一ID的socket的握手状态 如果为false进行握手
if(!$this->users[$k]['shou']){
$this->woshou($k, $buffer);
}else{
// 如果已经握手
$buffer = $this->uncode($buffer, $k);
if(FALSE == $buffer){
continue;
}
$this->send($k, $buffer);
}
}
}
}
}
/**
* 判断传入socket是否在用户socket池
*/
function search($sock){
foreach ($this->users as $k => $v){
if($sock == $v['socket'])
return $k;
}
return false;
}
/**
* 握手
*/
function woshou($k, $buffer){
// 获得 Sec-WebSocket-Key: 后的字符串
$buf = substr($buffer, strpos($buffer, 'Sec-WebSocket-Key:') + 18);
// 获得 Key
$key = trim(substr($buf, 0, strpos($buf,"\r\n")));
// 拼接魔幻字符串 加密 Key
$new_key = base64_encode(sha1($key."258EAFA5-E914-47DA-95CA-C5AB0DC85B11", TRUE));
// 拼接协议
$new_message = "HTTP/1.1 101 Switching Protocols\r\n";
$new_message .= "Upgrade: websocket\r\n";
$new_message .= "Sec-WebSocket-Version: 13\r\n";
$new_message .= "Connection: Upgrade\r\n";
$new_message .= "Sec-WebSocket-Accept: ".$new_key."\r\n\r\n";
// 向当前socket写入
socket_write($this->users[$k]['socket'], $new_message, strlen($new_message));
// 修改用户socket池中的当前socket为true
$this->users[$k]['shou'] = TRUE;
return TRUE;
}
/**
* 关闭指定ID的socket
*/
function close($k){
socket_close($this->users[$k]['socket']);
unset($this->users[$k]);
$this->sockets = [$this->master];
foreach($this->users as $v){
$this->sockets[] = $v['socket'];
}
$this->e("key:$k close");
}
function uncode($str, $key){
$mask = [];
$data = '';
// 字符串按解包数据格式进行二进制解包
//echo $str;
$msg = unpack('H*', $str);
$head = substr($msg[1], 0, 2);
if ($head == '81' && !isset($this->slen[$key])) {
$len = substr($msg[1], 2, 2);
$len = hexdec($len);
if(substr($msg[1], 2, 2) == 'fe'){
$len = substr($msg[1], 4, 4);
$len = hexdec($len);
$msg[1] = substr($msg[1], 4);
}else if(substr($msg[1], 2, 2) == 'ff'){
$len = substr($msg[1], 4, 16);
$len = hexdec($len);
$msg[1] = substr($msg[1], 16);
}
$mask[] = hexdec(substr($msg[1], 4, 2));
$mask[] = hexdec(substr($msg[1], 6, 2));
$mask[] = hexdec(substr($msg[1], 8, 2));
$mask[] = hexdec(substr($msg[1], 10, 2));
$s = 12;
$n = 0;
}else if($this->slen[$key] > 0){
$len = $this->slen[$key];
$mask = $this->ar[$key];
$n = $this->n[$key];
$s = 0;
}
$e = strlen($msg[1]) - 2;
for ($i = $s; $i <= $e; $i += 2) {
$data .= chr($mask[$n%4]^hexdec(substr($msg[1], $i, 2)));
$n++;
}
$dlen=strlen($data);
if($len > 255 && $len > $dlen + intval($this->sjen[$key])){
$this->ar[$key] = $mask;
$this->slen[$key] = $len;
$this->sjen[$key] = $dlen+intval($this->sjen[$key]);
$this->sda[$key] = $this->sda[$key].$data;
$this->n[$key] = $n;
return false;
}else{
unset($this->ar[$key], $this->slen[$key], $this->sjen[$key], $this->n[$key]);
$data = $this->sda[$key].$data;
unset($this->sda[$key]);
return $data;
}
}
function code($msg){
$frame = array();
$frame[0] = '81';
$len = strlen($msg);
if($len < 126){
$frame[1] = $len < 16 ? '0'.dechex($len) : dechex($len);
}else if($len < 65025){
$s = dechex($len);
$frame[1] = '7e'.str_repeat('0', 4 - strlen($s)).$s;
}else{
$s = dechex($len);
$frame[1]= '7f'.str_repeat('0', 16 - strlen($s)).$s;
}
$frame[2] = $this->ord_hex($msg);
$data = implode('', $frame);
return pack("H*", $data);
}
function ord_hex($data){
$msg = '';
$l = strlen($data);
for ($i= 0; $i < $l; $i++) {
$msg .= dechex(ord($data{$i}));
}
return $msg;
}
//用户加入
function send($k, $msg){
parse_str($msg, $g);
$ar=array();
if($g['type'] == 'add'){
$this->users[$k]['name'] = $g['ming'];
$ar['type'] = 'add';
$ar['name'] = $g['ming'];
$key = 'all';
}else{
$ar['nrong'] = $g['nr'];
$key = $g['key'];
}
$this->send1($k, $ar, $key);
}
function getusers(){
$ar=array();
foreach($this->users as $k => $v){
$ar[] = ['code'=>$k,'name' => $v['name']];
}
return $ar;
}
//$k 发信息人的code $key接受人的 code
function send1($k, $ar, $key = 'all'){
$ar['code1'] = $key;
$ar['code'] = $k;
$ar['time'] = date('m-d H:i:s');
$str = $this->code(json_encode($ar));
if($key == 'all'){
$users = $this->users;
if($ar['type'] == 'add'){
$ar['type'] = 'madd';
$ar['users'] = $this->getusers();
$str1 = $this->code(json_encode($ar));
socket_write($users[$k]['socket'], $str1, strlen($str1));
unset($users[$k]);
}
foreach($users as $v){
socket_write($v['socket'], $str, strlen($str));
}
}else{
socket_write($this->users[$k]['socket'], $str, strlen($str));
socket_write($this->users[$key]['socket'], $str, strlen($str));
}
}
//用户退出
function send2($k){
$this->close($k);
$ar['type'] = 'rmove';
$ar['nrong'] = $k;
$this->send1(false, $ar, 'all');
}
}
网上搜来的,出处未知,侵删。