您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center   Code  
会员   
   
 
     
   
 订阅
  捐助
swoole 服务端120行代码构建一个websocket 聊天室.
 
  2907  次浏览      15
 2017-12-20 
 
编辑推荐:
本文来自于segmentfault.com,本篇文章围绕构建聊天室来写的,代码详细叙述,希望对大家有帮助。

swoole :http://www.swoole.com/

PHP的异步、并行、高性能网络通信引擎,使用纯C语言编写,提供了PHP语言的异步多线程服务器,异步TCP/UDP网络客户端,异步MySQL,异步Redis,数据库连接池,AsyncTask,消息队列,毫秒定时器,异步文件读写,异步DNS查询。 Swoole内置了Http/WebSocket服务器端/客户端、Http2.0服务器端。

7-22更新------ 昨天已经用面向对象和redis重做了一次,现在支持切换分组,私信了.并且对其他地方也做了一些优化. 视频演示: http://www.bilibili.com/video... 视频中的列表没有正常刷新漏洞已修复 github : https://github.com/buffge/buf...

demo 地址 http://www.buffge.xin/buffcha...

end------

7-27更新

今天服务器被人攻击了 他攻击也许就是为了测试或者玩我,而且他也没有得逞

他利用我的redis的漏洞来攻击我,因为我本地用一个可视化软件去查看我服务器的数据库,所以我

开放的权限并且没有加密,之后有人想透过redis 给我服务器上传脚本. 不过这个脚本就是重复运行而已,应该是个

练手的.他这个脚本没有运行起来,因为我redis 的权限很低. 他首先给我redis里面加入了这样一句话

然后想让这个脚本在我服务器中循环运行.真的可恶.我好心开源给大家看了玩玩,我也没空去搞那么安全那么好 但是就是有这种人渣来黑我.

等过几天我忙完了手上的事重新开启这个聊天室并添加新功能 和新样式 不说了,我先举报去了,我已经找到他的ip和他的群了.就看tx管不管了.bye~

-------end

昨晚一直在学习swoole. 终于搞出了一个多人聊天室.新手可以看看,高玩请直接右上角.

思路:

1:用户登录主页面,此时后台将用户放入数据库(我不会redis memcached 等),

数据库格式为

第一次登录只需要将fd插入就行了.

2:此时如果不进行注册,那么就无法发送信息给全局.

如果注册了 那么填写用户名,ajax请求 将用户名进行加盐信息摘要获得token.

注册成功后发送token: 117fdba5e4d4050 ed18ffd85ac86ed5b5d72dec6a 271f4e4f6a6e03f4b957cd4:user_name:张三123

这样的一个字符串给服务器,服务器会进行token 和名称验证,如果是正确的token.那么将用户信息插入到数据库.

并通知其他websocket 有新用户上线.

3:快乐的聊天吧.

路上遇到的坑:

1:我原本准备在server.php(就是swoole 后台服务)创建一个对象,

存储所有访客的信息 姓名等等,但是他不共享这个对象,我觉得可以用redis 缓存起来,

我这里就写了玩的,就用mysql弄了,也没有面向对象.

2:不能用session 反正用了 之后就是各种问题,我原本是准备用session 存储用户名称的.

后台没办法想出一个加密token 然后存入数据库,

具体的token是这样的.

注册时候用盐和sha256生成token,然后去swoole 服务器那边验证的时候也用同样的用户名和盐进行计算,看是否一样.如果一样 说明是正确的登录了. 我不知道这个方法好不好.以前只会session,这个是昨晚突然想到的.

服务器那边的日志如下:

4个用户的聊天界面如下:

服务器端 代码 看注释.写的渣别说,我是菜鸟.

<?php

/*
<#日期 = "2017-7-19">
<#时间 = "00:46:16">
<#人物 = "buff" >
<#备注 = " ">
*/
if (php_sapi_name() !== 'cli') {
exit("使用cli模式");
}
$serv = new Swoole\Websocket\Server("192.168.1.109", 9501);
//回调函数 新建一个websocket连接时 触发的事件
$serv->on('Open', function($server, $req) {
$mysqli = new mysqli("127.0.0.1", 'buffge', 'daimin', 'buffchat');
$charsetsql = "set names utf8";
$mysqli->query($charsetsql);
//将新登陆的用户 保存到mysql
$sql = "INSERT INTO `users` (`fd`) VALUES ('{$req->fd}')";
$mysqli->query($sql);
if ($mysqli->affected_rows !== 1) {
echo "插入数据失败!sql== {$sql}";
}
echo "新客户端连接: " . $req->fd . "时间:" . date("Y-n-j H:i:s") . "\n";
$userlist = "";
//检查当前共有多少用户在线
$sql = "select `user_name` from users where `user_name`!=''";
$res = $mysqli->query($sql);
for ($i = 0; $i < $res->num_rows; $i++) {
$result = $res->fetch_assoc();
$userlist .= ('"' . $result['user_name'] . '",');
}
$userlist = substr($userlist, 0, strlen($userlist) - 1);
//通知用户 当前在线用户列表
$server->push($req->fd, "{\"code\":\"4\",\"users\":[{$userlist}]}");
$res->free();
$mysqli->close();
});
//当收到用户的消息时 触发事件
$serv->on('Message', function($server, $frame) {
$mysqli = new mysqli("127.0.0.1", 'buffge', 'daimin', 'buffchat');
$charsetsql = "set names utf8";
$mysqli->query($charsetsql);
$sql = "select * from users where fd={$frame->fd}";
$res = $mysqli->query($sql);
$result = $res->fetch_assoc();
//获取当前发消息的人的名称
$user_name = $result['user_name'];
echo "收到来自客户端{$frame->fd}的消息: " . $frame->data . "\n";
//当用户是第一个注册时候(发送的语句前面5个字是token)
if (strpos($frame->data, 'token') === 0) {
//如果数据库中没有令牌
if ($result['token'] === null) {
$userData = explode(':', $frame->data);
$hash = hash('sha256', 'daimin' . $userData[3]);
if ($userData[1] == $hash) {
$sql = "UPDATE `users` SET `token` = '{$hash}',`user_name` = '{$userData[3]}' WHERE `fd` = {$frame->fd}";
$mysqli->query($sql);
if ($mysqli->affected_rows !== 1) {
echo "更新用户信息失败, sql === {$sql}";
$server->push($frame->fd, "{\"code\":\"5\",\"mes\":\"更新用户信息失败\"}");
}
$server->push($frame->fd, "{\"code\":\"3\",\"user_name\":\"{$userData[3]}\"}");
$res->free();
$userlist = "";
//检查当前共有多少用户在线
$sql = "select `user_name` from users where `user_name`!=''";
$res = $mysqli->query($sql);
for ($i = 0; $i < $res->num_rows; $i++) {
$result = $res->fetch_assoc();
$userlist .= ('"' . $result['user_name'] . '",');
}
$userlist = substr($userlist, 0, strlen($userlist) - 1);
//通知所有用户 当前在线用户列表
foreach ($server->connections as $fd) {
$server->push($fd, "{\"code\":\"4\",\"users\":[{$userlist}]}");
}
echo "新注册用户 {$userData[3]}\n";
}
else {
$server->push($frame->fd, "{\"code\":\"5\",\"mes\":\"token错误\"}");
}
}
//如果只是发送包含token这个字符串的语句 群发
else {
foreach ($server->connections as $fd) {
$server->push($fd, "{\"code\":\"2\",\"mes\":\"{$frame->data}\",\"user_name\":\"{$user_name}\"}");
}
}
}
//如果不是注册用户
else {
//如果没有令牌
if ($result['token'] === null) {
$server->push($frame->fd, "{\"mes\":\"请先登录!\"}");
return;
}
//将换行转换为br
$mes = nl2br($frame->data);
//格式化json
$mes = str_replace("\n", "", $mes);
//群发消息
foreach ($server->connections as $fd) {
$server->push($fd, "{\"code\":\"2\",\"mes\":\"{$mes}\",\"user_name\":\"{$user_name}\"}");
}
}
$res->free();
$mysqli->close();
});
//当websocket 断开连接时 触发事件
$serv->on('Close', function($server, $fd) {
$mysqli = new mysqli("127.0.0.1", 'buffge', 'daimin', 'buffchat');
$charsetsql = "set names utf8";
$mysqli->query($charsetsql);
$sql = "DELETE FROM `users` WHERE `users`.`fd` = {$fd}";
//当用户退出时 删除信息
$mysqli->query($sql);
if ($mysqli->affected_rows !== 1) {
echo "删除用户信息失败, sql === {$sql}";
}
echo "客户端{$fd}已断开连接\n";
$mysqli->close();
});

$serv->start();

前端js 核心

websocket.onmessage = function (evt) {
console.log(evt.data);
var data = JSON.parse(evt.data);
//根据code 判断业务
switch (data.code) {
//个人消息
case '1':
break;
//全局消息
case '2':
var $time = bf_get_time();//这是我自己写的一个获取时间函数
var $mes = data.mes;//消息内容
var $user_name = data.user_name;//发消息的人
var $who = $user_name === selfName ? "self" : "other";//根据名称设置格式
var $append = "<div class=\"mes_item\"><p><span class=\"user_name\">" + $user_name + " </span><time>" + $time + "</time></p><p class=\"message " + $who + "\">" + $mes + "</p></div>"
$(".gui_content").append($append);
var $cont_scrTop = $(".gui_content").scrollTop();
var $list_height = $(".mes_item:last-of-type").height();
$(".gui_content").animate({'scrollTop': $cont_scrTop + $list_height}, 100);
break;
//通知注册用户成功消息
case '3':
var $append = '<li>';
$append += data.user_name;
$append += '</li>';
$(".user_list ul").append($append);
$(".gui_user").html("<p>欢迎 " + data.user_name + "</p>");
selfName = data.user_name;
break;
//更新当前在线列表 删除的我没写~
case '4':
var $append = '';
var $nowUser = $(".user_list ul li");
outer:
for (var user in data.users) {
for (var i = 0; i < $nowUser.length; i++) {
if ($nowUser[i].innerText === data.users[user]) {
continue outer;
}
}
console.log(data.users[user]);
$append += '<li>';
$append += data.users[user];
$append += '</li>';
}
$(".user_list ul").append($append);
break;
case '5':
alert(data.mes);
break;
}
console.log('收到来自服务器的消息: ' + data.code);

};
等有空了用面向对象写一次把mysql 换成redis,现在的代码有点乱,一些功能也没有写,因为太困了,连续写了8小时.

如果有大神看的话 能不能告诉我 哪里该改进. 比如

   
2907 次浏览       15
相关文章

深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
相关文档

重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
相关课程

基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程