WebSocket 网页聊天室
先给大家开一个原始的websocket的连接使用范例
<?php
/*
* recv是从套接口接收数据,也就是拿过来,但是不知道是什么
* read是读取拿过来的数据,就是要知道recv过来的是什么
* write是向套接口写数据,但是只是写,并没有发送出去
* send是write之后,将数据传输到套接口,以便其他人recv之后read
*/ //设置一些基本的变量
$host="192.168.1.68"; //链接ip
$port='1423'; //端口号
//设置超时时间
set_time_limit(0);
//创建一个Socket
$socket=socket_create(AF_INET,SOCK_STREAM,0) or die("Could not create socket\n");//绑定Socket到端口
$result=socket_bind($socket,$host,$port) or die("Could not bind to socket\n");//开始监听链接
$result=socket_listen($socket,3) or die("Could not setup socket listener\n");//accept in coming connections
//另一个Socket来处理通信
$spawn=socket_accept($socket) or die("Could not accept in coming connection\n");//获得客户端的输入
$input=socket_read($spawn,1024) or die("Could not read input\n");//清空输入字符串
$input=trim($input);//处理客户端输入并返回结果
$output=strrev($input)."\n";
socket_write($spawn,$output,strlen($output)) or die("Could not write out put\n");//关闭
socket_close($spawn);
socket_close($socket);
ob_implicit_flush(); //将打开或关闭绝对(隐式)刷送。绝对(隐式)刷送将导致在每次输出调用后有一次刷送操作 //地址与接口,即创建socket时需要服务器的IP和端口
$sk = new WebSocket('127.0.0.1',1208); //对创建的socket循环进行监听,处理数据
$sk->Run(); class WebSocket{ public $sockets; //socket的连接池,即client连接进来的socket标志
public $users; //所有client连接进来的信息,包括socket、client名字等
public $master; //socket的resource,即前期初始化socket时返回的socket资源
/*
* 构造函数
* 实例化的时候,自动运行
* */
public function __construct($address, $port){
//创建socket并把保存socket资源在$this->master
$this->master = $this->WebSocket($address, $port); //$socket //创建socket连接池 连接的用户
$this->sockets = array($this->master); //$clients }
/*
* 传相应的IP与端口进行创建socket操作
* 连接socket 创建tcp socket
* */
function WebSocket($address,$port){
$server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($server, SOL_SOCKET, SO_REUSEADDR, 1);//1表示接受所有的数据包
socket_bind($server, $address, $port);
socket_listen($server); //监听端口
return $server;
} //对创建的socket循环进行监听,处理数据
public function Run(){
$null = NULL;
//死循环,直到socket断开
while(true){
$changes = $this->sockets;
$socket = $this->master;
/*
这个函数是同时接受多个连接的关键,我的理解它是为了阻塞程序继续往下执行。
socket_select ($sockets, $write = NULL, $except = NULL, NULL, timeout); $sockets可以理解为一个数组,这个数组中存放的是文件描述符。当它有变化(就是有新消息到或者有客户端连接/断开)时,socket_select函数才会返回,继续往下执行。
$write是监听是否有客户端写数据,传入NULL是不关心是否有写变化。
$except是$sockets里面要被排除的元素,传入NULL是”监听”全部。
最后一个参数是超时时间
如果为0:则立即结束
如果为n>1: 则最多在n秒后结束,如遇某一个连接有新动态,则提前返回
如果为null:如遇某一个连接有新动态,则返回
*/
/*
* @socket_select 第四个参数
* 第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;
* 第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;
* 第三,timeout的值大于0,这就是等待的超时时间,即 select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。
*/
socket_select($changes,$null,$null,NULL);
//socket_select($changed, $null, $null, 0, 10); /*
* 如果有新的链接进来
* */
//如果有新的连接
if (in_array($socket, $changes)) {
//接受一个socket连接
$socket_new = socket_accept($socket);
//给新连接进来的socket一个唯一的ID
$changes = $this->sockets[] = $socket_new; //将新连接进来的socket存进连接池
//通过socket获取数据执行handshake
$header = socket_read($socket_new, 1024); $this->perform_handshaking($header, $socket_new, '127.0.0.1', '1208'); //握手协议 //获取client ip 编码json数据,并发送通知
/*
* 获取远程套接口的名字,包括它的IP和端口。
* */
socket_getpeername($socket_new, $ip);
$response = $this->mask(json_encode(array('type'=>'system', 'message'=>'ip:'.$ip.' 已连接!')));
$this->send_message($response);
$found_socket = array_search($socket, $changes);
unset($changes[$found_socket]);
} //轮询 每个client socket 连接 发送数据
foreach ($changes as $key=>$changed_socket) { //如果有client数据发送过来
/*
* @socket_recv ( resource $socket , string &$buf , int $len , int $flags )
* 从已连接的socket接收数据
* */
while(socket_recv($changed_socket, $buf, 1024, 0) >= 1)
{
//解码发送过来的数据
$received_text = $this->unmask($buf);
$tst_msg = json_decode($received_text);
$user_name = $tst_msg->name;
$user_message = $tst_msg->message; $this->writeLog('消息:', json_decode($received_text, true)); //把消息发送回所有连接的 client 上去
$response_text = $this->mask(json_encode(array('type'=>'usermsg', 'name'=>$user_name, 'message'=>$user_message)));
$this->send_message($response_text);
break 2;
} //检查offline的client
$buf = @socket_read($changed_socket, 1024, PHP_NORMAL_READ);
if ($buf === false) {
$found_socket = array_search($changed_socket, $changes);
/*
* 获取远程套接口的名字,包括它的IP和端口。
* */
socket_getpeername($changed_socket, $ip);
unset($changes[$found_socket]);
$response = $this->mask(json_encode(array('type'=>'system', 'message'=>'ip:'.$ip.' 已断开!')));
$this->send_message($response);
}
}
}
/*
* 在监听外面关闭socket链接
* 关闭监听的socket
* */
socket_close($this->master);
} /*
* 对发送的数据进行编码
* */
public function mask($text) {
$b1 = 0x80 | (0x1 & 0x0f);
$length = strlen($text); if ($length <= 125) {
$header = pack('CC', $b1, $length);
} elseif ($length > 125 && $length < 65536) {
$header = pack('CCn', $b1, 126, $length);
} elseif ($length >= 65536) {
$header = pack('CCNN', $b1, 127, $length);
} return $header.$text;
} /*
* 对接受来的数据进行解码
* */
public function unmask($text) {
/*
* @ ord
* 函数返回字符串的首个字符的 ASCII 值。
* */
$length = ord($text[1]) & 127;
if ($length == 126) {
$masks = substr($text, 4, 4);
$data = substr($text, 8);
} elseif ($length == 127) {
$masks = substr($text, 10, 4);
$data = substr($text, 14);
} else {
$masks = substr($text, 2, 4);
$data = substr($text, 6);
} $text = "";
$len = strlen($data);
for ($i = 0; $i < $len; $i++) {
$text .= $data[$i] ^ $masks[$i%4];
}
return $text;
} //握手的逻辑
public function perform_handshaking($receved_header,$client_conn, $host, $port)
{
$headers = array();
$lines = preg_split("/rn/", $receved_header); foreach($lines as $line) {
$line = chop($line);
if(preg_match('/A(S ): (.*)z/', $line, $matches)) {
$headers[$matches[1]] = $matches[2];
}
} if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $receved_header, $match)) { $key = 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: ".$key."\r\n\r\n";
socket_write($client_conn,$upgrade,strlen($upgrade));
} return true;
} /*
* @socket_
* recv是从套接口接收数据,也就是拿过来,但是不知道是什么
* read是读取拿过来的数据,就是要知道recv过来的是什么
* write是向套接口写数据,但是只是写,并没有发送出去
* send是write之后,将数据传输到套接口,以便其他人recv之后read
* 发送消息
* */
//发送消息的方法
public function send_message($msg) {
//global $this->sockets;
foreach($this->sockets as $changed_socket)
{
@socket_write($changed_socket,$msg,strlen($msg));
}
return true;
} /*
* 将发送的消息记录到log中
* var_export 输出或返回一个变量的字符串表示
* */
//打印本地Err
public function writeLog($str, $arr) {
$cont = var_export($arr, true)."\n";
$time = date('Y-m-d H:i:s',time()); file_put_contents('./WebSocket/mylog', $time, FILE_APPEND);
file_put_contents('./WebSocket/mylog', $str, FILE_APPEND);
file_put_contents('./WebSocket/mylog', $cont."\n", FILE_APPEND); echo $cont;
} }
//客户端的实现代码
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"/>
<title>HTML5 websocket 网页聊天室 javascript php</title>
<style type="text/css">
body,p{margin:0px; padding:0px; font-size:14px; color:#333; font-family:Arial, Helvetica, sans-serif;}
#ltian,.rin{width:98%; margin:5px auto;}
#ltian{border:1px #ccc solid;overflow-y:auto; overflow-x:hidden; position:relative;}
#ct{margin-right:111px; height:100%;overflow-y:auto;overflow-x: hidden;}
#us{width:110px; overflow-y:auto; overflow-x:hidden; float:right; border-left:1px #ccc solid; height:100%; background-color:#F1F1F1;}
#us p{padding:3px 5px; color:#08C; line-height:20px; height:20px; cursor:pointer; overflow:hidden; white-space:nowrap; text-overflow:ellipsis;}
#us p:hover,#us p:active,#us p.ck{background-color:#069; color:#FFF;}
#us p.my:hover,#us p.my:active,#us p.my{color:#333;background-color:transparent;}
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;}
.rin p{margin-right:160px;}
.rin span{float:right; padding:6px 5px 0px 5px; position:relative;}
.rin span img{margin:0px 3px; cursor:pointer;}
.rin span form{position:absolute; width:25px; height:25px; overflow:hidden; opacity:0; top:5px; right:5px;}
.rin span input{width:180px; height:25px; margin-left:-160px; cursor:pointer} #ct p{padding:5px; line-height:20px;}
#ct a{color:#069; cursor:pointer;}
#ct span{color:#999; margin-right:10px;}
.c2{color:#999;}
.c3{background-color:#DBE9EC; padding:5px;}
.qp{position:absolute; font-size:12px; color:#666; top:5px; right:130px; text-decoration:none; color:#069;}
.tc{text-align:center; margin-top:5px;}
</style>
</head>
<body>
<div id="ltian">
<div id="us" class="jb"></div>
<div id="ct"></div>
<a href="javascript:;" class="qp" onClick="this.parentNode.children[1].innerHTML=''">清屏</a>
</div>
<div class="rin">
<button id="sd">发送</button>
<p><input id="message"></p>
</div>
<div id="ems"><p></p><p class="tc"></p></div> </body>
<script src="http://www.study.com/Reids/jquery-1.11.3.min.js"></script>
<script>
//console.log(Math.ceil((Math.random()*100000)+(Math.random()*100000)));
if(typeof(WebSocket)=='undefined'){
alert('你的浏览器不支持 WebSocket ,推荐使用Google Chrome 或者 Mozilla Firefox');
}
</script>
<script>
$(function(){
var websocket;
var name = Math.ceil((Math.random()*100000)+(Math.random()*100000));
if(window.WebSocket) {
websocket = new WebSocket('ws://'+ip+':1208/WebSocket/Class/WebSocket.php'); //连接建立
websocket.onopen = function(evevt) {
console.log("WebSocket已连接!");
$('#ct').append('<p><span>大家好,WebSocket已连接!</span></p>');
} //收到消息
websocket.onmessage = function(event) {
var msg = JSON.parse(event.data); //解析收到的json消息数据
console.log(msg);
if(msg.type == 'system'){
$('#ct').append('<p><span>'+msg.message+'</span></p>');
console.log(msg.message);
}else if(msg.type == 'usermsg'){
$('#ct').append('<p><span>'+msg.message+'</span><a>用户:'+msg.name+'</a></p>');
console.log(msg.message);
}
} //发生错误
websocket.onerror = function(event) {
console.log("WebSocket连接出错!"+ event.data);
$('#ct').append('<p><span>WebSocket Error ' + event.data + '</span></p>');
} //连接关闭
websocket.onclose = function(event) {
console.log('WebSocket已断开连接');
$('#ct').append('<p><span>WebSocket已关闭!</span></p>');
}
/* 发送消息 */
function send() {
var message = $('#message').val(); if(!message) {
alert('发送消息不能为空!'); return false;
} var msg = { message: message, name: name }; try{
websocket.send(JSON.stringify(msg));
$('#message').val('');
//websocket.send(msg);
} catch(ex) {
console.log(ex);
}
} //按下enter键发送消息
$(window).keydown(function(event) {
if(event.keyCode == 13) {
send();
}
}); //点发送按钮发送消息
$('#sd').bind('click',function() {
send();
}); } else {
console.log('你的浏览器不支持Web Socket!');
}
})
</script>
WebSocket 网页聊天室的更多相关文章
- WebSocket 网页聊天室的实现(服务器端:.net + windows服务,前端:Html5)
websocket是HTML5中的比较有特色一块,它使得以往在客户端软件中常用的socket在web程序中也能轻松的使用,较大的提高了效率.废话不多说,直接进入题. 网页聊天室包括2个部分,后端服务器 ...
- JAVA实现webSocket网页聊天室
一.什么是webSocket WebSocket 是一种网络通信协议,是持久化协议.RFC6455 定义了它的通信标准. WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全 ...
- Java和WebSocket开发网页聊天室
小编心语:咳咳咳,今天又是聊天室,到现在为止小编已经分享了不下两个了,这一次跟之前的又不大相同,这一次是网页聊天室,具体怎么着,还请各位看官往下看~ Java和WebSocket开发网页聊天室 一.项 ...
- JavaWeb网页聊天室(WebSocket即时通讯)
原文:http://baike.xsoftlab.net/view/656.html Git地址 http://git.oschina.net/loopcc/WebSocketChat 概要: Web ...
- 基于flask的网页聊天室(四)
基于flask的网页聊天室(四) 前言 接前天的内容,今天完成了消息的处理 具体内容 上次使用了flask_login做用户登录,但是直接访问login_requare装饰的函数会报401错误,这里可 ...
- 基于WebSocket实现聊天室(Node)
基于WebSocket实现聊天室(Node) WebSocket是基于TCP的长连接通信协议,服务端可以主动向前端传递数据,相比比AJAX轮询服务器,WebSocket采用监听的方式,减轻了服务器压力 ...
- websocket+golang聊天室
原文地址: http://www.niu12.com/article/3 websocket+golang聊天室 main.go和index.html放在同一目录下 main.go package m ...
- Spring之WebSocket网页聊天以及服务器推送
Spring之WebSocket网页聊天以及服务器推送 转自:http://www.xdemo.org/spring-websocket-comet/ /Springframework /Spring ...
- Ext JS学习第十六天 事件机制event(一) DotNet进阶系列(持续更新) 第一节:.Net版基于WebSocket的聊天室样例 第十五节:深入理解async和await的作用及各种适用场景和用法 第十五节:深入理解async和await的作用及各种适用场景和用法 前端自动化准备和详细配置(NVM、NPM/CNPM、NodeJs、NRM、WebPack、Gulp/Grunt、G
code&monkey Ext JS学习第十六天 事件机制event(一) 此文用来记录学习笔记: 休息了好几天,从今天开始继续保持更新,鞭策自己学习 今天我们来说一说什么是事件,对于事件 ...
随机推荐
- 【学习总结】Eclipse常用快捷键
相关博文 [JAVA]eclipse-Introduction
- Vue打包后访问静态资源路径问题
Vue介绍中static文件夹里放的是静态资源目录,如图片.字体等. 我们发现运行npm run start后本地图片路径是没问题的,但是打包上传后会怎么样呢? 我们知道,执行npm run buil ...
- [三下五除二]在Eclipse上的JFinal_Demo
承接上回在IDEA的JFinal的项目的导入,今次同样是同一个文件,但在Eclipse上运行.在Eclipse上运行官网的JFinal的例子是及其快捷. 打开Eclipse,并进入如下的界面. 点击导 ...
- UIGestureRecognizer 手势
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.v ...
- springboot中使用RabbitMq
转载:http://www.ityouknow.com/springboot/2016/11/30/spring-boot-rabbitMQ.html RabbitMQ 即一个消息队列,主要是用来实现 ...
- 【串线篇】SQL映射文件-resultMap自定义封装
mybatis默认封装规则: 1).按照列明和属性名一一对应的规则(不区分大小写) 2).如果不一一对应: 1).开启驼峰命名(数据库aaa_bbb, 程序中aaaBbb) 2).起别名 3).自定义 ...
- BZOJ3207 花神的嘲讽计划I
Time Limit: 10 Sec Memory Limit: 128 MB Summary 给你一个模式串P,q个询问,对每个询问回答从Pl到Pr是否存在与给定串相同的子串,同时有所有的给定串长度 ...
- MySQL使用版本号实现乐观锁
原创转载请注明出处:https://www.cnblogs.com/agilestyle/p/11608581.html 乐观锁适用于读多写少的应用场景 乐观锁Version图示 Project D ...
- php min()函数 语法
php min()函数 语法 作用:从所有参数中找到最小数 语法:min(X,Y,Z) 或者min(array(X,Y,Z)) 参数:min函数中参数至少一个,可以多个参数,也可以是数组. 说明:如果 ...
- 4412 chmod权限
chmod权限 使用命令"man 2 chmod"学习chmod函数• int chmod(const char *path, mode_t mode);– 参数*path:文件路 ...