在开始之前,有句话想说,曾经我以为socket会很难入门,所以为了节省时间,使用了ajax轮询的方式,最近项目不是很多,想起来优化一下曾经的项目,就准备引入socket代替ajax轮询,从开始到发出第一句话并接收处理用了大概一天时间,socket并不可怕,可怕的是我当初畏惧它的心。
好了,废话不多说,开始干活。
环境:阿里云ECS(windows) + Thinkphp5.0.24
在开始之前要先确定tp版本,tp5.0.24应该使用topthink/think-worker的 1.0 版本,刚搭好socket环境的时候,这个问题困扰我很久才解决,就是版本问题,tp5是不能使用高版本的think-worker的。
然后以下是我一步一步试验出来的流程:
一、要在服务器上开放端口,具体方法就是win+r -> 搜索防火墙 -> 高级设置 -> 入站规则 -> 新建规则 -> 端口,例如12138 -> 下 一步…… -> 保存
二、如果是阿里云ECS,要在ECS中建立安全组,ECS控制台 -> 具体的某个服务器 -> 安全组设置 -> 将刚刚开放的12138端口加入到安全组中
三、别忘了重启服务器,不知道这步适不适用于所有情况,反正我的服务器没重启端口就一直不生效,也是困扰了很久
端口配置完成之后,就要开始引包了,推荐使用composer引入
composer的具体使用方法请百度
项目引入成功后,可以测试一下端口是否可以成功ping通
端口通过之后就可以开始配置项目了:
在项目的访问根目录写一个开启服务的文件server.php:
#!/usr/bin/env php
<?php
define('APP_PATH', __DIR__ . '/../application/');
define('BIND_MODULE','admin/Socket');
// 加载框架引导文件
require __DIR__ . '/../thinkphp/start.php';
server.php第四行代表具体workerman初始化文件在application/admin/controller下的socket.php,socket.php示例:
<?php
namespace application\admin\controller;
use think\worker\Server;
use Workerman\Lib\Timer;
error_reporting(E_ERROR | E_PARSE);
class Socket extends Server{
protected $socket = 'websocket://0.0.0.0:12138'; //0.0.0.0表示内部、外部等均可访问
protected $processes = 1; //因为业务需要向指定ID发送消息,此处进程数必须为1,若不需要可设置其他值
protected $uidConnections = [];
/**
* 收到信息
* @param $connection
* @param $data
*/
public function onMessage($connection, $data){
global $worker;
if(!isset($connection -> uid)){
$connection -> uid = $data['u_id'];
/*
* 保存uid到connection的映射,这样可以方便的通过uid查找connection,
* 实现针对特定uid推送数据
*/
$worker -> uidConnections[$connection -> uid] = $connection;
}
$connection -> lastMessageTime = time();
//此处添加个人的业务代码,例如对数据的处理、对发送者的反馈或向对方发送消息等操作
}
//向指定ID发送消息
private function sendMessageByUid($uid, $message){
global $worker;
if(isset($worker -> uidConnections[$uid])){
$worker -> uidConnections[$uid] -> send($message);
}
}
/**
* 当连接建立时触发的回调函数
* @param $connection
*/
public function onConnect($connection){
//此处可以判断连接数等
}
/**
* 当连接断开时触发的回调函数
* @param $connection
*/
public function onClose($connection){
global $worker;
if(isset($connection -> uid)){
// 连接断开时删除映射
unset($worker -> uidConnections[$connection -> uid]);
}
}
/**
* 当客户端的连接上发生错误时触发
* @param $connection
* @param $code
* @param $msg
*/
public function onError($connection, $code, $msg){
echo "error $code $msg\n";
}
/**
* 每个进程启动
* @param $worker
*/
public function onWorkerStart($worker){
//进程启动时加入对每个连接的心跳判断,若55秒内没有对服务器发起过心跳或其他任何请求,则断开连接
Timer::add(1, function()use($worker){
$time_now = time();
foreach($worker -> connections as $connection) {
// 有可能该connection还没收到过消息,则lastMessageTime设置为当前时间
if (empty($connection -> lastMessageTime)) {
$connection -> lastMessageTime = $time_now;
$u_ids[] = $connection -> uid;
continue;
}
// 上次通讯时间间隔大于心跳间隔,则认为客户端已经下线,关闭连接
if ($time_now - $connection->lastMessageTime > 55) {
$connection -> close();
}
}
});
}
}
每次使用时,需要用命令行手动运行server.php来开启服务:
1.在cmd命令行中使用cd来进入到server.php所在目录
2.运行 php server.php 来开启服务,服务开启成功后,如下图提示:
需要注意的是,每次socket.php的代码更改后,需要重新开启一次服务,否则新代码将不会执行
后端配置完成后,开始配置前端,此处给出的是websocket,基于layui的示例。layui是一款优秀的前端框架,layui的即时通讯layim极为方便的解决了聊天界面的复杂实现且扩展容易。
layim的使用及引入请参考官方网站:layui
//首先判断浏览器是否支持websocket
if(typeof(WebSocket) == 'undefined'){
layer.msg('你的浏览器不支持 WebSocket,无法进行聊天功能,推荐使用Google Chrome、Mozilla Firefox、360浏览器及QQ浏览器等');
}
//layui使用
layui.use('layim', function(layim){
//layim的初始化配置
layim.config({
brief: false,//是否简约模式(如果true则不显示主面板)
title: '客服会话', //主面板最小化后显示的名称
min: false, //用于设定主面板是否在页面打开时,始终最小化展现
isAudio: false, //是否开启聊天工具栏音频
isVideo: false, //是否开启开启聊天工具栏视频
notice: false, //是否开启桌面消息提醒,即在浏览器之外的提醒
voice: false, //不开启声音提示
isfriend: true, //是否开启好友
isgroup: false, //是否开启群组
maxLength: 3000, //可允许的消息最大字符长度
chatLog: layui.cache.dir + 'css/modules/layim/html/chatlog.html', //历史记录模板
init: { //获取主面板列表信息
url: '', //获取好友列表的接口地址
type: 'get', //默认get,一般可不填
data: {
//额外参数
}
},
uploadImage: {
url: '', //图片上传地址
}
});
var so = new WebSocket('ws://域名或IP:12138');
//websocket心跳
var heartCheck = {
timeout: 55000, //55秒心跳一次,告诉服务器,这个连接还接着用
timeoutObj: null,
reset: function(){
clearTimeout(this.timeoutObj);
this.start();
},
start: function(){
this.timeoutObj = setTimeout(function(){
console.log('我心跳了');
}, this.timeout);
},
remove: function(){
clearTimeout(this.timeoutObj);
}
}
//连接成功时触发
so.onopen = function(){
console.log('我上线了');
//开启心跳
heartCheck.start();
};
//so.readyState属性值
// 0 :对应常量CONNECTING (numeric value 0),
// 正在建立连接连接,还没有完成。The connection has not yet been established.
// 1 :对应常量OPEN (numeric value 1),
// 连接成功建立,可以进行通信。The WebSocket connection is established and communication is possible.
// 2 :对应常量CLOSING (numeric value 2)
// 连接正在进行关闭握手,即将关闭。The connection is going through the closing handshake.
// 3 : 对应常量CLOSED (numeric value 3)
// 连接已经关闭或者根本没有建立。The connection has been closed or could not be opened.
so.onclose = function(){
console.log('会话已关闭');
heartCheck.remove();
}
//发送消息时执行
layim.on('sendMessage',function(res) {
var mine = res.mine; //包含我发送的消息及我的信息
var to = res.to; //对方的信息
so.send(JSON.stringify({
//可加其他参数
type: 'chatMessage',//随便写,需要时对应上即可
data: res
}));
});
//监听收到的聊天消息
so.onmessage = function(res) {
//重置心跳
heartCheck.reset();
//官方示例是JSON.parse(),我在使用的时候不生效,试了不少方法,随后eval()可以解析
var msg_res = eval('('+ res.data +')');
//以下为我写的业务代码,收到消息时有code,并对参数进行了处理
if(msg_res.code == 200){
if(msg_res.addfriend == 1){
//添加好友
layim.addList(msg_res);
}else if(msg_res.send == 1){
//系统消息
if(msg_res.system == 1){
layim.getMessage({
system: true
,id: msg_res.to_id
,type: "friend"
,content: msg_res.msg
});
}else{
layim.getMessage(msg_res);
}
}
}else{
//弹出框报错
layer.msg(msg_res.msg);
}
};
});
以上就是socket的简单使用方法,更多逻辑操作请参照自身的业务。本文只是为了解决一些简单的入门问题,重在抛砖引玉,各位看官如有意见及建议请留言,在下必然回复您的厚爱。
<!--结束,谢谢,再见-->