1.client.html
<!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;
}
#ems {
position: absolute;
z-index: 5;
display: none;
top: 0px;
left: 0px;
max-width: 230px;
background-color: #F1F1F1;
border: solid 1px #CCC;
padding: 5px;
}
#ems img {
width: 44px;
height: 44px;
border: solid 1px #FFF;
cursor: pointer;
}
#ems img:hover, #ems img:active {
border-color: #A4B7E3;
}
#ems a {
color: #069;
border-radius: 2px;
display: inline-block;
margin: 2px 5px;
padding: 1px 8px;
text-decoration: none;
background-color: #D5DFFD;
}
#ems a:hover, #ems a:active, #ems a.ck {
color: #FFF;
background-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:;" rel="external nofollow" rel="external nofollow" class="qp" onClick="this.parentNode.children[1].innerHTML=''">清屏</a>
</div>
<div class="rin">
<button id="sd">发送</button>
<span><img src="http://www.yxsss.com/ui/sk/t.png" title="表情" id="imgbq"><img src="http://www.yxsss.com/ui/sk/e.png" title="上传图片"><form><input type="file" title="上传图片" id="upimg"></form></span>
<p><input id="nrong"></p>
</div>
<div id="ems"><p></p>
<p class="tc"></p></div>
<script>
if (typeof(WebSocket) == 'undefined') {
alert('你的浏览器不支持 WebSocket ,推荐使用Google Chrome 或者 Mozilla Firefox');
}
</script>
<script src="http://www.yxsss.com/ui/p/a.js" type="text/javascript"></script>
<script>
(function () {
var key = 'all', mkey;
var users = {};
var url = 'ws://127.0.0.1:8000';
var so = false, n = false;
var lus = A.$('us'), lct = A.$('ct');
function st() {
var Arr1 = ["聪明的", "狡猾的", "可爱的", "美丽的", "狡猾的", "善良的", "帅气的", "逗比的"];
var Arr2 = ["大灰狼", "小白兔", "母老虎", "外星人", "皮卡丘", "HelloKitty", "吴亦凡", "薛之谦"];
var ran1 = Math.floor(Math.random() * Arr1.length + 1) - 1;
var ran2 = Math.floor(Math.random() * Arr2.length + 1) - 1;
var n = Arr1[ran1] + Arr2[ran2];
//以上五行是用来随机生成用户昵称的方法,参考一下 ,如果想自定义用户名可以将以上五行注释,然后以下两行取消注释
//n=prompt('请给自己取一个霸气的名字:');
//n=n.substr(0,16);
//console.log(n);
if (!n) {
return;
}
so = new WebSocket(url);
so.onopen = function () {
if (so.readyState == 1) {
so.send('type=add&ming=' + n);
}
}
so.onclose = function () {
so = false;
lct.appendChild(A.$$('<p class="c2">退出聊天室</p>'));
}
so.onmessage = function (msg) {
eval('var da=' + msg.data);
var obj = false, c = false;
if (da.type == 'add') {
var obj = A.$$('<p>' + da.name + '</p>');
lus.appendChild(obj);
cuser(obj, da.code);
obj = A.$$('<p><span>[' + da.time + ']</span>欢迎<a>' + da.name + '</a>加入</p>');
c = da.code;
} else if (da.type == 'madd') {
mkey = da.code;
da.users.unshift({'code': 'all', 'name': '大家'});
for (var i = 0; i < da.users.length; i++) {
var obj = A.$$('<p>' + da.users[i].name + '</p>');
lus.appendChild(obj);
if (mkey != da.users[i].code) {
cuser(obj, da.users[i].code);
} else {
obj.className = 'my';
document.title = da.users[i].name;
}
}
obj = A.$$('<p><span>[' + da.time + ']</span>欢迎' + da.name + '加入</p>');
users.all.className = 'ck';
}
if (obj == false) {
if (da.type == 'rmove') {
var obj = A.$$('<p class="c2"><span>[' + da.time + ']</span>' + users[da.nrong].innerHTML + '退出聊天室</p>');
lct.appendChild(obj);
users[da.nrong].del();
delete users[da.nrong];
} else {
da.nrong = da.nrong.replace(/{\\(\d+)}/g, function (a, b) {
return '<img src="sk/' + b + '.jpg">';
}).replace(/^data\:image\/png;base64\,.{50,}$/i, function (a) {
return '<img src="' + a + '">';
});
//da.code 发信息人的code
if (da.code1 == mkey) {
obj = A.$$('<p class="c3"><span>[' + da.time + ']</span><a>' + users[da.code].innerHTML + '</a>对我说:' + da.nrong + '</p>');
c = da.code;
} else if (da.code == mkey) {
if (da.code1 != 'all')
obj = A.$$('<p class="c3"><span>[' + da.time + ']</span>我对<a>' + users[da.code1].innerHTML + '</a>说:' + da.nrong + '</p>');
else
obj = A.$$('<p><span>[' + da.time + ']</span>我对<a>' + users[da.code1].innerHTML + '</a>说:' + da.nrong + '</p>');
c = da.code1;
} else if (da.code == false) {
obj = A.$$('<p><span>[' + da.time + ']</span>' + da.nrong + '</p>');
} else if (da.code1) {
obj = A.$$('<p><span>[' + da.time + ']</span><a>' + users[da.code].innerHTML + '</a>对' + users[da.code1].innerHTML + '说:' + da.nrong + '</p>');
c = da.code;
}
}
}
if (c) {
obj.children[1].onclick = function () {
users[c].onclick();
}
}
lct.appendChild(obj);
lct.scrollTop = Math.max(0, lct.scrollHeight - lct.offsetHeight);
}
}
A.$('sd').onclick = function () {
if (!so) {
return st();
}
var da = A.$('nrong').value.trim();
if (da == '') {
alert('内容不能为空');
return false;
}
A.$('nrong').value = '';
so.send('nr=' + esc(da) + '&key=' + key);
}
A.$('nrong').onkeydown = function (e) {
var e = e || event;
if (e.keyCode == 13) {
A.$('sd').onclick();
}
}
function esc(da) {
da = da.replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');
return encodeURIComponent(da);
}
function cuser(t, code) {
users[code] = t;
t.onclick = function () {
t.parentNode.children.rcss('ck', '');
t.rcss('', 'ck');
key = code;
}
}
A.$('ltian').style.height = (document.documentElement.clientHeight - 70) + 'px';
st();
var bq = A.$('imgbq'), ems = A.$('ems');
var l = 80, r = 4, c = 5, s = 0, p = Math.ceil(l / (r * c));
var pt = 'sk/';
bq.onclick = function (e) {
var e = e || event;
if (!so) {
return st();
}
ems.style.display = 'block';
document.onclick = function () {
gb();
}
ct();
try {
e.stopPropagation();
} catch (o) {
}
}
for (var i = 0; i < p; i++) {
var a = A.$$('<a href="javascript:;" rel="external nofollow" rel="external nofollow" >' + (i + 1) + '</a>');
ems.children[1].appendChild(a);
ef(a, i);
}
ems.children[1].children[0].className = 'ck';
function ct() {
var wz = bq.weiz();
with (ems.style) {
top = wz.y - 242 + 'px';
left = wz.x + bq.offsetWidth - 235 + 'px';
}
}
function ef(t, i) {
t.onclick = function (e) {
var e = e || event;
s = i * r * c;
ems.children[0].innerHTML = '';
hh();
this.parentNode.children.rcss('ck', '');
this.rcss('', 'ck');
try {
e.stopPropagation();
} catch (o) {
}
}
}
function hh() {
var z = Math.min(l, s + r * c);
for (var i = s; i < z; i++) {
var a = A.$$('<img src="' + pt + i + '.jpg">');
hh1(a, i);
ems.children[0].appendChild(a);
}
ct();
}
function hh1(t, i) {
t.onclick = function (e) {
var e = e || event;
A.$('nrong').value += '{\\' + i + '}';
if (!e.ctrlKey) {
gb();
}
try {
e.stopPropagation();
} catch (o) {
}
}
}
function gb() {
ems.style.display = '';
A.$('nrong').focus();
document.onclick = '';
}
hh();
A.on(window, 'resize', function () {
A.$('ltian').style.height = (document.documentElement.clientHeight - 70) + 'px';
ct();
})
var fimg = A.$('upimg');
var img = new Image();
var dw = 400, dh = 300;
A.on(fimg, 'change', function (ev) {
if (!so) {
st();
return false;
}
if (key == 'all') {
alert('由于资源限制 发图只能私聊');
return false;
}
var f = ev.target.files[0];
if (f.type.match('image.*')) {
var r = new FileReader();
r.onload = function (e) {
img.setAttribute('src', e.target.result);
};
r.readAsDataURL(f);
}
});
img.onload = function () {
ih = img.height, iw = img.width;
if (iw / ih > dw / dh && iw > dw) {
ih = ih / iw * dw;
iw = dw;
} else if (ih > dh) {
iw = iw / ih * dh;
ih = dh;
}
var rc = A.$$('canvas');
var ct = rc.getContext('2d');
rc.width = iw;
rc.height = ih;
ct.drawImage(img, 0, 0, iw, ih);
var da = rc.toDataURL();
so.send('nr=' + esc(da) + '&key=' + key);
}
})();
</script>
</body>
</html>
2.websocket.php
<?php
error_reporting(E_ALL ^ E_NOTICE);
ob_implicit_flush();
$sk = new Sock('127.0.0.1', 8000);
$sk->run();
class Sock
{
public $sockets;
public $users;
public $master;
private $sda = array();//已接收的数据
private $slen = array();//数据总长度
private $sjen = array();//接收数据的长度
private $ar = array();//加密key
private $n = array();
public function __construct($address, $port)
{
$this->master = $this->WebSocket($address, $port);
$this->sockets = array($this->master);
}
function run()
{
while (true) {
$changes = $this->sockets;
$write = NULL;
$except = NULL;
socket_select($changes, $write, $except, NULL);
foreach ($changes as $sock) {
if ($sock == $this->master) {
$client = socket_accept($this->master);
$key = uniqid();
$this->sockets[] = $client;
$this->users[$key] = array(
'socket' => $client,
'shou' => false
);
} else {
$len = 0;
$buffer = '';
do {
$l = socket_recv($sock, $buf, 1000, 0);
$len += $l;
$buffer .= $buf;
} while ($l == 1000);
$k = $this->search($sock);
if ($len < 7) {
$this->send2($k);
continue;
}
if (!$this->users[$k]['shou']) {
$this->woshou($k, $buffer);
} else {
$buffer = $this->uncode($buffer, $k);
if ($buffer == false) {
continue;
}
$this->send($k, $buffer);
}
}
}
}
}
function close($k)
{
socket_close($this->users[$k]['socket']);
unset($this->users[$k]);
$this->sockets = array($this->master);
foreach ($this->users as $v) {
$this->sockets[] = $v['socket'];
}
$this->e("key:$k close");
}
function search($sock)
{
foreach ($this->users as $k => $v) {
if ($sock == $v['socket'])
return $k;
}
return false;
}
function WebSocket($address, $port)
{
$server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($server, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind($server, $address, $port);
socket_listen($server);
$this->e('Server Started : ' . date('Y-m-d H:i:s'));
$this->e('Listening on : ' . $address . ' port ' . $port);
return $server;
}
function woshou($k, $buffer)
{
$buf = substr($buffer, strpos($buffer, 'Sec-WebSocket-Key:') + 18);
$key = trim(substr($buf, 0, strpos($buf, "\r\n")));
$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_write($this->users[$k]['socket'], $new_message, strlen($new_message));
$this->users[$k]['shou'] = true;
return true;
}
function uncode($str, $key)
{
$mask = array();
$data = '';
$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[] = array('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');
}
function e($str)
{
//$path=dirname(__FILE__).'/log.txt';
$str = $str . "\n";
//error_log($str,3,$path);
echo iconv('utf-8', 'gbk//IGNORE', $str);
}
}
?>
3.Windows+R输入cmd进入命令行,找到以上代码保存的文件目录,输入php websocket.php进行启动
4.打开Apache,输入http://127.0.0.1/workerman-chat/chat/client.html,路径地址根据自己的存放的地址进行操作。
5.聊天室引用了一个sk文件夹的表情包,这个sk文件夹可以自己建,放在和上面代码同一个文件夹下即可,放入19张图片,名字依次为1-19,可以发送表情。
6.在进行命令行操作时有可能会报错,需要配置php.ini,搜索sockets,如下图908行所示,把前面的注释去掉。
源代码在这里下载原文,我仅仅是代码的搬运工…