【原创】node+express+socket搭建一个实时推送应用
技术背景
Web领域的实时推送技术,也被称作Realtime技术。这种技术要达到的目的是让用户不需要刷新浏览器就可以获得实时更新。
应用场景:
- 监控系统:后台硬件热插拔、LED、温度、电压发生变化
- 即时通信系统:其它用户登录、发送信息
- 即时报价系统:后台数据库内容发生变化
技术实现方案:ajax long polling(ajax长轮询),comet(http长连接)、socket
这里有篇文章介绍了这几种技术,可以看一下。
http://www.ibm.com/developerworks/cn/web/wa-lo-comet/
http://jingyan.baidu.com/article/08b6a591e07ecc14a80922f1.html
websocket简介
HTTP是一种基于消息(message)的请求(request )/应答(response)协议。当我们在网页中点击一条链接(或者提交一个表单)的时候,浏览器给服务器发一个request message,然后服务器算啊算,答复一条response message。主动发起TCP连接的是client,接受TCP连接的是server。HTTP消息只有两种:request和response。client只能发送request message,server只能发送response message。一问一答,因此按HTTP协议本身的设计,服务器不能主动的把消息推给客户端。
因此,如果让服务器端也可以主动发送信息到客户端,就可以很大程度改进这些不足。WebSocket就是一个实现这种双向通信的新协议。
WebSocket是基于HTTP的功能追加协议
WebSocket最初由html5提出,但现在已经发展为一个独立的协议标准。WebSocket可以分为协议( Protocol )和 API 两部分,分别由 IETF 和W3C制定了标准。
先来看看WebSocket协议的建立过程。
为了实现WebSocket通信,首先需要客户端发起一次普通HTTP请求(也就是说,WebSocket的建立是依赖HTTP的)。请求报文可能像这样:
GET ws://websocket.example.com/ HTTP/1.1
Host: websocket.example.com
Upgrade: websocket
Connection: Upgrade
Origin: http://example.com
Sec-WebSocket-Key:pAloKxsGSHtpIHrJdWLvzQ==
Sec-WebSocket-Version:13
其中HTTP头部字段 Upgrade: websocket 和 Connection: Upgrade 很重要,告诉服务器通信协议将发生改变,转为WebSocket协议。支持WebSocket的服务器端在确认以上请求后,应返回状态码为 101 Switching Protocols 的响应:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: nRu4KAPUPjjWYrnzxDVeqOxCvlM=
其中字段 Sec-WebSocket-Accept 是由服务器对前面客户端发送的 Sec-WebSocket-Key 进行确认和加密后的结果,相当于一次验证,以帮助客户端确信对方是真实可用的WebSocket服务器。
验证通过后,这个握手响应就确立了WebSocket连接,此后,服务器端就可以主动发信息给客户端了。此时的状态比较像服务器端和客户端接通了电话,无论是谁有什么信息想告诉对方,开口就好了。
一旦建立了WebSocket连接,此后的通信就不再使用HTTP了,改为使用WebSocket独立的数据帧
整个过程像这样:

开始码砖
1.建立项目文件,安装node 和express框架
socket.io http://socket.io/docs/
服务器端安装socket.io
$ npm install socket.io客户端下载socket.io.js

client是客户端文件 server是服务器端文件。
2.写界面

我的界面是这样的
html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>聊天室</title>
<link rel="stylesheet" type="text/css" href="css/index.css"/>
</head>
<body>
<div class="main">
<div class="main-top">
socket.io demo
</div>
<div class="main-body">
<section class="chatRoomInfo">
<div class="info">当前共有<span class="chatNum">0</span>人在线。在线列表: <span class="chatList"></span></div>
</section>
<!--<section class="chatRoomTip">
<div>子木加入到聊天室</div>
</section>
<section class="user clearfix">
<span>子木</span>
<div>
测试测试测试测试测试测试测试测试测试试测试测试测试测试测试测试测试测试测试测试测试
</div>
</section>
<section class="server clearfix">
<span>子木</span>
<div>
测试测试测试
</div>
</section>-->
</div>
<div class="main-footer clearfix">
<div class="input">
<input type="text" name="msg" id="msg" value="" />
</div>
<button type="button" class="send">发送</button>
</div>
</div>
<script src="js/jquery-2.1.0.js" type="text/javascript" charset="utf-8"></script>
<script src="js/socket.io.js" type="text/javascript" charset="utf-8"></script>
<script>
//do something</script>
</body>
</html>
css
* {
margin: 0;
padding: 0;
}
.clearfix {
zoom: 1;
}
.clearfix:after {
clear: both;
content: '.';
display: block;
width: 0;
height: 0;
visibility: hidden;
}
.main {
width: 100%;
height: 100%;
font-size: 14px;
}
.main-top {
height: 30px;
background-color: #3d3d3d;
text-indent: 15px;
color: #ffffff;
font-size: 16px;
line-height: 30px;
}
.main-body {
background-color: #efeff4;
position: absolute;
top: 30px;
bottom: 50px;
width: 100%;
overflow-y: scroll;
scrollbar-3dlight-color: ;
}
.chatRoomInfo {
padding: 10px;
font-size: 12px;
color: #666;
}
.chatRoomTip {
text-align: center;
padding: 10px;
font-size: 12px;
color: #444;
}
.user {
width: 100%;
min-height: 38px;
min-width: 36px;
margin-bottom: 15px;
}
.user span {
float: right;
}
.user div {
float: right;
min-height: 38px;
min-width: 38px;
max-width: 70%;
line-height: 38px;
padding: 0 15px;
color: #FFFFFF;
margin-right: 10px;
word-break: break-all;
background-color: #007aff;
position: relative;
border-radius: 5px;
}
.user div:after {
content: "";
position: absolute;
right: -5px;
top: 4px;
width: 0;
height: 0;
border-top: solid transparent;
border-left: 7px solid #007aff;
border-bottom: 4px solid transparent;
}
.server {
width: 100%;
min-height: 38px;
min-width: 36px;
margin-bottom: 15px;
}
.server span {
float: left;
}
.server div {
float: left;
min-height: 38px;
min-width: 38px;
max-width: 70%;
line-height: 38px;
padding: 0 15px;
color: #FFFFFF;
margin-left: 10px;
word-break: break-all;
background-color: #007aff;
position: relative;
border-radius: 5px;
}
.server div:after {
content: "";
position: absolute;
left: -5px;
top: 4px;
width: 0;
height: 0;
border-top: solid transparent;
border-right: 7px solid #007aff;
border-bottom: 4px solid transparent;
}
.main-footer{
position: absolute;
bottom: 0;
width: 100%;
height: 50px;
}
.input{
float: left;
width: 80%;
height: 40px;
margin-top: 5px;
margin-left: 1%;
margin-right: 1%;
border: 1px solid #666666;
}
.input input{
width: 100%;
height: 40px;
outline: none;
border: none;
font-size: 14px;
color: #333;
}
.send{
float: left;
width: 16%;
height: 40px;
margin-top: 5px;
margin-left: 1%;
border: none;
background-color: #e8e8e8;
color: #007aff;
outline: none;
}
现在开始写逻辑
客户端代码实现
/*按钮点击效果*/
$('.send').mousedown(function(){
$(this).css({'background':"#007aff",'color':"#ffffff"});
})
$('.send').mouseup(function(){
$(this).css({'background':"#e8e8e8",'color':"#ffffff"});
})
/*socket*/
window.onload=function () {
var username=prompt('请输入您的姓名');
if (!username){
alert('姓名必填');
history.go(0);
}
// username="子木";
userId=genUid();
var userInfo={
'userid':userId,
'username':username
};
//连接socket后端服务器
var socket=io.connect("ws://127.0.0.1:4000");
//通知用户有用户登录
socket.emit('login',userInfo);
//监听新用户登录
socket.on('login',function (o) {
updateMsg(o, 'login');
});
//监听用户退出
socket.on('logout',function (o) {
updateMsg(o, 'logout');
});
//发送消息
socket.on('message',function (obj) {
if(obj.userid==userId) {
var MsgHtml='<section class="user clearfix">'
+'<span>'+obj.username+'</span>'
+'<div>'+obj.content+'</div>'
+'</section>';
}else{
var MsgHtml='<section class="server clearfix">'
+'<span>'+obj.username+'</span>'
+'<div>'+obj.content+'</div>'
+'</section>';
}
$('.main-body').append(MsgHtml);
$('.main-body').scrollTop(99999);
})
$('.send').click(function () {
var content=$('input[name="msg"]').val();
if (content){
var obj={
'userid':userId,
'username':username,
'content':content
}
socket.emit('message',obj);
$('input[name="msg"]').val("");
}
}) } /*用户id生成*/
function genUid() {
return new Date().getTime()+""+Math.floor(Math.random()*899+100);
}
function logout(){
socket.disconnect();
location.reload();
}
/*监听函数*/
function updateMsg(o,action) {
//当前在线列表
var onlineUser=o.onlineUser;
//当前在线数
var onlineCount=o.onlineCount;
//新加用户
var user=o.user;
//更新在线人数
var userList='';
var separator = '';
for(key in onlineUser){
userList+=separator+onlineUser[key];
separator = '、';
}
//跟新房间信息
$('.chatNum').text(onlineCount);
$('.chatList').text(userList);
//系统消息
if(action=='login'){
var sysHtml='<section class="chatRoomTip"><div>'+user.username+'进入聊天室</div></section>';
}
if(action=="logout"){
var sysHtml='<section class="chatRoomTip"><div>'+user.username+'退出聊天室</div></section>';
}
$(".main-body").append(sysHtml);
$('.main-body').scrollTop(99999);
}
服务器代码实现 app.js
var app = require('express')();
var http=require('http').Server(app);
var io=require('socket.io')(http);
app.get('/socket/client/index.html',function (req,res) {
res.send('<h1>welcome</h1>');
})
//在线用户
var onlineUser={};
var onlineCount=0;
io.on('connection',function (socket) {
console.log('新用户登录');
//监听新用户加入
socket.on('login',function (obj) {
socket.name=obj.userid;
//检查用户在线列表
if(!onlineUser.hasOwnProperty(obj.userid)){
onlineUser[obj.userid]=obj.username;
//在线人数+1
onlineCount++;
}
//广播消息
io.emit('login',{onlineUser:onlineUser,onlineCount:onlineCount,user:obj});
console.log(obj.username+"加入了聊天室");
})
//监听用户退出
socket.on('disconnect',function () {
//将退出用户在在线列表删除
if(onlineUser.hasOwnProperty(socket.name)){
//退出用户信息
var obj={userid:socket.name, username:onlineUser[socket.name]};
//删除
delete onlineUser[socket.name];
//在线人数-1
onlineCount--;
//广播消息
io.emit('logout',{onlineUser:onlineUser,onlineCount:onlineCount,user:obj});
console.log(obj.username+"退出了聊天室");
}
})
//监听用户发布聊天内容
socket.on('message', function(obj){
//向所有客户端广播发布的消息
io.emit('message', obj);
console.log(obj.username+'说:'+obj.content);
});
})
http.listen(4000, function(){
console.log('listening on *:4000');
});
代码全部贴上来
源码地址:https://github.com/zimuqi/socketChat
下载后安装好socket.io express后进入到server 目录下 直接node app.js。然后打开项目主页就可以看到效了。可以多打开几个窗口互动一下。
有兴趣的可以再去研究一下WebIM系统,实现类似微信,qq的功能,客户端可以看到好友在线状态,在线列表,添加好友,删除好友,新建群组等,消息的发送除了支持基本的文字外,还能支持表情、图片和文件。
【原创】node+express+socket搭建一个实时推送应用的更多相关文章
- Spring MVC 实现web Socket向前端实时推送数据
最近项目中用到了webSocket服务,由后台实时向所有的前端推送消息,前端暂时是不可以发消息给后端的,数据的来源是由具体的设备数据收集器收集起来,然后通过socket推送给后端,后端收到数据后,再将 ...
- node+express+jade搭建一个简单的"网站"
1.建立工程文件夹:my_jade 2.下载express和jade包到本地.我个人不喜欢下载成全局的,我喜欢下到工程文件夹中去. 3.建立相关的文件夹和文件. index.js: style.css ...
- node+express+ejs搭建一个简单的"页面"
1.建立工程文件夹my_ejs. 2.首先利用npm install express和npm install ejs 下载这两个家伙.至于要不要设置成全局的,看习惯,我习惯性的下载到本项目中的文件夹中 ...
- Socket IO Web实时推送
1服务器pom.xml引入 <!-- 服务端 --> <dependency> <groupId>com.corundumstudio.socketio</g ...
- dwr3+spring实现消息实时推送
最近项目要实现一个消息推送的功能,主要就是发送站内信或者系统主动推送消息给当前在线的用户.每次的消息内容保存数据库,方便用户下次登录后也能看到.如果当前用户在线,收到站内信就主动弹出提示.一开始想到的 ...
- node+express+socket.io+mysql=通讯服务器搭建(一)
首发github/blog 欢迎大家评论给星 安装 首先假定你已经安装了 Node.js,接下来为你的应用创建一个目录,然后安装express-generator应用骨架 $ mkdir node-d ...
- springboot搭建一个简单的websocket的实时推送应用
说一下实用springboot搭建一个简单的websocket 的实时推送应用 websocket是什么 WebSocket是一种在单个TCP连接上进行全双工通信的协议 我们以前用的http协议只能单 ...
- 利用socket.io实现消息实时推送
最近在写的项目中存在着社交模块,需要实现这样的一个功能:当发生了用户被点赞.评论.关注等操作时,需要由服务器向用户实时地推送一条消息.最终完成的项目地址为:socket-message-push,这里 ...
- 基于Node.js的实时推送 juggernaut
基于Node.js的实时推送 juggernaut Juggernaut 基于 Node.js 构建.为浏览器和服务器端提供一个实时的连接,可在客户端和服务器端进行数据的实时推送,适合多角色游戏.聊天 ...
随机推荐
- 理解javascript里的ABC--apply bind call
一,三者共同点 js中的apply,call,bind是对于初学者比较难的概念之一,比如说我..参考几篇文章之后,统一来讲, 1.这三个函数都属于Function.prototype下面的方法,如下图 ...
- IDEA快捷键
[常规] Ctrl+Shift + Enter,语句完成 "!",否定完成,输入表达式时按 "!"键 Ctrl+E,最近的文件 Ctrl+Shift+E,最近更 ...
- SVO原理解析
最近空闲时间在研究Semi-Direct Monocular Visual Odometry(SVO)[1,2],觉得它值得写一写.另外,SVO的运算量相对较小,我想在手机上尝试实现它. 关于SVO的 ...
- Spring test
@Rollback 用于标记在spring test中是否提交事务, 默认为true, 即不提交, 如果需要设置单元测试完成时自动提交事务, 需要设置rollback为false; 可以使用 @Com ...
- Java常见Exception整理
前言: 技术开发入坑近1年,摸打滚爬,各种升级打怪.因目前从事Java相关,故整理了一下并把常见的异常(Exception)贴出来,一来为了后续提醒自己,二来供即将入坑的朋友打一下预防针!A级(代码逻 ...
- [转]别再抱怨了,国内这么多优秀的Android资源你都知道吗?
因为一些大家都知道的原因,android很多官方出品的优秀开发资源在国内无法访问. 国内的同行们对此也做出了很多努力,有很多朋友通过各种手段把很多优秀的资源搬运到了国内,为国内android开发者提供 ...
- pandas read table
http://pandas.pydata.org/pandas-docs/stable/10min.html import pandas as pd res = pd.read_table(" ...
- mysql定时任务
查看event是否开启: show variables like '%sche%'; 将事件计划开启: set global event_scheduler=1; 关闭事件任务: alter even ...
- ThinkphpCMF笔记
1.模板js,css文件__PUBLIC__ <link href="__TMPL__Public/style.css" rel="stylesheet" ...
- [实战]MVC5+EF6+MySql企业网盘实战(29)——更新日志
摘要 NetDisk更新日志,及项目使用说明. 开发工具 Vs2013+mysql+ef6+mvc5 bug 1.在加载列表的时候,默认加载的所有,修改为,过滤逻辑删除的文件. 2.加载音乐,文档等分 ...