技术背景

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>人在线。在线列表:&nbsp;<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搭建一个实时推送应用的更多相关文章

  1. Spring MVC 实现web Socket向前端实时推送数据

    最近项目中用到了webSocket服务,由后台实时向所有的前端推送消息,前端暂时是不可以发消息给后端的,数据的来源是由具体的设备数据收集器收集起来,然后通过socket推送给后端,后端收到数据后,再将 ...

  2. node+express+jade搭建一个简单的"网站"

    1.建立工程文件夹:my_jade 2.下载express和jade包到本地.我个人不喜欢下载成全局的,我喜欢下到工程文件夹中去. 3.建立相关的文件夹和文件. index.js: style.css ...

  3. node+express+ejs搭建一个简单的"页面"

    1.建立工程文件夹my_ejs. 2.首先利用npm install express和npm install ejs 下载这两个家伙.至于要不要设置成全局的,看习惯,我习惯性的下载到本项目中的文件夹中 ...

  4. Socket IO Web实时推送

    1服务器pom.xml引入 <!-- 服务端 --> <dependency> <groupId>com.corundumstudio.socketio</g ...

  5. dwr3+spring实现消息实时推送

    最近项目要实现一个消息推送的功能,主要就是发送站内信或者系统主动推送消息给当前在线的用户.每次的消息内容保存数据库,方便用户下次登录后也能看到.如果当前用户在线,收到站内信就主动弹出提示.一开始想到的 ...

  6. node+express+socket.io+mysql=通讯服务器搭建(一)

    首发github/blog 欢迎大家评论给星 安装 首先假定你已经安装了 Node.js,接下来为你的应用创建一个目录,然后安装express-generator应用骨架 $ mkdir node-d ...

  7. springboot搭建一个简单的websocket的实时推送应用

    说一下实用springboot搭建一个简单的websocket 的实时推送应用 websocket是什么 WebSocket是一种在单个TCP连接上进行全双工通信的协议 我们以前用的http协议只能单 ...

  8. 利用socket.io实现消息实时推送

    最近在写的项目中存在着社交模块,需要实现这样的一个功能:当发生了用户被点赞.评论.关注等操作时,需要由服务器向用户实时地推送一条消息.最终完成的项目地址为:socket-message-push,这里 ...

  9. 基于Node.js的实时推送 juggernaut

    基于Node.js的实时推送 juggernaut Juggernaut 基于 Node.js 构建.为浏览器和服务器端提供一个实时的连接,可在客户端和服务器端进行数据的实时推送,适合多角色游戏.聊天 ...

随机推荐

  1. ABP 索引

    官方网站 Github ABP集合贴 @ kebinet https://www.codeproject.com/articles/1115763/using-asp-net-core-entity- ...

  2. SQLSERVER单表CRUD通用方法

    一.适用场景 ①当你书写简单的增删改查心累了 ②当你的项目不考虑并发.高性能 ③当你追求更快速的开发效率 ④当你的业务只涉及单表 二.代码展示 ①单表Insert public bool Insert ...

  3. AutoMapper简单用法

    首先在NuGet添加AutoMapper /// <summary> /// AutoMapper帮助类 /// </summary> public static class ...

  4. Javascript获取div真实高度

    第一种情况就是宽高都写在样式表里,就比如#div1{width:120px;}.这中情况通过#div1.style.width拿不到宽度,而通过#div1.offsetWidth才可以获取到宽度. 第 ...

  5. Python为8bit深度图像应用color map

    图片中存在着色版的概念,二维矩阵的每个元素的值指定了一种颜色,因此可以显示出彩色. 迁移调色板 下述python代码将VOC数据集中的某个语义分割的图片的调色板直接应用在一个二维矩阵代表的图像上 #l ...

  6. javascript操作字符串的方法

    string.indexOf()//返回字符串中第一个与给定子串匹配的子串序号字符串的IndexOf()方法搜索在该字符串上是否出现了作为参数传递的字符串,如果找到字符串,则返回字符的起始位置 (0表 ...

  7. Nginx在线服务状态下平滑升级或新增模块的详细操作

    今天应开发的需求,需要在Nginx增加一个模块,并不能影响现有的业务,所以就必须要平滑升级Nginx,好了,不多说了 1:查看现有的nginx编译参数 /usr/local/nginx/sbin/ng ...

  8. sqlserver多文件组数据库的备份和还原实战

    数据库文件过大时就要进行数据分区,就是讲数据库拆分到多个文件组中.已方便数据文件管理,提高数据库的读取效能,多文件组如何进行数据库的备份和还原呢,今天主要做多文件组数据库的备份和还原实验. 第一步 创 ...

  9. SVN 图标消失

    问题描述:第一次安装完好,电脑重启后,svn小图标消失不见.查看注册表,发现属于svn小图标的注册表都消失不见. 问题分析:删除杀毒软件(360),因为电脑安装了360,ShellIconOverla ...

  10. 第一章 Part 2/2 Git 一览

    被跟踪文件(Tracked files) 被跟踪文件是 Git 管理的工作目录 (存储库) 中的文件.当你添加新文件或使更新现有文件时,Git都会跟踪这些文件变化.在某个时间点,你将通过命令将这些文件 ...