8. TCP服务和socket连接
开始这部分前,需要先解释一下当初让我困惑许久的TCP/IP、HTTP和socket之间的关系。
TCP/IP是一组协议栈,从底向上包含网络层、传输层和应用层。IP协议在最底层的网络层,TCP协议在中间的传输层,而HTTP则位于最顶层的应用层。所以HTTP是一个位于应用层的协议,一般用于web服务器和本地浏览器之间的数据传输。
TCP协议在HTTP协议的下一层,为HTTP提供服务。当浏览器请求web服务器的数据时,会通过TCP建立一条到服务器的连接,数据传输完成后关闭连接。
socket就不同了,它本身不属于TCP/IP协议栈。它其实是一个TCP/IP编程的接口,主要是为了方便进行网络编程而开发的。
好了,这几个概念清楚后,让我们来看看node中如何处理TCP和socket。
在node中建立一个原始的TCP服务需要net模块。使用net.createServer()就可以建立一个简单的TCP服务。createServer接收一个callback为参数,该callback在每次TCP连接的时候都会被调用。
callback接收一个socket对象作为参数,该对象包含许多关于本次连接的参数。我们可以读取socket中的参数,也可以利用它传递信息给请求方(socket.write()或socket.end())。
最后还要注意,使用net.createServer()建立服务器之后,还需要使用listen方法来监听某一特定的端口。一个简单的TCP服务器可以写成:
var net = require('net');
var server = net.createServer(function(socket) {
socket.end('end of connection');
});
server.listen(3000);
9. HTTP服务
node的http模块也可以建立服务,也就是我们常见的web服务。http.createServer()就可以建立一个http服务,该方法同样接收一个callback作为参数。
这里callback和net的callback不同,它有两个参数:request和response。我们稍微复习一下http的基本原理:客户端每次发送一个请求(request),服务器对该请求作出响应,响应内容作为response返回给客户端。响应完毕,客户与服务器之间的连接随即关闭。request可以有不同的请求类型,常见的有GET、POST、PUT、DELETE几个。
现在callback两个参数的意义就很明确了,request用来获取客户端请求的详细信息,在对请求作出相应的处理后,结果通过response返回给客户端。
下面让我们来看一个简单的例子,一个http服务只接收post请求,将请求的内容转换为大写再返回给客户端。这里要注意request和response都有streaming的概念,内容可以按流输入和输出。为了方便处理,这里使用了一个through2-map模块来传递数据流。
var http = require('http');
var map = require('through2-map');
var server = http.createServer(function(req, res) {
if (req.method != 'POST') {
return res.end('send me a POST\n');
}
req.pipe(map(function(chunk) {
return chunk.toString().toUpperCase();
})).pipe(res);
});
server.listen(Number(process.argv[2]));
两个地方需要注意一下:首先是可以直接用request.method来了解请求的类型。另外一个是在listen端口号的时候,使用了Number来将命令行参数转换为数字。
10. JSON API服务
JSON API是现在常见的web服务方式。web服务器只提供一组API接口,客户端根据接口请求需要的信息。信息通过JSON的形式在服务器和客户端之间传输。
node中实现JSON API的方式非常直观。下面让我们来看看不使用框架如何实现简单的JSON API。
首先,API是通过HTTP的形式实现的,所以必须使用node的http模块。前面说过,API实际上是一组接口,也就是某种形式的路径。客户端根据API发出请求。根据前面说过的关于HTTP服务的知识,我们知道所有客户端的请求信息都包含在request对象中。这个对象除了代表访问类型的method属性外,还有一个很重要的属性url,就是请求的地址(路径)。根据这个路径和其附带的参数,我们就能判断客户端需要的内容了。
request的url属性是一个对象,包含了客户端发送请求的许多信息。比如客户端发送这样一个请求:
/api/parsetime?iso=2013-08-10T12:10:15.474Z
里面包含了请求的地址/api/parsetime,也包含的请求的参数iso=2013-08-10T12:10:15.474Z。手动解析这个对象显然非常麻烦,于是这里又有一个常用的node模块url,它提供了针对url的许多方法,我们这里需要的是url.parse(request.url, true)这个方法来解析url请求。这里的true是为了将参数解析为对象的形式。没有true的话解析结果将会有一个query属性为iso=2013-08-10T12:10:15.474Z;加上true则会将查询参数也解析为一个对象,即类似{query: {iso: '2013-08-10T12:10:15.474Z'}}的形式。
路径解析出来了,我们就可以简单实现许多框架中的route的概念了。其实route最简单的形式就是一个switch,根据路径执行响应的case来对请求作出响应:
function route(reqUrl) {
var parse = url.parse(reqUrl, true);
var res = {};
switch (parse.pathname) {
case '/api/parsetime':
res = parsetimeIso(parse.query);
break;
case '/api/unixtime':
res = parsetimeUnix(parse.query);
break;
default:
console.error('Unknown route');
}
return res;
}
这里我定义了两条route:/api/parsetime和/api/unixtime。他们分别对查询参数调用不同的函数,返回不同的处理结果。
最后,在将处理结果返回给客户端前还需要做一些处理。既然叫做JSON API服务,返回给客户的自然是JSON对象了。于是需要用到js内置的JSON.stringify()做最后的处理,保证返回JSON对象。
让我们来看看这个完整的程序吧,程序很简单,只有上面定义的两个API接口,查询参数是一个ISO格式的时间。parsetime接口取出这个时间的小时、分钟和秒。parsetimeUnix则将这个时间转换为unix时间戳。转换结果统一为JSON格式返回给客户:
var http = require('http');
var url = require('url');
server = http.createServer(function (req, res) {
if (req.method != 'GET') return res.error('You should send me a GET\n');
var response = route(req.url);
var json = JSON.stringify(response);
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(json);
});
server.listen(process.argv[2]);
function route(reqUrl) {
var parse = url.parse(reqUrl, true);
var res = {};
switch (parse.pathname) {
case '/api/parsetime':
res = parsetimeIso(parse.query);
break;
case '/api/unixtime':
res = parsetimeUnix(parse.query);
break;
default:
console.error('Unknown route');
}
return res;
}
function parsetimeIso(query) {
var date = new Date(query.iso);
return {
'hour': date.getHours(),
'minute': date.getMinutes(),
'second': date.getSeconds()
};
}
function parsetimeUnix(query) {
return {'unixtime': Date.parse(query.iso)};
}
经过前面的讲解,相信看懂这个程序是没问题了。到这里,我们短暂的node之旅也暂时告一段落的。这个简短的笔记中并未涉及太多高层次的模块和框架,就是为了告诉大家node的本质其实是比较靠底层的。除了做web服务器之外,node也为js打开了系统脚本,TCP服务等一系列领域的大门。相信自己的选择,努力吧少年!世界终将是你们的。
网友评论