socket聊天室:
<?php
/**
* Created by PhpStorm.
* User: Administrator
* Date: 2019/5/7
* Time: 10:26
*/
class Wss
{
public $socket = null;
public $sockets = [];//连接池~
public $write = null;
public $except = null;
public $user = [];
public function __construct($ip, $port)
{
/**创建一个套接字
*AF_INET:IPv4 网络协议,
*SOCK_STREAM:提供一个顺序化的、可靠的、全双工的、基于连接的字节流。支持数据传送流量控制机制。TCP 协议即基于这种流式套接字
* 如果所需的协议是 TCP 或 UDP,可以直接使用常量 SOL_TCP 和 SOL_UDP 。
*/
$this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
/**
* 设置套接字的套接字选项
* 要在套接字级别检索选项level,SOL_SOCKET将使用参数 。可以通过指定该级别的协议编号来使用其他级别,例如TCP
* SO_REUSEADDR
* 允许一个程序在多个实例对同一个端口进行绑定。
* 如果你定义个SO_REUSEADDR,只定义一个套接字在一个端口上进行监听,如果服务器出现意外而导致没有将这个端口释放,那么服务器重新启动后,你还可以用这个端口,因为你已经规定可以重用了,如果你没定义的话,你就会得到提示,ADDR已在使用中。用在多播的时候,也经常使用SO_REUSEADDR,也是为了防止机器出现意外,导致端口没有释放,而使重启后的绑定失败~
*/
socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, true);
/**
* 给套接字绑定名字
*/
socket_bind($this->socket, $ip, $port);
/**
* 侦听套接字上的连接
*/
socket_listen($this->socket);
$this->sockets[] = $this->socket;
while (true) {
$tmp_sockets = $this->sockets;
/**
* 在指定的超时套接字数组上运行select()系统调用
*write将监视数组中 列出的套接字以查看写入是否不会阻塞。
* except将监视阵列中 列出的套接字是否有异常。
*/
socket_select($tmp_sockets, $this->write, $this->except, null);
foreach ($tmp_sockets as $sock) {
if ($sock == $this->socket) {
/**
* 接受套接字上的连接
*/
$conSock = socket_accept($sock);
var_dump("conSock:" . $conSock);
$this->sockets = $conSock;
$this->user[] = ['socket' => $conSock, 'handshake' => false];
} else {
/**
* 从套接字读取最大长度字节
*/
$request = socket_read($sock, 1024);
$k = $this->getUserIndex($sock);
if (strlen($request) == 8) {
$this->close($k);
continue;
}
if (!$this->user[$k]['handshake']) {
$response = $this->handleShake($request);
var_dump("response:" . $response);
socket_write($sock, $response, strlen($response));
$this->user[$k]['handshake'] = true;
} else {
$msg = $this->decode($request);
$this->send($msg,$k);
}
}
}
}
}
private function send($msg,$k){
$arr=explode('===',$msg);
if ($arr[0]==='login'){
$this->user[$k]['name']=$arr[1];
$res['msg']=$arr[1].':login success';
$res['type']='login';
$names['name']=$this->getUserName();
$names=$this->encode(json_encode($names));
foreach ($this->user as $v){
socket_write($v['socket'],$names,strlen($names));
}
}
if ($arr[0]=='con'){
$res['content']=$arr[1];
$res['name']=$this->user[$k]['name'];
$res['time']=date('Y-m-d H:i:s',time());
$res['type']='con';
}
$res=$this->encode(json_encode($res));
foreach ($this->user as $v){
socket_write($v['socket'],$res,strlen($res));
}
}
private function encode($msg)
{
$frame = [];
$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;
}
$data = '';
$l = strlen($msg);
for ($i = 0; $i < $l; $i++) {
$data .= dechex(ord($msg{$i}));
}
$frame[2] = $data;
$data = implode('', $frame);
return pack("H*", $data);
}
private function getUserName(){
foreach ($this->user as $v){
$name[]=$v['name'];
}
return $name;
}
private function decode($buffer)
{
$decoded = '';
$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++) {
$decoded .= $data[$index] ^ $masks[$index % 4];
}
return $decoded;
}
private function close($k)
{
socket_close($this->user[$k]['socket']);
unset($this->user[$k]);
$this->sockets = null;
$this->sockets[] = $this->socket;
foreach ($this->user as $k => $v) {
$this->sockets[] = $v['socket'];
}
}
private function getUserIndex($sock)
{
foreach ($this->user as $k => $v) {
if ($v['socket'] == $sock) {
return $v;
}
}
}
private function handleShake($request)
{
preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $request, $match);
$key = $match[1];
$new_key = base64_encode(sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
$response = "HTTP/1.1 101 Switching Protocols\r\n";
$response .= "Upgrade: websocket\r\n";
$response .= "Connection: Upgrade\r\n";
$response .= "Sec-WebSocket-Accept: $new_key\r\n";
$response .= "Sec-WebSocket-Protocol: chat\r\n\r\n";
return $response;
}
private function decode($buffer)
{
$decoded = '';
$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++) {
$decoded .= $data[$index] ^ $masks[$index % 4];
}
return $decoded;
}
}
new Wss(0, 9501);
客户端:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"/>
<style type="text/css">
body,p{margin:0px; padding:0px; font-size:14px; color:#333; font-family:Arial, Helvetica, sans-serif;}
#box,.but-box{width:50%; margin:5px auto;border-radius:5px}
#box{border:1px #ccc solid;height:400px;width:700px;margin-top:50px;overflow-y:auto; overflow-x:hidden; position:relative;}
#user-box{margin-right:111px; height:100%;overflow-y:auto;overflow-x: hidden;}
#msg-box{width:110px; overflow-y:auto; overflow-x:hidden; float:right; border-left:1px #ccc solid; height:100%; background-color:#F1F1F1;}
button{float:right; width:80px; height:35px; font-size:18px;}
input{width:100%; height:30px; padding:2px; line-height:20px; outline:none; border:solid 1px #CCC;}
.but-box p{margin-right:160px;}
</style>
<script src="https://cdn.bootcss.com/jquery/2.2.1/jquery.min.js"></script>
</head>
<body>
<h3 style="margin-left:600px">这是个web聊天室 </h3>
<div id="box">
<div id="msg-box"></div>
<div id="user-box"></div>
</div>
<div class="but-box">
<button id="send">发送</button>
<p><textarea cols="60" style="resize:none" id="content"> </textarea></p>
</div>
</body>
</html>
<script>
var name = prompt('请输入用户名:');
socket = new WebSocket('ws://119.23.72.178:9501','char');
console.log(socket)
socket.onopen = function(){
console.log('connected success');
socket.send('login==='+name);
}
socket.onmessage = function(e){
data = JSON.parse(e.data);
console.log(data);
if(data.type=='login'){
$('#user-box').append('<li style="color:gray">'+data.msg+'</li>');
}
if(data.type=='user'){
$('#msg-box').html('');
for(i=0;i<data.name.length;i++){
$('#msg-box').append('<li style="color:gray">'+data.name[i]+'</li>');
}
}
if(data.type=='con'){
$('#user-box').append('<li><span style="color:blue">'+data.time+'</span><span style="color:red">'+data.name+'</span><span style="color:blue">'+data.content+'</span></li>');
}
}
document.onkeydown = function(e){
if(e.keyCode==13){
send();
}
}
$('#send').click(function(){
send();
})
function send(){
content = $('#content').val();
$('#content').val('');
if(content==''){
return false;
}
socket.send('con==='+content);
}
</script>