登录
注册
node.js 学习社区
Node.js与Golang使用感受与小结

大天才

2014-12-18 15:04

目录:

一、互联网的基石TCP/IP协议

二、HTTP服务器编写与编程语言无关

三、构建HTTP服务器需要掌握的知识点

四、HTTP协议基础

五、Node.js简介

六、是前端选择,还是后端的福音?

七、Node.js与传统php-fpm模式之间的对比

八、安装Node.js

九、让浏览器读懂你的心--Content-Type

十、向浏览器发送文件

十一、Node.js异步流程控制(序列模式、并发模式、有限并发模式)

十二、静态资源文件的简单优化

十三、现在还需少些什么?

十四、Golang 简介

十五、为什么要用Golang ?

十六、安装Golang开发环境

十七、如何使用Golang 编写一个HTTP服务器?

十八、Golang 的http包与Node.js的http模块对比

前言

这篇文章是对于近段时间以来我对于这两个平台的一些初步的了解,或许文中某些观点与您有所不同,但这却是此时我对这两个平台的一些认识,以及使用这两个平台给我带来的真实感受。

一、互联网的基石TCP/IP协议

可以毫不夸张的说,如果没有TCP/IP 协议,那么也就没有此时的互联网。TCP/IP协议的架构层,经常被人们与OSI网络分层相对比。从概念上来说OSI或许更加完善先进,但是历史却选择了TCP/IP协议。因为早期的unix系统实现了TCP/IP协议,并且运行的很好。于是OSI模式便很少有人愿意去实现它,也许正是因为这样。TCP/IP协议才成就了今天的地位,行业的标准。

二、HTTP服务器编写与编程语言无关

2009年之前,大部份web方面的从业者都习惯于使用服务器+语言的运行模式,比如用

Apache+php 或Nginx+php的模式。但随后出现的一些语言平台以及基于某些语言的框架打破了这种模式。比如Node.js,又比如Ruby社区的Rails。由此可见编写一个HTTP服务器,与您所使用的语言没太多关系。不管你用javascript、或php或python,只要能把内容通过HTTP协议推送到前端浏览器就好。

三、构建HTTP服务器需要掌握的知识点

1、HTTP协议基础(知道HTTP通信的基本模式,包括常用报文)

这篇文章没有打算对于某门语言进行入门级别的指导,因此您可能需要自己去网络上搜集这方面的资源。关于HTTP协议基础可以参考《HTTP权威指南》,您可以花一两周的时间把这本书看完。相信您看完之后,多少都会对您的知识体系有所帮助。

2、熟悉一门语言python,ruby,php,javascript 等,甚至Lisp也行。

熟悉一门语言是必须的,建议您选择一门喜欢的语言去学习,无论是Golang或是javascript,都是不错的开始。编程语言很大程度上只是你去实现某些事情的一个工具,不必执着于哪门语言好或坏,每一门语言能存在五年以上并且还有很多人用的语言,必然有着它吸引人的地方。

3、能够用你熟悉的语言进行基于TCP/IP协议上的通信。

无论你选择什么样的语言,如果进行网络编程的话都需要想办法让它能支持TCP/IP协议。据目前的情况来看,很多现代语言或多或少的都有方法去支持TCP/IP通信。甚至php这门语言,也可以通过libevent 的扩展实现一个简单的http服务器。

四、HTTP协议基础

HTTP是以TCP/IP为基础的网络协议,目前该协议最为常用的版本是1.1版本。了解HTTP协议的一些简单知识,可以在您编写服务器时更快的发现和解决问题。当然HTTP头中的很多信息对于网站的优化都会有影响,比如头部中的缓存控制可以让您减少服务器的I/O消耗,现代服务器的主要瓶颈还是在于I/O消耗。

HTTP协议主要由两个过程组成------请求报文与返回报文。

关于请求报文

请求报文主要由两部份组成,第一部份则是起始行,起始行规定了请求时使用的HTTP方法、请求资源路径以及当前客户端使用的HTTP协议版本(虽然现在大部份浏览器都默认使用1.1版本,但是还是有部份用户使用更为古老的版本)。下图是一个请求报文的抓包截图,我们将会对其进行简单的说明。

字段

字段名

解释

扩展

Host

nodejs.org

告诉服务器当前访问的主机名为nodejs.org

如果你正在实现一个类似于Apache中的vhost功能,那么这个字段可能对你有用

User-Agent

:Mozilla/5.0 (windows NT 6.1; wow64;rv:18.0) Gecko/20100101 Firefox/18.0

告诉服务器,当前访问您的设备的工作环境

如果你正在做一个浏览器信息统计的功能,那么这段报文可能对你有用,当然这段报文也可以被用户伪造

Accept

text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

告诉服务器,当前访问您的浏览器支持些什么文档类型


Accept-Language en-US,en;q=0.5

告诉服务器,当前访问您的设备支持些什么语言

如果您正在制作一个国际化的站点,那么这一部份信息可能对您有用。

Accept-Encoding gzip, deflate

告诉服务器,当前访问您的设备支持哪些压缩算法

如果您想减少网络传输量,特别是静态资源的传输量。可能这一部份信息对您有用,在实际工作中有些静态资源通过Gzip压缩后,体积只有原来的30%左右。

Pragma

no-cache

告诉服务器,此页面不缓存

事实上这个字段可以用Cache-Control控制

Cache-Control

no-cache

告诉服务器,此页面不缓存

做页面缓存控制的时候可以使用


第一行 GET http://nodejs.org/ HTTP/1.1 这一行的意思是浏览器告诉服务器端,当前正在使用HTTP 1.1协议中的GET方法获取http://nodejs.org/ 这个路径的资源。第一行之下的部份,可以单独看成一个部份。以键值对(K-V)的形式存在,可以想像成关系数据库中的字段与字段值。

  Web开发基本上都是围绕http协议展开,所以了解并熟悉http协议对于长远发展来说是有好处的。通过合理利用http header字段,还能够帮助我们减少很多安全方面的问题。但目前注重一块的web开发人员较少。

五、Node.js简介

Node.js是一个基于谷歌V8引擎的平台,通过利用V8引擎并遵守COMMONJS标准实现了一个高效稳定的平台。Node.js做web编程是基于数据流的方式,可以直接操控http流,正由于这样的特性,我们可以方便的去定制发送给客户端的数据,同时由于使用了Javascript并且由于它的异步性,更适合于现代应用程序的开发。目前全世界已经有多家公司在使用,著名的用户如linkin,TaoBao,Myspace等。

六、是前端选择,还是后端的福音?

Node.js发布之后,在国内外都引起了不小的讨论风暴。甚至国内有很大一批前端工程师认为这个为前端工程师准备的平台,也有一些后端工程师不习惯于Node.js的编程风格。

Javascript是一门饱受争议的语言,它从设计到投入使用只花了很短的一段时间。它借鉴了很多语言的特性,当然也存在了很多语言有的缺点。由于历史的原因,Javascript早期是没有包的根据,类的话也只是去通过prototype去模拟实现。不过Javascript 却是世界上最为自由的语言之一,你可以天马行空的利用语法糖去模拟你熟悉的语言。一般情况下不建议去模拟其它语言,当你使用什么语言的时候,最好去思想在这门语言中去解决同样的问题会怎么做,而不是用另一种语言的思维去做 Javascript早期似乎成为了前端开发者的必备工具,可随着时代的发展大家发现原来JS也是可以写服务器端程序的。

事实上,它只是一个高效http服务器的解决方案之一。并非特别的为前端还是后端工程师准备,如果你想,你就可以用。但是是否能用好,就在于你自己。纵观软件发展,我们已经很难以分清什么是前端或后端工程师,以操作系统而言,我们现在都是在应用层上面作开发(有些书中称为用户态模式),那么相对于那些内核工程师,我们是前端还是后端昵?


七、Node.js与传统php-fpm模式之间的对比

传统PHP的运行模式(非libevent之类的运行方式):

大部份PHP运行的环境,基本上都会有一个中间件角色的服务器,它负责与PHP的端口进行通信(默认是9000端口),如Apache、Nginx都可以作为这样的中间件。当一个http请求从客户端发起后,服务器一般做以下几件事情:

1、根据用户发送的请求报文,判断是哪种HTTP方法,是POST 还是GET,然后在根据服务器软件的实现进行下一步处理。

2、判断用户请求的资源是否是静态还是动态脚本,这一步特别是一些nginx之类的中间件会去处理。如果请求的是静态文件,在权限都符合并且资源存在的情况下会直接发送给浏览器,当然有些已经请求过的文件可能会直接返回一个304状态码,这样浏览器就会利用缓存里面的内容给用户。如果是动态文件,那么中间件就会去判断应该怎么去处理。这也是为什么大多数类似于nginx之类的服务器,需要对php之类的脚本在配置文件中进行配置的原因。

3、如果是php,那么中间件会把请求先转发给php解释器,然后由php解释器运算完成之后,在把结果返回给中间件,中间件在把结果返回给浏览器。中间件在这里作了代理的功能,事实上它做的工作由php通过libevent之类的模块也可以做,但是实现成本过高,所以大部份企业还是选择nginx或apache去实现这些基础功能,而把精力用于业务的开发上。

Node.js的运行模式---基于数据流的现代编程

对于现代程序开发来说,最为直接的方式便是对于数据流进行直接控制。这样你可以决定哪些东西返回给用户,哪些东西是你需要隐藏的。一个Node.js的应用接到一个http请求之后会做以下事情:

1、路由器分发处理规则:因为应用本身就是服务器,所以不用转发,不用代理,直接通过自定义的规则去处理请求。

2、具体业务可以按队列形式处理,也可以按异步形式处理。简单来说,传统的web开发之中,我们需要做一件事情的时候,一般会等待上一个事情做完,然后才会去处理新的事情。但是在node.js中,你可以同时处理多件事情。而且当它们完成后,会将各自的结果返回给用户。

(图为以前所做PPT中所截取)

平台风格

Node.js

PHP

安装方式

可源码或直接安装二进制安装包

通过源码编译安装或使用第三方绿色包等

编程风格

异步风格且基于数据操作

同步风格

处理用户请求方式

应用本身即是HTTP服务器

需要借助如Nginx之类的服务器

扩展安装方式

npm

Phpize或在初始安装时配置

实时运行模式

每次更改需要重启应用,但是也可以参考Java中Play框架去热加载,但是会影响性能。因为热加载的一般原因是实时对文件改动进行监控。

修改文件后直接保存,即时生效,但程序会每次去读取新的php文件,有一定的I/O损耗。

语言规则

类C风格

类C风格

性能对比

----请自行使用http_load之类工具对比

----请自行使用http_load之类工具对比

八、安装Node.js

先登录 http://nodejs.org/download/ 然后下载适合你的平台版本,建议初学者下载二进制安装包,但熟悉之后还是用源码方式编译安装好。编译安装的好处在于,编译器会根据用户所工作的平台环境进行优化。

假设我们此时下载了二进制包,那么

一路Next下去就可以了。

要检测是否安装成功,在你的控制台中输入 node -v (windows用户可以在开始菜单中的运行中输入cmd进行,由于文章在windows平台写的,以windows版本为例)

九、让浏览器读懂你的心--Content-Type

写Node.js程序,你只需要一个编辑器,一个运行环境就好~

var http = require("http");
http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': "text/plain; charset=utf-8"});
  res.write('世界你好!');
  res.end();
}).listen(3000, '127.0.0.1');
console.log('Server running at http://127.0.0.1:3000/');

将里面的代码内容保存为hello.js,然后通过控制台运行 node <你保存的目录>/hello.js 。做完这一切之后,打开你的浏览器输入http://127.0.0.1:3000 按下回车看看效果吧。

通过这个DEMO,你应该能看到一个简单的http服务器的成功产生了吧。当然要做一个功能齐全的服务器还有很多东西需要做,这里只是一个简单的DEMO.在这个DEMO中,大家应该会发现Content-Type这个东东,前面讲进Content-Type这个东东是告诉浏览器要做些什么事情。比如有的时候你发给用户的是不是文本文件而是视频或是图片,那么就需要告诉浏览器调用系统的某些程序来运行或是显示它。

十、向浏览器发送文件

还是先上代码吧,一切以代码为先。

var server,
    ip   = "127.0.0.1", port = 3000,http = require('http'), fs = require("fs"), folderPath = "static",//存放文件夹
    url = require('url'),
    path=require('path'),
    urlpath,//请求路径
    filePath;//文件路径
var types = {
    "css": "text/css",
    "gif": "image/gif",
    "html": "text/html",
    "ico": "image/x-icon",
    "jpeg": "image/jpeg",
    "jpg": "image/jpeg",
    "js": "text/javascript",
    "png": "image/png",
    "tiff": "image/tiff",
    "txt": "text/plain"
};
server = http.createServer(function (req, res) {
    urlpath = url.parse(req.url);
    filePath = folderPath + urlpath.pathname;
    var content_type= types[path.extname(urlpath.pathname).substr(1)]||'text/plain';
    fs.readFile(filePath, function(err, file) {
      if (err) {
          res.writeHead(404, {'Content-Type': 'text/plain'});
          res.end();
          return;
      }
      res.writeHead(200, {'Content-Type': content_type});
      res.write(file);
      res.end();
    });
});
server.listen(port, ip);
console.log("Server running at http://" + ip + ":" + port);

发送文件当然存在读取的文件的这一过程,所以需要用到fs包。这个DEMO应该比较简单,只要你有前面的基础,相信很快能明白这个DEMO的意图。这一部份不打算具体说明,因为已经有很多前期的说明,而且网络上也有很多文章,我们着重需要讲的是Node.js异步流程控制,这一部份中文资料较少,而现在业内据我所知大多是用AsyncJS或step之类的node库来实现,并不是说他们的实现不好,而是中间的原理需要搞懂才能掌握某些被认为的关键技术。

十一、Node.js异步流程控制(序列模式、并发模式、有限并发模式)

Javascript在基本语法上与其它大部份C派生的语言没有太多区别,你可能很容易从其它语言过度到Javascript。很多从其它语言转到Javascript来的用户,在用一段时间之后很可能对这门语言又爱又恨,特别是对于异步流程的控制。

对于大部份异步编程的模型来说,大多是事件驱动型且是基于进程来编码。这样为我们带来了诸多好处,我们不必去处理为了实现同样目的而做的多线程模型里面的问题。多线程编程里面,由于多个线程访问的内存块是一样的,所以可能会出现死锁、共享竞争等问题。

异步编程是什么昵?打个比方,你需要准备一个丰富的晚餐,其中有炒菜与熬汤。如果是阻塞式同步编程的话,你需要先去炒菜,等菜炒好了,然后在去熬汤。在炒菜的过程中你不能在做其它事情,只有两样都好了的时候你才能开始你的晚餐。那异步会怎么做昵?可以一边熬汤,一边炒菜。菜炒好的时候,可能汤也好了。然后你就可以开动了。如果以每一个步骤的时间来计算的话,两者的时间成本里面显然异步要能做的事情更多。

阻塞式同步编程:炒菜(5分钟)+熬汤(30分钟)=开饭时用了35分钟

异步式编程模型:【炒菜(5分钟)熬汤(30分钟)】=开饭时用了30分钟

通过以上的例子可以知道异步编程模型在完全任务的时间比以及时间利用上要高效的多。

研究异步流处理的意义

异步流程控制不好,很容易写出巢式风格代码,随着项目的深入与功能的扩展会发现越来越难以维护,或许这也是很多用不习惯JS的程序员不喜欢 JS的一个原因 之一吧。

async1(function(input, result1) {
 async2(function(result2) {
    async3(function(result3) {
      async4(function(result4) {
       async5(function(output) { // do something with output });
        });
      });
    });
   })

以上这种代码便是我们可以在某些程序员同学中可以看到过的代码,特别是操作数据库部份经常容易出现这样的代码。以前一个同事说用过Node.js开发过一次程序后就不想再次开发程序,因为已经受够了这种难以维护的代码。当时我向他推荐了一些类似如Asyncjs,Step之类的流程控制的库,最后那个项目是否采用就不得而知。

巢式风格的代码 可能引起的问题

解决之道 目前社区有三种观点:

(一)

将代码分离再分离。虽然还是有存在回调函数的问题,但是代码的复杂度得到一定的降低。举例而言(以nodejs为例,但异步流程控制问题并不是NODEJS的专利):

var http=require("http");
     http.createServer(function(request,response){
	     response.writeHead(200,{"Content-Type":"text/plain"});
	     response.write("hello");
	     response.end();
     }).listen(80,"127.0.0.1");

上面代码看似没有什么问题,但是如果这个时候我要向浏览器端返回一个文件,如果文件没存在返回404。又如果我要增加数据库访问功能。如果代码全放到这个里面。那维护起来会是怎么样的情况!

var http=require("http");
	function requesthander(req,res){
		res.writeHead(200,{"Content","text/plain"});
		res.wirte("hello");
		res.end();
	}
	http.createServer(requesthander).listen(80,"127.0.0.1");

这种分离方法被早期的一些设计所使用,其主体思想是将巢状代码给展平,分离每一步逻辑。但是这个也带来了另外的问题。虽然实现了分享,但是代码之间过于松散,代码之间的关系必须层层深入的去理解。

(二)

将代码进一步抽象,使用统一的库或工具包去组织代码,不破坏原有代码的逻辑与组织。就Javascript 世界来说出现了很多优秀的库。如Setp,Async(如网易开源的游戏开发框架)之类的库,他们提供了丰富的流控制接口,利用这些接口方法你可以很好的组织或利用好你的代码,减轻开发的负担。

使用方式如下:

async.series(
    [
        function(callback) {
            callback(null, "Node.js");//第一个函数的执行结果传给下个
        },
        function(callback) {
            callback(null, "JavaScript");//第二个函数的执行结果在传给最后的回调函数
        },
    ],
    function(err, response) {
        // response is ["Node.js", "JavaScript"]  最后的结果收集。
    }
);

(三)

编译执行。是的,其大概思路是将已有的代码再通过某种方式编译一式,最终实现异步的功能。比如国内老赵的Jscex.虽然本人不太喜欢这种风格的方试,但是还是向大家简单介绍一下其代码风格。Eval的使用以及代码组织方式让我放弃了使用此库。

下面开始此次的正题,介绍社区中常用的三种js异步流程控制模式

无论网络上的代码怎么变化,提供了怎么样的接口,但目前来说仍旧脱离不开以下三种模式。希望通过简单的介绍能让您也写出一个自己的异步流程控制库或能了解其基本原理也不错。

Series(串行模式)

-------每次只执行一个任务。

Fully parallel(全并发模式)

-------一次性全部执行

Limitedly parallel(限制并发模式)

-------每次按限制的个数执行并发任务。

异步处理库需要具备的功能

1、能够提供控制函数集执行的方法。

2、能够收集执行过程中所产生的数据。

3、提供特殊情况下的并发限制,因为在某些情况下对于并发数量是有控制的,否则可能会让你的系统吃不消。

4、能够提供方法能在所有函数集确认执行成功后执行。

Series模式

适用场景:对执行顺序有具体要求的情景。

如查询多条数据库且数据库语句有依赖情况下

又比如说写文件(目录权限,文件是否存在,文件权限);

特点:

按顺序执行函数集

在运行时每次只运行一个任务

确保每次运行都在上一个任务完成后执行。

每一步的数据是可控,可在下一个任务执行时被调用到(数据顺序性)。

Fully parallel模式

适用场景:并发运行多个任务,当所有任务运行完通知回调方法。如多个I/O操

作并且每个时长不确定的时候,可能需要这种模式。

特点:

并发运行多个任务,不用等待上一个任务执行完才执行。

运算执行只能在回调函数中得到,因为并发模式不能确保执行的顺序。

Limited parallel模式

适用场景:系统资源有限,且每个执行任务消耗资源较多。

特点:

并发运行多个限定任务,不用等待上一个任务执行完才执行。

运算执行结果只能在回调函数中得到,因为并发模式不能确保执行的顺序。

附三种模式的代码:

/**
*  Series flow
*@param {Array} callbacks
*@param {Function} last
*@example series([fun1(next){
someFun();
next();//or next(value)
},fun2(next){
   someFun();
next();//or next(value) 
}])
**/
function series(callbacks, last) {
  var results = [];
  function next() {
    var callback = callbacks.shift();
    if(callback) {
      callback(function() {
        results.push(Array.prototype.slice.call(arguments));
        next();
      });
    } else {
      last(results);
    }
  }
  next();
}
// Example task
function async(arg, callback) {
  var delay = Math.floor(Math.random() * 5 + 1) * 100; // random ms
  console.log('async with \''+arg+'\', return in '+delay+' ms');
  setTimeout(function() { callback(arg * 2); }, delay);
}
function final(results) { console.log('Done', results); }


series([
  function(next) { async(1, next); },
  function(next) { async(2, next); },
  function(next) { async(3, next); },
  function(next) { async(4, next); },
  function(next) { async(5, next); },
  function(next) { async(6, next); }
], final);
--------------------------------------------------------
function fullParallel(callbacks, last) {
  var results = [];
  var result_count = 0;
  callbacks.forEach(function(callback, index) {
    callback( function() {
      results[index] = Array.prototype.slice.call(arguments);
      result_count++;
      if(result_count == callbacks.length) {
        last(results);
      }
    });
  });
}
// Example task
function async(arg, callback) {
  var delay = Math.floor(Math.random() * 5 + 1) * 100; // random ms
 // console.log('async with \''+arg+'\', return in '+delay+' ms');
  for(var i =0;i <100000000;i++){


  }
  setTimeout(function() { callback(arg * 2); }, delay);
  console.log(+new Date());
 
}
function final(results) { console.log('Done', results); }


fullParallel([
  function(next) { async(1, next); },
  function(next) { async(2, next); },
  function(next) { async(3, next); },
  function(next) { async(4, next); },
  function(next) { async(5, next); },
  function(next) { async(6, next); }
], final);
-------------------------------------------------
function limited(limit, callbacks, last) {
  var results = [];
  var running = 1;
  var task = 0;
  function next(){
    running--;
    if(task == callbacks.length && running == 0) {
      last(results);
    }
    while(running < limit && callbacks[task]) {
      var callback = callbacks[task];
      (function(index) {
        callback(function() {
          results[index] = Array.prototype.slice.call(arguments);
          next();
        });
      })(task);
      task++;
      running++;
    }
  }
  next();
}
// Example task
function async(arg, callback) {
  var delay = Math.floor(Math.random() * 5 + 1) * 1000; // random ms
  console.log('async with \''+arg+'\', return in '+delay+' ms');
  setTimeout(function() {
    var result = arg * 2;
    console.log('Return with \''+arg+'\', result '+result);
    callback(result);
  }, delay);
}
function final(results) { console.log('Done', results); }


limited(2, [
  function(next) { async(1, next); },
  function(next) { async(2, next); },
  function(next) { async(3, next); },
  function(next) { async(4, next); },
  function(next) { async(5, next); },
  function(next) { async(6, next); }
], final);

---------------------------------------------

参考文献 :http://book.mixu.net/

原文地址:http://www.cnblogs.com/tdyz/archive/2013/06/04/3116931.html

回复 · 1

发表回复

你可以在回复中 @ 其他人