博客列表 >socket 用websocket 和PHP的 socket 做的聊天室。

socket 用websocket 和PHP的 socket 做的聊天室。

有什么是忘不了的的博客
有什么是忘不了的的博客原创
2020年06月02日 22:59:28855浏览

做的比较简单,主要是一个思想和对 socket 的学习。

这个只能自己玩玩,没有做发送的数处理等一系列的小问题。。。

php代码:

<?php 

$address = '0.0.0.0'; //0.0.0.0代表所有内网的ip,你也可以是0,代表所有的内网和外网的ip都可以访问。
$port = 8888;
$socket = new So($address , $port);
$socket->run();
class So
{
	private $sockets;//报存所有的链接套接字
	private $master; //服务产生的套接字
	private $user; //报存链接的用户信息。

	function __construct($address,$port)
	{
		//创建socket并把保存socket套接字
		$this->connect($address,$port);
		
	}
	//服务端 socket套接字创建。
	private function connect($address,$port){
		$socket = socket_create(AF_INET, SOCK_STREAM ,SOL_TCP);
		socket_set_option($socket,SOL_SOCKET,SO_REUSEADDR,true);
		socket_bind($socket,$address,$port);
		socket_listen($socket);
		//吧系统的套接字保存起来
		$this->master = $socket;
		$this->sockets[] = $socket;
	}
	public function run()
	{
		$master = $this->master;
		$write = NUll;
   		$except = NUll;

		while (true) {
			$sockets = $this->sockets;
			// var_dump($sockets);
			//阻塞执行,一旦有新变动就继续执行,并获取变动的套接字,新的链接会让服务产生的套接字发生变动。
			//变动只是一个动词,表示状态发生变化。
			//socket_select 是实现多用户链接的关键所在,就是因为他能找到是谁在变动。
			socket_select($sockets,$write,$except ,NULL);
                        //这里的排序是为了干掉$sockets的默认下标
			sort($sockets);
			$sock = $sockets[0];
			//判断是新链接的套接字。
			if ($sock == $master) {
				//用户请求过来,服务器通过系统的套接字创建一个新的套接字分配给他。
				$client = socket_accept($sock);
				//吧这个套接字存入套接字数组。
				$this->sockets[] = $client ;
				//在用户数组,保存这个用户的连接套接字和连接状态
				$this->user[] = ['socket'=>$client,'state'=>false];
			}else{
				$request = socket_read($sock,1024);
				//获取当前链接的套接字用户的健值。
				$key = $this->setKey($sock);
				$len = strlen($request);
				//用户直接关闭浏览器的时候会接受一个长度为8的数据,
				if ($len == 8) {
					$this->close($key);
					continue;
				}
				
				//判断是否已经握手成功
				if (!$this->user[$key]['state']) {
					// 调用握手方法,$sock需要握手的套接字
					$this->handleShake($request,$sock);
					//握手成功修改这个用户的链接状态为true
					$this->user[$key]['state'] = true;
				}else{
					//获取收据,和发送数据
					$msg = $this->decode($request);
					// echo $msg;
					$this->send($msg,$key);
					// $msg = $this->encode('hello wored !');
					// socket_write($sock, $msg, strlen($msg));
				}
				
			}		
		}
	}
	//获取当前套接字的健值
	public function setKey($value){
		foreach ($this->user as $k => $v) {
			if ($value == $v['socket']) {
				return $k;
			}
		}
	}
	//与客户端websocket握手
	private function handleShake($request,$sock){
		var_dump($request);
		preg_match('/Sec-WebSocket-Key: (.*)\r\n/',$request,$match);
		$accept = base64_encode(sha1($match[1].'258EAFA5-E914-47DA-95CA-C5AB0DC85B11',true));
		$buffer  = "HTTP/1.1 101 Switching Protocols\r\n";
		$buffer .= "Connection: Upgrade \r\n";
		$buffer .= "Sec-WebSocket-Accept: $accept\r\n";
		$buffer .= "Sec-WebSocket-Version: 13  \r\n";
		$buffer .= "Upgrade: websocket \r\n\r\n";
		socket_write($sock , $buffer, strlen($buffer));
	}
	//接受数据
	private function send($msg,$key)
	{
		$list = explode('===',$msg);
		// var_dump($list);
		switch ($list[0]) {
			case 'login':
				$this->user[$key]['name']=$list[1];
				$msg = ['msg'=>$list[1].'登录聊天室','type'=>'login'];
				$this->write(json_encode($msg));
				$this->usersWeite();
				break;
			case 'text':
				$text = ['text'=>$list[1],'sender'=>$list[3],'type'=>'text'];
				$this->write(json_encode($text));
				break;
			default:
				# code...
				break;
		}
	}

	//发送当前连接的用户
	private function usersWeite()
	{
		$userList = array_column($this->user, 'name');
		$users = ['user'=>$userList,'type'=>'users'];
		$this->write(json_encode($users));
	}

	//发送数据
	private function write($msg,$key = 'all')
	{
		$msg = $this->encode($msg);
		if ($key == 'all') {
			foreach ($this->sockets as  $sock) {
				if ($sock != $this->master) {
					socket_write($sock , $msg, strlen($msg));	
				}
			}
		}else{
				// socket_write($sock , $msg, strlen($msg));	
		}
	}




	public function close($key)
	{
		//先获取指定的套接字
		$socket = $this->user[$key]['socket'];
		//关闭该套接字
		socket_close($socket);
		//删除该用户信息
		unset($this->user[$key]);
		//重新组装套接字数组
		$this->sockets = array_column($this->user,'socket');
		//系统套接字必须在首位。
		array_unshift($this->sockets,$this->master);
		//更新客户端 在线用户列表
		$this->usersWeite();
	}
	/*
		对websocket传输过来的数据解码。你无需理解,照搬就可以。
	 */
	private function decode($received){
	  $len = $masks = $data = $decoded = null;
	  $buffer = $received;
	  $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;
	}

	/*
		对传输给websocket的数据编码。你无需理解,照搬就可以。
	 */
	private function encode($msg){
	    if (!is_scalar($msg)) {
	        print_r("只允许发送标量数据");
	    }
	    
	    // 数据长度
	    $len = strlen($msg);
	    
	    // 这边仅实现传输文本帧!第一个字节,文本帧 1000 0001 => 129
	    // 如果需要例如二进制帧,用于传输大文件,请另行实现
	    $first_byte = chr(129);
	    
	    if ($len <= 125) {
	        // payload length = 7bit 支持的最大范围!
	        $second_byte = chr($len);
	    } else {
	        if ($len <= 65535) {
	            // payload length = 7 , extended payload length = 16bit,支持的最大范围 65535
	            // 最后16bit 被解释为无符号整数,排序为:大端字节序(网络字节序)
	            $second_byte = chr(126) . pack('n' , $len);
	        } else {
	            // payload length = 7,extended payload length = 64bit
	            // 最后 64 位被解释为无符号整数,大端字节序(网络字节序)
	            $second_byte = chr(127) . pack('J' , $len);
	        }
	    }
	    
	    // 注意了,发送给客户端的数据不需要处理
	    // 详情查看 websocket 文档!!
	    $encoded_data = $first_byte . $second_byte . $msg;
	    
	    // 这个就是发送给客户端的数据!   
	    return $encoded_data;
	}
}

htem:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<style type="text/css" media="screen">
li{
list-style-type: none;
}
#centre{
width: 70%;
height: 600px;
margin: 0 auto;
background-color: #ddd;
}
#chat{
width: 70%;
height: 540px;
display: inline-block;
background-color: #f3f3f3;
vertical-align: top;
margin:30px 0 30px 20px;
}
#user{
display: inline-block;
width: 20%;
height: 540px;
background-color: #d3d3d3;
vertical-align: top;
margin:30px 0 30px 40px;
}
#content{
height:80%;
}
#sand{
height:20%;
background-color: #fff
}
.user_login{
text-align: center;
color: #9c8e8e;
}
textarea{
width: 90%;
     height: 88%;
     resize:none;
     vertical-align: bottom;
     display: inline-block;
     margin-top: 5px;
     margin-left: 10px;
}
</style>
</head>
<body>
<div id="centre">
<div id="chat">
<div id="content">
<ul>
</ul>
</div>
<div id="sand">
<textarea name="text" id="text"></textarea>
<button type="button">发送</button>
</div>
</div>
<div id="user"></div>
</div>
</body>
<script>
var name = '';
var so = new WebSocket('ws://127.0.0.1:8888');//这里记得改成你自己的ip和端口
console.log(so)
so.onopen = function(){
console.log('链接成功');
//输入名字
setName();
}
so.onmessage=function(e){
let data = JSON.parse(e.data);
if (data.type == 'login') {
$('#content ul').append('<li>'+data.msg+'</li>');
}
if (data.type == 'users') {
$('#user').html('')
for (var i = 0; i < data.user.length; i++) {
$('#user').append('<li>'+data.user[i]+'</li>');
}
}
if(data.type == 'text'){
var sender = data.sender == name ? '我' : data.sender;
$('#content ul').append('<li>'+sender+':'+data.text+'</li>');
}
console.log(data)

}
function setName(){
while(true){
name = window.prompt('请输入你的名字!','');
if(!name){
alert('必须输入名字才能继续访问')
}else{
break;
}
}
login = 'login==='+name
so.send(login)
}
$('button').click(function(e){
var text = $('#text').val();
if(text != ''){
text = 'text==='+text+'===name==='+name
so.send(text)
$('#text').val('');
}else{
alert('你还没有写入消息呢!')
}
})


</script>
</html>


声明:本文内容转载自脚本之家,由网友自发贡献,版权归原作者所有,如您发现涉嫌抄袭侵权,请联系admin@php.cn 核实处理。
全部评论
文明上网理性发言,请遵守新闻评论服务协议