一、GateWayWorker简单介绍:
a)、GatewayWorker基于Workerman开发的一个项目框架,用于快速开发TCP长连接应用,例如app推送服务端、即时IM服务端、游戏服务端、物联网、智能家居等等;
b)、如果你的项目是长连接并且需要客户端与客户端之间通讯,建议使用GatewayWorker。
短连接或者不需要客户端与客户端之间通讯的项目建议使用Workerman。
GatewayWorker不支持UDP监听,所以UDP服务请选择Workerman。
如果你是一个有多进程socket编程经验的人,喜欢定制自己的进程模型,可以选择Workerman。
二、GatewayClient安装
按照手册下载地址:https://github.com/walkor/GatewayClient
你也可以拿我的压缩包:百度网盘,提取码:4ewq
下载完成后你可以放在THINKPHP框架的extend目录,为了方便开发:
三、结合THINKPHP框架开发:
参照手册:http://doc2.workerman.net/work-with-other-frameworks.html
在Events.php下直接上现成的测试代码(注意:仅供参考)
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
/**
* 用于检测业务代码死循环或者长时间阻塞等问题
* 如果发现业务卡死,可以将下面declare打开(去掉//注释),并执行php start.php reload
* 然后观察一段时间workerman.log看是否有process_timeout异常
*/
use \GatewayWorker\Lib\Gateway;
/**
* 主逻辑
* 主要是处理 onConnect onMessage onClose 三个方法
* onConnect 和 onClose 如果不需要可以不用实现并删除
*/
class Events
{
/**
* 当客户端连接时触发
* 如果业务不需此回调可以删除onConnect
*
* @param int $client_id 连接id
*/
public static function onConnect($client_id)
{
Gateway::sendToClient($client_id, json_encode([
'type'=>'init',//客户连接后向用户发送绑定uid请求(1)
'client_id'=>$client_id
]));
// 向当前client_id发送数据
// Gateway::sendToClient($client_id, "Hello $client_id\r\n");
// 向所有人发送
// Gateway::sendToAll("$client_id login\r\n");
}
/**
* 当客户端发来消息时触发
* @param int $client_id 连接id
* @param mixed $message 具体消息
*/
public static function onMessage($client_id, $message)
{
// 向所有人发送
// Gateway::sendToAll("$client_id said $message\r\n");
$message_data = json_decode($message,true);
if(!$message_data){
return;
}
switch ($message_data['type']) {
case "say":
$content = $message_data['data'];
$fromid = $message_data['fromid'];
$toid = $message_data['toid'];
$infotype = $message_data['infotype'];
$data = [
'type' => 'answer',
'id' => $client_id,
'fromid' =>$fromid,
'toid' => $toid,
'time' => time(),
'data' => $content,
'infotype' => $infotype
];
if(Gateway::isUidOnline($toid)){//判断用户是否在线
$data['isread'] = 1;
file_put_contents(__DIR__.'/123.txt',json_encode($data));
Gateway::sendToUid($toid, json_encode($data));//向指定用户发送信息
}else{
$data['isread'] = 0;
$data['type'] = 'save';
file_put_contents(__DIR__.'/1234.txt',json_encode($data));
Gateway::sendToUid($fromid,json_encode($data));//向指定用户发送信息
}
break;
case "bind":
/*接收到客户端发来的uid后进行绑定(3)*/
$uid = $message_data['fromid'];
Gateway::bindUid($client_id,$uid);//将client_id与uid进行绑定
break;
case "pong":
return ;//心跳检测
break;
}
}
/**
* 当用户断开连接时触发
* @param int $client_id 连接id
*/
public static function onClose($client_id)
{
// 向所有人发送
// GateWay::sendToAll("$client_id logout\r\n");
}
}
前端代码(仅供参考):
注意:文件位置在你的项目地址\application\index\view\index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
#talk{
width:370px;
list-style-type:none;
}
#talk .right{
text-align: left;
}
#talk .left{
text-align: right;
}
.regi_box{
width:250px;
border:1px solid #000;
padding:10px;
box-sizing: border-box;
}
.regi_box .sub,.regi_box .sub2{
width:100%;
text-align: center;
color:#fff;
background:#ff5555;
margin-top:10px;
padding:5px;
box-sizing: border-box;
border-radius: 7px;
cursor: pointer;
}
.out{
padding:2px 4px;
border-radius: 7px;
background:#ff5555;
color:#fff;
cursor:pointer;
width:fit-content;
}
</style>
</head>
<body>
{if ($user['id']==0 || $user['id']=='')}
<!--<div class="regi_box">
<div class="register">注册会员</div>
<label for="nickname">昵称:</label><input type="text" id="nickname" name="nickname" value="">
<div class="sub">注册</div>
</div>-->
<div class="regi_box">
<div class="register">登录</div>
<label for="uid">ID:</label><input type="text" id="uid" name="id" value="">
<div class="sub2">登录</div>
</div>
{else}
您已登录,{$user['nickname']},IP:{$user['client_ip']}
<div class="out">退出</div>
{/if}
<div class="panel panel-default panel-intro">
<input type="hidden" id="uid" value="{$uid}">
<div class="main wrap">
<ul id="talk" class="wrap">
{foreach name="list" item="vo"}
{if $vo.uid!=$user['id']}
<!--我的发送-->
{if $vo.info_type==1}
<li class="right"><p>{$vo.content}</p></li>
{else}
<li class="right"><img src="{$vo.content}" style="width:100px;height:100px;"></li>
{/if}
{else}
<!--对方发送-->
{if $vo.info_type==1}
<li class="left"><p>{$vo.content}</p></li>
{else}
<li class="left"><img src="{$vo.content}" style="width:100px;height:100px;"></li>
{/if}
{/if}
{/foreach}
</ul>
</div>
<div class="comment">
<textarea id="comment" class="form-control editor" rows="1" name="comment" cols="50"></textarea>
<input type="file" name="pic" id="file" style="display:none">
<span id="pic">图片</span>
<button type="button" class="butt send-btn">发送</button>
</div>
</div>
</body>
<script src="__JS__/jquery-1.11.3.min.js"></script>
<script type="text/javascript">
$('.sub').click(function(){
let nickname = $('#nickname').val();
if(nickname==''){
alert('请填写昵称!');
}
$.ajax({
url:'/register',
dataType:'json',
data:{
nickname:nickname
},
type:'post',
success:function(res){
alert('登录成功');
setTimeout(function(){
window.location.reload();
},1000)
}
})
});
$('.sub2').click(function(){
let id = $('#uid').val();
if(id==''){
alert('请填写ID号!');
}
$.ajax({
url:'/login',
dataType:'json',
data:{
id:id
},
type:'post',
success:function(res){
alert('登录成功');
setTimeout(function(){
window.location.reload();
},1000)
}
})
});
$('.out').click(function(){
$.ajax({
url:'/out',
dataType:'json',
data:{
},
type:'post',
success:function(res){
alert('退出登录成功');
setTimeout(function(){
window.location.reload();
},1000)
}
})
});
var fromid = {$user['id']==''?0:$user['id']};
var toid = 1;//登录ID1时这里修改2,登录ID2时这里修改1。
var ws = new WebSocket("ws://127.0.0.1:8282");
var msg = '{"type":"bind","fromid":"'+fromid+'"}';
// send();
ws.onmessage = function(e){
ws.send(msg);
// var message = e;
var message2 = jQuery.parseJSON(e.data);
console.log('message2',e,message2);
// var message = eval("("+e.data+")");
switch(message2.type){
case "answer" :
if(toid==message2.fromid){
var html = '';
if(message2.infotype=='1'){
html += '<li class=\"right\">'+'<p>'+message2.data+'</p>'+'</li>';
}else{
html += '<li class=\"right\"><img src=\"'+message2.data+'\" style="width:100px;height:100px;"></li>';
}
$("#talk").append(html);
message2.type = '1';
send(message2);
}
break;
case "init" :
var bind = '{"type":"bind","fromid":"'+fromid+'"}';
ws.send(bind);/*接收到服务端发来的绑定uid请求,将uid发送至服务端进行绑定(2)*/
break;
case "ping" :
var pong = '{"type":"pong","fromid":"'+fromid+'"}';
break;
case "save" :
if(message2.fromid==fromid){
message2.fromid = toid;
message2.type='2';
send(message2);
}
break;
}
};
$(".send-btn").click(function() {
var message = $("#comment").val();
var html = '';
html += '<li class=\"left\">'+'<p>'+message+'</p>'+'</li>';
$("#talk").append(html);
$("#comment").val("");
var msg = '{"type":"say","infotype":"1","data":"'+message+'","fromid":"'+fromid+'","toid":"'+toid+'"}';
// send();
if(fromid=='' || fromid==0){
alert('请先登录');return;
}
ws.send(msg);
});
function send(message){
var uid = message.fromid;
var content = message.data;
var time = message.time;
var isread = message.isread;
var toid = message.toid;
// url('index/message/talk')
$.ajax({
url: "/talk",
type: 'POST',
dataType: 'json',
data: {
'uid' : uid,
'content' : content,
'time' : time,
'isread' : isread,
'type':message.type,
'info_type':message.infotype,
'toid':toid,
}
})
}
$(document).ready(function(){
$('#talk').scrollTop( $('#talk')[0].scrollHeight );
});
$("#pic").click(function() {
$("#file").click();
});
$("#file").change(function() {
fromdata = new FormData();
fromdata.append('file',$("#file")[0].files[0]);
$.ajax({
url: "{:url('vilage/message/upload')}",
type: 'post',
dataType: 'json',
data: fromdata,
processData:false,
contentType:false,
success:function(res){
console.log(res);
res = JSON.parse(res);
console.log(res);
if(res.code==1)
{
var domain = window.location.host;
var imgurl = 'http://' + domain + res.url;
var msg = '{"type":"say","infotype":"2","infotype":"2","data":"'+imgurl+'","fromid":"'+fromid+'","toid":"'+toid+'"}';
var html = '';
html += '<li class=\"left\"><img src=\"'+imgurl+'\" style="width:100px;height:100px;"></li>';
$("#talk").append(html);
$(".chat-content").append(html);
ws.send(msg);
}else{
alert(res.msg);
}
}
})
});
</script>
</html>
bind.php(处理发送消息记录数据库操作、登录、注册、注销的文件)仅供参考:
注意:文件位置在你的项目地址\application\index\controller\bind.php
<?php
namespace app\index\controller;
use GatewayClient\Gateway;
use think\Db;
class Bind extends Base{
public function index(){
$get = input();
$data = json_decode($get['data']);
// echo json_encode(array('data'=>$data));exit;
//加载GatewayClient。关于GatewayClient参见本页面底部介绍
// require_once '/vendor/workerman/gatewayClient/Gateway.php';
// GatewayClient 3.0.0版本开始要使用命名空间
// 设置GatewayWorker服务的Register服务ip和端口,请根据实际情况改成实际值(ip不能是0.0.0.0)
Gateway::$registerAddress = '127.0.0.4:1236';
// 假设用户已经登录,用户uid和群组id在session中
// $_SESSION['uid']
$uid = $data['user'];
$group_id = $_SESSION['group'];
// client_id与uid绑定
Gateway::bindUid($client_id, $uid);
// 加入某个群组(可调用多次加入多个群组)
Gateway::joinGroup($client_id, $group_id);
}
public function talk(){
$get = input();
// print_r($get);die;
$res = Db::name('talklog')->insert(array(
'uid'=>$get['uid'],
'content'=>$get['content'],
'time'=>$get['time'],
'isread'=>$get['isread'],
'type'=>$get['type'],
'info_type'=>$get['info_type'],
'toid'=>$get['toid'],
'message_type'=>$get['message_type']==''?0:$get['message_type']
));
}
public function register(){
$get = input();
$data['client_ip'] = $_SERVER['REMOTE_ADDR'];
$data['nickname'] = trim($get['nickname']);
$data['time'] = time();
$res = Db::name('talkuser')->insert($data);
if($res){
echo 1;exit;
}
}
public function out(){
unset($_SESSION['user']);
echo 1;exit;
}
public function login(){
$get = input();
$user = Db::name('talkuser')->where('id',$get['id'])->find();
if($user){
$_SESSION['user'] = $user;
echo 1;exit;
}
}
}
index.php,代码如下(仅供测试):
注意:文件位置在你的项目地址\application\index\controller\index.php
<?php
namespace app\index\controller;
use think\Controller;
use think\Config;
use think\Db;
use think\cache\driver\Redis;
//use Workerman\Worker;
class Index extends Base
{
public function index()
{
//测试,2021、05、08
//获取会员
$user = Db::name('talkuser')->where('id',$_SESSION['user']['id'])->find();
//获取聊天记录
$list = Db::name('talklog')->order('time asc')->select();
$this->assign('user',$user);
$this->assign('list', $list);
return $this->fetch('/index');
}
}
然后找到start_gateway.php,修改协议及端口(websocket://0.0.0.0:8282):
四、GateWayworker的启动与停止:
在windows系统下直接点击运行 “start_for_win.bat” 文件即可:
注意:如果启动失败,则找你现在php版本的PHP.ini文件找到disabled_function删除proc_open函数
五、windows系统下需要开启telnet(跟着我的截图走):
打开telnet后,在cmd命令行输入telnet 127.0.0.1 8282然后回车:
注意:这两个窗口不能关掉
简易聊天室成品图:
六、数据表设计图: