技术背景
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 构建.为浏览器和服务器端提供一个实时的连接,可在客户端和服务器端进行数据的实时推送,适合多角色游戏.聊天 ...
随机推荐
-
使用bat/vbs/ahk对Windows下进行自动化操作
回想90年代,我们在DOS下使用各种命令链对操作进行简化和自动化,如DOS 5.0添加的DosKey,利用管道和重定向对多组命令进行链式操作.后来使用了Ubuntu和其它Linux发型版后,bash下 ...
-
delphi 开发者 linux 实务(转)
Linux Essentials for Delphi Developers There is currently no way using Delphi to target Linux. Lon ...
-
百度地图api 常用demo
功能一:获取map地图窗口的可视区域: var map = new BMap.Map("allmap"); // 创建Map实例 map.centerAndZ ...
-
Delphi线程简介---Create及其参数、Resume、Suspend
TThread在Classes单元里的声明如下 type TThread = class private FHandle: THandle; FThreadID: THandle; FTerminat ...
-
展讯NAND Flash高级教程【转】
转自:http://wenku.baidu.com/view/d236e6727fd5360cba1adb9e.html 展讯NAND Flash高级教程
-
spring + mybatis 注解式事务不回滚的原因分析 @Transactional
在一个项目中发现spring的事务无法回滚. DEBUG: org.mybatis.spring.SqlSessionUtils - SqlSession [org.apache.ibatis.ses ...
-
Oracle---->;基本DDL
修改表名: rename table_name1 to table_name2; delete [from] persons where lastname= 'Wilson';
-
Android用户界面UI组件--AdapterView及其子类(四) GridView
GridView常用的XML属性: android:columnWidth 设置列的宽度. android:horizontalSpacing 两列之间的间距. android:numColum ...
-
php 控制页面跳转
<?php class UserAction extends Action{ public function index(){ echo "你好!"; $m=M('user' ...
-
ALTER TABLE causes auto_increment resequencing, resulting in duplicate entry ’1′ for key ‘PRIMARY’
在打开navicat设计表时,想更改主键id为自动增长,会弹出来这么一个提示.翻译为:更改表将导致自动增长(列)的重新排序,主键会有重复的‘1’.原因是因为auto_increment是从1开始自增的 ...