简介:
首先在WebSocket出现之前,很多网站为了实现实时推送技术,通常采用的方案是轮询技术,让浏览器每隔几秒就发送一次请求。询问是否有新消息,他做不到服务器主动向客户端推送消息。这样不停连接会大量消耗服务器带宽和资源。面对这种状况,HTML5定义了一个新的基于TCP的应用层协议----WebSocket协议,它能更好的节省服务器资源和带宽并实现真正意义上的实时推送。这种网络通信协议的特点就是持久连接,只需要依赖http协议进行一次连接,建立连接后服务器和客户端都能主动向对方发送或接收数据,也就是一种双向通信协议。
属性:
Socket.readyState
只读属性 readyState 表示连接状态,可以是以下值:
0 - 表示连接尚未建立。
1 - 表示连接已建立,可以进行通信。
2 - 表示连接正在进行关闭。
3 - 表示连接已经关闭或者连接不能打开
Socket.bufferedAmount 只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。
事件:
open Socket.onopen 连接建立时触发
message Socket.onmessage 客户端接收服务端数据时触发
error Socket.onerror 通信发生错误时触发
close Socket.onclose 连接关闭时触发
方法:
Socket.send() 使用连接发送数据
Socket.close() 关闭连接
使用场景:
应用的场景是很广泛的,即时聊天、消息推送、及时互动游戏、实时股价、实况报道、直播、基于位置的应用,越来越多的开发者借用移动设备的GPS功能来实现他们基于位置的网络应用。如果你一直记录用户的位置(比如运行应用来记录运动轨迹),你可以收集到更加细致化的数据。当需要实时显示数据的时候用户最期望的是不需要去刷新页面 ,所以用WebSockets是个不错的选择。
然后考虑用什么搭建服务器,对于服务器方面,网上有不同对websocket支持的服务器,例如PHP、nodejs、python、tomcat…下面用nodejs和python搭建服务器练习一下websocket。
接下来要考虑浏览器对websocket的支持 IE10等浏览器以下是不兼容的,所以我们实际的项目中要考虑低版本浏览器的兼容方案:这里我们可以使用Socket.IO,是一个开源的WebSocket库,同时也提供客户端JS库。Socket.IO支持以事件为基础的实时双向通讯,它可以工作在任何平台、浏览器或移动设具有不错的稳定性和性能。对于那些不支持WebSocket的浏览器,会自动降为Ajax连接,最大限度地保证了兼容性。 其实node上有多种websocket模块可以选择,常见的是ws和socketio,(ws一个使用简单,速度极快,稳定的websocket客户端和服务端的Node.js实现。) 考虑到兼容性下面的聊天室用socket.io来做
第一步
安装node 下载地址:https://nodejs.org/en/download/
安装express npm install express
安装socket npm install socket.io
第二步编写的文件package.json,server.js,index.html. 在根目录下建立server.js
server.js代码
var express = require('express'),
app = express(),
server = require('http').createServer(app),
// //引入socket.io模块并绑定到服务器
io = require('socket.io').listen(server),
users = [];//保存所有在线用户昵称
app.use('/', express.static(__dirname + '/www'));
server.listen(process.env.PORT || 8888);
io.sockets.on('connection', function(socket) {
//设置昵称
socket.on('login', function(nickname) {
if (users.indexOf(nickname) > -1) {
socket.emit('nickExisted');
} else {
//socket.userIndex = users.length;
socket.nickname = nickname;
users.push(nickname);
socket.emit('loginSuccess');
// 向所有连接到服务器的客户端发送当前登陆用户的昵称
io.sockets.emit('system', nickname, users.length, 'login');
};
});
//断开连接的事件
socket.on('disconnect', function() {
if (socket.nickname != null) {
将断开连接的用户从users中删除
users.splice(users.indexOf(socket.nickname), 1);
//通知除自己以外的所有人
socket.broadcast.emit('system', socket.nickname, users.length, 'logout');
}
});
//接收新消息
//接收新消息
socket.on('postMsg', function(msg, color) {
//将消息发送到除自己外的所有用户
while (true) {
socket.broadcast.emit('newMsg', socket.nickname, msg, color);
}
});
//接收
socket.on('img', function(imgData, color) {
// 通过一个newImg事件分发到除自己外的每个用户
socket.broadcast.emit('newImg', socket.nickname, imgData, color);
});
});
index.html代码
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="author" content="Wayou">
<meta name="description" content="hichat | a simple chat application built with node.js and websocket">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>hichat</title>
<link rel="stylesheet" href="styles/main.css">
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
<link rel="icon" href="favicon.ico" type="image/x-icon">
</head>
<body>
<div class="wrapper">
<div class="banner">
<h1>HiChat :)</h1>
<span id="status"></span>
</div>
<div id="historyMsg">
</div>
<div class="controls" >
<div class="items">
<input id="colorStyle" type="color" placeHolder='#000' title="font color" />
<input id="emoji" type="button" value="emoji" title="emoji" />
<label for="sendImage" class="imageLable">
<input type="button" value="image" />
<input id="sendImage" type="file" value="image"/>
</label>
<input id="clearBtn" type="button" value="clear" title="clear screen" />
</div>
<textarea id="messageInput" placeHolder="enter to send"></textarea>
<input id="sendBtn" type="button" value="SEND">
<div id="emojiWrapper">
</div>
</div>
</div>
<div id="loginWrapper">
<p id="info">connecting to server...</p>
<div id="nickWrapper">
<input type="text" placeHolder="nickname" id="nicknameInput" />
<input type="button" value="OK" id="loginBtn" />
</div>
</div>
<script src="/socket.io/socket.io.js"></script>
<script src="scripts/hichat.js"></script>
<script>
/**REMOVE ME IF YOU CANT ACCESS GOOGLE SERVICE**/
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-46794744-7', 'hichat.herokuapp.com');
ga('send', 'pageview');
/**REMOVE END**/
</script>
</body>
</html>
hitcat.js代码
window.onload = function() {
//实例化并初始化
var hichat = new HiChat();
hichat.init();
};
//定义一个类
var HiChat = function() {
this.socket = null;
};
//向原型添加方法
HiChat.prototype = {
init: function() {
var that = this;
//建立服务器的socket连接
this.socket = io.connect();
//监听socket的connect事件这个事件表示链接建立
this.socket.on('connect', function() {
//连接服务器后显示昵称输入框
document.getElementById('info').textContent = 'get yourself a nickname :)';
document.getElementById('nickWrapper').style.display = 'block';
document.getElementById('nicknameInput').focus();
});
this.socket.on('nickExisted', function() {
document.getElementById('info').textContent = '!nickname is taken, choose another pls';
});
this.socket.on('loginSuccess', function() {
document.title = 'hichat | ' + document.getElementById('nicknameInput').value;
document.getElementById('loginWrapper').style.display = 'none';
document.getElementById('messageInput').focus();
});
this.socket.on('error', function(err) {
if (document.getElementById('loginWrapper').style.display == 'none') {
document.getElementById('status').textContent = '!fail to connect :(';
} else {
document.getElementById('info').textContent = '!fail to connect :(';
}
});
this.socket.on('system', function(nickName, userCount, type) {
//判断用户是连接还是离开以显示不同的信息
var msg = nickName + (type == 'login' ? ' joined' : ' left');
that._displayNewMsg('system ', msg, 'red');
//将在线人数显示到页面顶部
document.getElementById('status').textContent = userCount + (userCount > 1 ? ' users' : ' user') + ' online';
});
this.socket.on('newMsg', function(user, msg, color) {
that._displayNewMsg(user, msg, color);
});
this.socket.on('newImg', function(user, img, color) {
that._displayImage(user, img, color);
});
//昵称的确认按钮
document.getElementById('loginBtn').addEventListener('click', function() {
var nickName = document.getElementById('nicknameInput').value;
//判断输入框是否为空
if (nickName.trim().length != 0) {
//不为空将昵称发送到服务器
that.socket.emit('login', nickName);
} else {
//为空输入框获取焦点
document.getElementById('nicknameInput').focus();
};
}, false);
document.getElementById('nicknameInput').addEventListener('keyup', function(e) {
if (e.keyCode == 13) {
var nickName = document.getElementById('nicknameInput').value;
if (nickName.trim().length != 0) {
that.socket.emit('login', nickName);
};
};
}, false);
document.getElementById('sendBtn').addEventListener('click', function() {
var messageInput = document.getElementById('messageInput'),
msg = messageInput.value,
color = document.getElementById('colorStyle').value;
messageInput.value = '';
messageInput.focus();
if (msg.trim().length != 0) {
that.socket.emit('postMsg', msg, color);//把消息发送到服务器
that._displayNewMsg('me', msg, color);//把自己的消息显示到自己的窗口中
return;
};
}, false);
document.getElementById('messageInput').addEventListener('keyup', function(e) {
var messageInput = document.getElementById('messageInput'),
msg = messageInput.value,
color = document.getElementById('colorStyle').value;
if (e.keyCode == 13 && msg.trim().length != 0) {
messageInput.value = '';
that.socket.emit('postMsg', msg, color);
that._displayNewMsg('me', msg, color);
};
}, false);
document.getElementById('clearBtn').addEventListener('click', function() {
document.getElementById('historyMsg').innerHTML = '';
}, false);
document.getElementById('sendImage').addEventListener('change', function() {
//检查是否有文件选中
if (this.files.length != 0) {
var file = this.files[0],
//读取文件
reader = new FileReader(),
color = document.getElementById('colorStyle').value;
if (!reader) {
that._displayNewMsg('system', '!your browser doesn\'t support fileReader', 'red');
this.value = '';
return;
};
reader.onload = function(e) {
//读取成功,显示页面并发送到服务器
this.value = '';
that.socket.emit('img', e.target.result, color);
that._displayImage('me', e.target.result, color);
};
reader.readAsDataURL(file);
};
}, false);
this._initialEmoji();
document.getElementById('emoji').addEventListener('click', function(e) {
var emojiwrapper = document.getElementById('emojiWrapper');
emojiwrapper.style.display = 'block';
e.stopPropagation();
}, false);
document.body.addEventListener('click', function(e) {
var emojiwrapper = document.getElementById('emojiWrapper');
if (e.target != emojiwrapper) {
emojiwrapper.style.display = 'none';
};
});
document.getElementById('emojiWrapper').addEventListener('click', function(e) {
var target = e.target;
if (target.nodeName.toLowerCase() == 'img') {
var messageInput = document.getElementById('messageInput');
messageInput.focus();
messageInput.value = messageInput.value + '[emoji:' + target.title + ']';
};
}, false);
},
_initialEmoji: function() {
var emojiContainer = document.getElementById('emojiWrapper'),
docFragment = document.createDocumentFragment();
for (var i = 69; i > 0; i--) {
var emojiItem = document.createElement('img');
emojiItem.src = '../content/emoji/' + i + '.gif';
emojiItem.title = i;
docFragment.appendChild(emojiItem);
};
emojiContainer.appendChild(docFragment);
},
_displayNewMsg: function(user, msg, color) {
var container = document.getElementById('historyMsg'),
msgToDisplay = document.createElement('p'),
date = new Date().toTimeString().substr(0, 8),
//determine whether the msg contains emoji
msg = this._showEmoji(msg);
msgToDisplay.style.color = color || '#000';
msgToDisplay.innerHTML = user + '<span class="timespan">(' + date + '): </span>' + msg;
container.appendChild(msgToDisplay);
container.scrollTop = container.scrollHeight;
},
_displayImage: function(user, imgData, color) {
var container = document.getElementById('historyMsg'),
msgToDisplay = document.createElement('p'),
date = new Date().toTimeString().substr(0, 8);
msgToDisplay.style.color = color || '#000';
msgToDisplay.innerHTML = user + '<span class="timespan">(' + date + '): </span> <br/>' + '<a href="' + imgData + '" target="_blank"><img src="' + imgData + '"/></a>';
container.appendChild(msgToDisplay);
container.scrollTop = container.scrollHeight;
},
_showEmoji: function(msg) {
var match, result = msg,
reg = /\[emoji:\d+\]/g,
emojiIndex,
totalEmojiNum = document.getElementById('emojiWrapper').children.length;
while (match = reg.exec(msg)) {
emojiIndex = match[0].slice(7, -1);
if (emojiIndex > totalEmojiNum) {
result = result.replace(match[0], '[X]');
} else {
result = result.replace(match[0], '<img class="emoji" src="../content/emoji/' + emojiIndex + '.gif" />');//todo:fix this in chrome it will cause a new request for the image
};
};
return result;
}
};
最后用node server启动服务器
打开浏览器访问localhost
小结:不管是服务器还是客户端,socket.io提供两个核心方法:emit方法用于发送消息,on方法用于监听对方发送的消息。
ws实例:
安装 pywebsocket
在执行以上程序前,我们同样需要创建一个支持 WebSocket? 的服务。使用 git 命令下载:
git clone https://github.com/google/pywebsocket.git[[BR]]
mod_pywebsocket 需要 python 环境支持
mod_pywebsocket 是一个 Apache HTTP 的 Web Socket扩展,
安装步骤如下:
解压下载的文件。
进入 pywebsocket 目录。
执行命令:
$ python setup.py build
$ sudo python setup.py install
查看文档说明:
$ pydoc mod_pywebsocket
开启服务
在 pywebsocket/mod_pywebsocket 目录下执行以下命令:
$ sudo python standalone.py -p 9998 -w …/example/
打开浏览器即可访问 我们创建的 websocket.html 文件
websocket.html代码
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script type="text/javascript">
function WebSocketTest()
{
if ("WebSocket" in window)
{
alert("您的浏览器支持 WebSocket!");
// 打开一个 web socket
var ws = new WebSocket("ws://localhost:9998/echo");
ws.onopen = function()
{
alert("连接已打开")
// Web Socket 已连接上,使用 send() 方法发送数据
ws.send("nagainnagain");
};
ws.onmessage = function (evt)
{
alert('接收的信息是:'+evt.data);
// ws.close();
};
ws.onclose = function()
{
// 关闭 websocket
alert("连接已关闭...");
};
}
else
{
// 浏览器不支持 WebSocket
alert("您的浏览器不支持 WebSocket!");
}
}
</script>
</head>
<body>
总结: Websocket协议处理三件事:建立连接和断开连接、发送数据和接收数据、处理错误