Skip to content

Instantly share code, notes, and snippets.

@masdude
Created April 11, 2014 14:18
Show Gist options
  • Save masdude/10472566 to your computer and use it in GitHub Desktop.
Save masdude/10472566 to your computer and use it in GitHub Desktop.

SAE 初体验

一直在用SAE做一些个人的小型项目,印象比较深刻的,还算成功的有两个:

一个是某品牌的现场活动,通过摄像头拍摄4张不同动作的照片,然后合成一张gif动画照片,合成完毕后会获得一个序列号,只要在微信上关注活动号,并发送序列号就能拿到gif动画,程序还自动发送微博到官方号上。利用SAE最大的好处就是其中很多组件节省了很多时间,例如存储、图片处理、任务系统等等。因为活动是阶段性的,所以活动结束后,站点就可以关闭,不像以前服务器都是一年起租。

另一个是朋友店里的微信托管,朋友的店是具有三家分店的汗蒸店,本来微信上用户就不少,之前都是用来发送一些品牌故事、优惠活动等消息。后来朋友想在微信上实现预约,由于订阅号的限制还是很大,所以申请为服务号,通过HTML5等实现了在线预约,现在刚刚开始使用,每天预约量还不大,相信经过一定的推广,会有越来越多的人使用。用户在在线预约后,如果条件不是特别特殊,会自动回复用户确认预定,如果条件比较特殊,会通过微信将消息直接发送给各分店店长,店长回复确认与否。

所以说,SAE还是大大简化的开发的流程,使得不用关心很多底层服务,把更多的心思放在了程序逻辑本身。更何况,费用上还是很低廉的。

Channel 初体验

13年12月初,SAE推出了Channel服务,有幸申请到了测试权限。一时没想到应用场景,但是想先了解下使用的方法,以备后面的项目需要使用,可以节省时间,快速开发。

Channel是SAE提供的实时消息推送服务。通过在浏览器和SAE服务端之间建立长连接,使得应用可以方便的向Javascript客户端实时的推送消息。开发者可以利用Channel服务开发自己的实时性要求比较高的应用,如游戏、在线聊天室、在线直播等。

既然官方都提到了聊天室,那就做个聊天室的DEMO吧。 http://dpchat.sinaapp.com/chat

大概只花了30分钟,就写出了最简单的demo,但是打开另外一个浏览器,进入聊天室,发现消息只能发给最后一个进入聊天室的人,文档上也没找到所以然,最后问了小虎,技术答复:“一个channel只能有一个client连接,多个client请创建多个channel。” 好吧,理解出偏差了。

其实这也不难理解,聊天室是需要群发的,但是如果是游戏等其他场景,不用不到全发了。不过还是希望SAE能够让大家自己创建单发通道和群发通道,因为自己要去维护一个群发的列表,还是有点麻烦的。

然后DEMO又耽误了2天,原因是 <script src='http://channel.sinaapp.com/api.js'></script> 总抛502,拿不到JS文件,后来发现直接用WebSocket也行。

好了,废话不多说了,看看code。

chat.php (聊天室用户部分)

<?php
$uid = $_COOKIE['uid'];
if (empty($uid))
{
  $uid = mt_rand();
  setcookie('uid', $uid, time()+3000, '/', 'dpchat.sinaapp.com', FALSE, TRUE);
}

$kv = new SaeKV();
$kv->init();
$connection = $kv->get('Chat.'.$uid);
if (empty($connection))
{
  $channel = new SaeChannel();
  $connection = $channel->createChannel('Chat.'.$uid, 3600);
  $kv->set('Chat.'.$uid, $connection);
}
?>

先获取用来标识用户的Cookie,如果空则重新生成一个,然后从KVDB中获取到此用户的Socket连接(此处可以优化使用MC)。 剩下的就是聊天室本身的HTML代码和JS代码了。基本流程:

  • 获取或申请Channel连接,并存储以备刷新后重复使用
  • 建立WebSocket连接
  • ajax发送enter事件给app,告知用户进入聊天室,app群发消息给所有人
  • 用户发送消息给app,app群发给所有人
  • 用户退出时,ajax发送exit事件给app,告知用户退出聊天室,app群发消息给所有人

其中的事件和消息发送,我没有用websockt本身的事件,而是自己用了ajax来转发到app,这样的好处主要是可控,以及更多的自定义参数。

ping.php(聊天室app,转发程序)

这部分程序其实更简单了,只是通过cookie,获取到当前用户的标识。然后自己维护一个客户列表,事件或消息进来的时候,循环群发即可。

代码就不大段大段的贴了,大家自己看吧

https://gist.github.com/alcoholwang/8216375

意见和建议

  1. 文档还不够全面,进入Channel的服务项目,只有3中语言的Class Reference链接,其中关键性的一些内容还要通过其他途径进入了解。特别是单发和广播的那个说明,发现很多人都误解为广播模式;
  2. 最好能帮助用户维护客户连接列表,然后可以通过不同的方法进行单发或广播,这样满足不同应用场景的使用;
  3. 这么好的功能,赶紧公开使用吧!!
<?php
$uid = $_COOKIE['uid'];
if (empty($uid))
{
$uid = mt_rand();
setcookie('uid', $uid, time()+3000, '/', 'dpchat.sinaapp.com', FALSE, TRUE);
}
$kv = new SaeKV();
$kv->init();
$connection = $kv->get('Chat.'.$uid);
if (empty($connection))
{
$channel = new SaeChannel();
$connection = $channel->createChannel('Chat.'.$uid, 3600);
$kv->set('Chat.'.$uid, $connection);
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>聊天室</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="author" content="alcoholwang">
<link rel="stylesheet" href="/assets/css/bootstrap.min.css" type="text/css" />
<style>
#messages {height:100%;overflow-y:scroll;padding:0;}
#messages li {margin:6px 0;padding:0;list-style:none;}
#messages li span.label {cursor:pointer;}
</style>
<!--[if lt IE 9]>
<script src="/assets/js/html5shiv.min.js" type="text/javascript"></script>
<script src="/assets/js/respond.min.js" type="text/javascript"></script>
<![endif]-->
<script src="/assets/js/jquery.min.js" type="text/javascript"></script>
<script src="/assets/js/bootstrap.min.js" type="text/javascript"></script>
<script src='http://channel.sinaapp.com/api.js'></script>
</head>
<body>
<div class="container">
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">聊天内容</h3>
</div>
<div class="panel-body">
<ul id="messages"></ul>
</div>
<div class="panel-footer clearfix">
<div class="col-sm-11">
<input type="text" class="form-control" id="message" placeholder="聊天内容" />
</div>
<div class="col-sm-1">
<button type="button" class="btn btn-success" id="messageSure">发送</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="nicknameModal" tabindex="-1" role="dialog" data-backdrop="static" data-keyboard="false" data-show="show">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">你的昵称</h4>
</div>
<div class="modal-body">
<input type="text" class="form-control" id="nickname" placeholder="昵称" />
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" id="nicknameSure">确定</button>
</div>
</div>
</div>
</div>
<script>
jQuery(document).ready(function($) {
$('.panel-body').outerHeight($(document).innerHeight() - $('.panel-heading').outerHeight() - $('.panel-footer').outerHeight() - 150);
var $channel = {'connection':'<?=$connection?>', 'nickname':'', 'socket':null};
$channel.sendMessage = function(data) {
$.post('/ping', data);
$channel.socket.send(data);
console.log(data, $channel.socket);
};
$channel.createMessage = function(className, nickname, msg) {
label = $('<span class="label">').addClass('label-' + className).text(nickname).click(function(e){
e.preventDefault();
msg = '@' + nickname + ' ' + $('#message').val();
$('#message').val(msg).focus();
});
return $('<li>').html(msg).prepend(label);
};
$channel.onOpen = function() {
$channel.sendMessage({'nickname':$channel.nickname, 'event':'enter'});
$('#message').focus();
};
$channel.onError = function(msg) {
console.log(this, msg);
var li = $('<li>').html('<span class="label label-danger">'+msg+'</span>');
$('#messages').append(li);
};
$channel.onClose = function() {
$channel.sendMessage({'nickname':$channel.nickname, 'event':'exit'});
$('#message').focus();
};
$channel.onMessage = function(msg) {
if (msg && msg.data)
{
msg = $.parseJSON(msg.data);
if (msg && msg.nickname && msg.message)
{
var li = $channel.createMessage('info', msg.nickname, ' 说: ' + msg.message);
$('#messages').append(li);
}
else if (msg.event)
{
var li = $channel.createMessage((msg.event == 'exit'?'danger':'success'), msg.nickname, ' '+(msg.event == 'exit'?'退出':'进入')+'聊天室。');
$('#messages').append(li);
}
}
$('#message').focus();
};
var requestNickname = function() {
$('.panel-title').html($channel.connection);
$('#nicknameSure').click(function(e){
e.preventDefault();
var nickname = $.trim($('#nickname').val());
if (nickname == '') {
$('#nickname').focus();
return;
}
$('#nicknameModal').modal('hide');
$channel.nickname = nickname;
$channel.socket = sae.Channel($channel.connection);
$channel.socket.onopen = $channel.onOpen;
$channel.socket.onmessage = $channel.onMessage;
$channel.socket.onclose = $channel.onClose;
$channel.socket.onerror = $channel.onError;
});
$('#nicknameModal').modal('show').on('shown.bs.modal', function(){ $('#nickname').focus(); });
$('#messageSure').click(function(e){
e.preventDefault();
var message = $.trim($('#message').val());
if (message == '') {
$('#message').focus();
return;
}
$channel.sendMessage({'nickname':$channel.nickname, 'message':message});
$('#message').val('');
});
$('#message').keyup(function(e) {
var code = e.which;
if(code==13) {
e.preventDefault();
$('#messageSure').trigger('click');
}
});
$(window).bind('beforeunload', function(){
$channel.socket.close();
return '你确定要离开聊天室吗?';
});
}();
});
</script>
</body>
</html>
<?php
header('Content-Type: application/json');
$uid = $_COOKIE['uid'];
if (empty($uid))
{
echo 'is not chat user';
exit();
}
$kv = new SaeKV();
$kv->init();
$connections = $kv->get('Chating');
if (empty($connections)) $connections = array();
$nickname = post('nickname');
if (empty($nickname)) $nickname = '火星人'.$_SERVER["REMOTE_ADDR"];
$event = post('event');
$message = post('message');
$result = array('nickname'=>$nickname);
if (!empty($message))
{
$result['message'] = $message;
}
else if (!empty($event))
{
$result['event'] = $event;
switch ($event) {
case 'enter':
$connections[$uid] = time();
break;
default:
unset($connections[$uid]);
break;
}
}
$result = json_encode($result);
$channel = new SaeChannel();
foreach ($connections as $uid => $time) {
$chat = 'Chat.'.$uid;
$send = $channel->sendMessage($chat, $result);
if ($send !== TRUE) unset($connections[$uid]);
echo '//', $chat, '--', (($send === TRUE)?'.':'!'), PHP_EOL;
}
$kv->set('Chating', $connections);
echo $result;
function post($para)
{
if (empty($para)) return NULL;
$data = isset($_POST[$para])?trim($_POST[$para]):NULL;
return empty($data)?NULL:htmlspecialchars($data);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment