因為公司有這個需求,記錄一下
建立兩個陣列來記錄連線的客戶端以及握手狀態
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 協議》,轉載必須註明作者和本文連結