完全是扯淡,大部分的例子都太老了。
当学习了一点点后端知识之后,才发现自己是多么的无能,http和tcp/ip自己对其底层完全不清楚,如果仅仅是知道大概,后续的学习也会造成影响。目前node是有提供对应的API,所以自己还是继续学习node,后面有时间一定要好好学习这里的知识。
中间件
简单的理解就是需要公共处理的方法。比如说,对于http请求而言。当后端接受到请求之后。会接到一个request对象,这个对象里的url参数可能是一段string。但是对于业务而言,业务只想配置一个路由规则。符合这个路由规则就进入某一个特定的方法。而中间件扮演的角色就是,业务告诉中间件一堆规则和规则对应的方法,业务就不再关心了,中间件接受到请求,处理url,然后匹配规则,一旦命中就执行规则对应的方法。
尝试来写一个例子:达到的要求是请求超时,记录日志,这里涉及到一个connect模块。这个项目分为三个文件。
package.json:
{
"name": "request-time",
"version": "0.0.1",
"dependencies": {
"connect": "^3.4.0"
}
}
request-time.js:
module.exports = function(opts){
var time = opts.time || 1000;
return function (req, res, next){
var timer = setTimeout(function(){
console.log('\033[91m 请求超时 \033[39m \n', req.method, req.url);
}, time);
var end = res.end;
res.end = function(chunk, enconding){
res.end = end;
res.end(chunk, enconding);
clearTimeout(timer);
}
next(); // 将会继续执行下一个中间件
}
}
demo.js:
var connect = require('connect'),
time = require('./request-time.js');
// 获取 connect 对象
var server = connect();
// 将时间控制器假如中间件队列中
server.use(time({time: 1000}));
// 将访问 /a 的结果加入中间件队列中,url再增加一点判断就失效了,应该有更严格的匹配
server.use(function(req, res, next){
if(req.url === '/a'){
res.writeHead(200);
res.end('Fast!');
}else {
next();
}
})
// 将访问 /b 的结果加入中间件队列中
server.use(function(req, res, next){
if(req.url === '/b'){
setTimeout(function(){
res.writeHead(200);
res.end('Slow!');
},1000)
}else {
next();
}
})
server.listen(2000)
node demo.js,访问http://localhost:2000/a或者http://localhost:2000/b会得到相对应的回溯。这里就有两个中间件。connect和request-time,通过包引入,和自己编写两种形式。而且connect是流式处理,根据注册方法的顺序会产生一个队列,上一个方法执行最后调用next,下一个队列中的方法立即被调用。
connect配套的中间件
serve-static:var serveStatic = require('serve-static');server.use('/karyn', serveStatic('./images'));,封装在包serve-static中,使用之后所有访问/karyn的请求都会打到该文件下的images目录下,如果访问的图片文件存在,即可返回该图片。
query:var serveQuery = require('connect-query'); server.use(serveQuery());,封装在包connect-query中,使用之后可以直接通过req.query拿到url上的query数据。
logger:var connectLogger = require('connect-logger'); server.use(connectLogger());,封装在包connect-logger中,使用之后可以通过connectLogger来打印请求信息,可是不是很好使,应该是自己使用不当。
websocket
websocket客户端基于事件的编程模型与node中自定义事件差不多。
websocket实现了客户端与服务器端之间的长连接,而node事件驱动的方式十分擅长与大量客户端保持高并发。
有点:
- 客户端与服务器端只建立一个 TCP 连接,可以使用更少的连接。
- Websocket 服务器端可以推送数据到客户端,这远比 HTTP 请求响应模式更灵活、更高效。
- 有更轻量级的协议头,减少数据传送量。
以前页面需要频繁通信都是通过轮询的方案,现在可以使用websocket的方式,前后端一但建立通信之后,这个通信是双向的,双方都可以通过send方法向对方push数据,再结合事件的机制的话,就能做非常多的事儿了。
// node代码,引入包 websocket.io和express
var ws = require('websocket.io'),
express = require('express'),
app = express();
// 对访问当前目录直接打到静态资源文件html中
app.use(express.static('html'));
// 监听2000端口
http = app.listen(2000);
// websocket 也监听http的端口
var server = ws.attach(http);
// 绑定事件,建立链接后的处理
server.on('connection', function (socket) {
socket.on('message', function (msg) {
console.log(msg)
});
setTimeout(function(){
socket.send('pang')
},3000)
});
// js 代码
var ws = new WebSocket('ws://127.0.0.1:2000');
ws.onopen = function(){
ping();
}
ws.onmessage = function(ev){
console.log('got: ' + ev.data);
}
function ping(){
ws.send('ping')
}
上面就实现了一个简单websocket通信。websocket这么方便,这么好,为啥没有得到大面积使用呢?肯定有问题。下面是使用websocket遇到的问题。
- 首先客户端的支持性需要一定的程度
- 服务器并不知道消息推送是否成功,(可以通过消息确认及超时来判断)
- 客户端直接关掉浏览器,服务端是不知道客户端已经关闭了,还会继续推送(可通过唤醒来判断)
- 比如手机的客户端,待机就会断掉,对手机而言,非常耗电
- 对服务器而言,websocket 比 http 消耗大。服务器的压力增大
- 没有降级处理方案
- 并且在我的使用中,对应多个客户端时,有时候某个客户端关闭,服务端会报错,目前没有查到原因
- 广播方式需要自己实现
socket.io
既然websocket非常好,只要有好的,我们就应该想办法克服那些不好的。Guillermo Rauch就对此做了一系列的封装,前后端都出了相应的方案,就是socket.io,实现和上面相同的功能。
// 引入 socket.io 包
var sio = require('socket.io'),
express = require('express'),
app = express();
// 对访问当前目录直接打到静态资源文件html中
app.use(express.static('html'));
// 监听2000端口
http = app.listen(2000);
// websocket 也监听http的端口
var server = sio.listen(http);
// 绑定事件,建立链接后的处理
server.on('connection', function(socket) {
socket.on('socketName1', function(msg) {
console.log(msg);
});
socket.on('close', function(msg) {
console.log(msg);
});
setInterval(function() {
socket.emit('socketName2', 'pang');
}, 3000);
setInterval(function() {
socket.broadcast.emit('socketName2', 'pang2');
}, 5000);
});
// 前端代码:
// 引入js文件:socket.io.js
var ws = io.connect('ws://127.0.0.1:2000');
ws.emit('socketName1', 'ping');
ws.on('socketName2', function(msg){
console.log('got: ' + msg);
})
简单的通信就建立起来了。socket.io会采用多种方式与后端进行链接,最终采用的方式一定是最优的。如果不支持最优解有一定的降级处理,socket.io会尝试采用以下方式去建立连接:
- websocket
- 长轮询
- falsh socket
- http 长连接
socket.io支持命名空间,一个页面可以支持多个socket连接,这样带来的好处就是多个功能可以区分。还支持全局广播,这个全局广播,所有建立了socket的连接都会接受到。如果服务器断掉了,所有连接中断,但是当服务器重启的时候,所有连接都会尝试重连。连上之后还是会继续之前的通信,但是如果此时客户端没有更新,还是会去找之前的连接。如果实际项目中,一定要考虑兼容。
但是socket.io还是无法确认每次消息的推送都成功了。所以需要自己做一个确认的机制。实现思路:发送消息后,需要接受放给一个回执,如果超时没有得到回执再次发送,接受端需要做去重的操作。确定该消息只接受一次,其他的都丢掉