Node.js网络编程

Node.js为javascript语言提供了一个在服务端运行的平台,它以其事件驱动,非阻塞I/O机制使得它本身非常适合开发运行在在分布式设备上的I/O密集型应用,分布式应用要求Node.js必须对网络通信支持友好,事实上Node.js也提供了非常强大的网络通信功能,本文就主要探讨如何使用Node.js进行网络编程。

首先,网络编程的概念是"使用套接字来达到进程间通信的目的"。通常情况下,我们要使用网络提供的功能,可以有以下几种方式:

1.使用应用软件提供的网络通信功能来获取网络服务,最著名的就是浏览器,它在应用层上使用http协议,在传输层基于TCP协议;

2.在命令行方式下使用shell 命令获取系统提供的网络服务,如telnet,ftp等;

3.使用编程的方式通过系统调用获取操作系统提供给我们的网络服务。

本文主要目的就是要探讨如何在Node.js中通过编程来获取操作系统提供的网络服务来达到不同主机进程间通信。现在回过头来在看看网络编程的概念,这里涉及到一个套接字(socket)的概念,所谓套接字,实际上是两个不同进 程间进行通信的端口(这里的端口有别于IP地址中常用的端口),它是对网络层次模型中网络层及其下面各层操作的一个封装,为了让开发者能够使用各种语言调用操作系统提供的网络服务,在不同服务端语言中都使用了套接字这个概念,开发者只要获得一个套接字(socket),就可以使用套接字(socket)中各种方法来创建不同进程之间的连接进而达到通信目的。通常情况下,我们使用以下网络层次模型,

所谓的socket(套接字)就是将操作系统中对于传输层及其以下各层中对于网络操作的处理进行了封装,然后提供一个socket对象,供我们在应用程序中调用这个对象及其方法来达到进程间通信的目的。

基于以上概念,Node.js也提供了对socket的支持,它提供了一个net模块用来处理和TCP相关的操作,提供了dgram模块用来处理UDP相关操作,关于TCP和UDP的区别这里就不赘述了,属于老生常谈的话题了。

1.创建TCP服务端。

在Node.js中,可以很方便地创建一个socket服务端,利用如下函数

var server=net.createServer([options],[listener]);

其中net为我们引入的net模块,options参数为可选,它是一个对象,其中包含如下两个属性:

allowHalfOpen,该属性默认为false,这个时候如何TCP客户端发送一个FIN包的时候,服务端必须回送一个FIN包,这使得这个TCP连接两端同时关闭,这种情况下关闭后任何一方都不能再发送信息,而如果该属性为true,表示TCP客户端发送一个FIN包的时候,服务端不回发,这将导致TCP客户端关闭到服务端的通信,而服务端仍然可以向客户端发送信息。这种情况下如果要完全关闭这个TCP双向连接,则需要显式调用服务端socket的end方法。

pauseOnConnect,该属性默认为false,当它被设置为true的时候表示该TCP服务端与之相连接的客户端socket传输过来的数据将不被读取,即不会触发data事件。如果需要读取客户端传输的数据,可以使用socket的resume方法来设置该socket。

参数listener表示一个创建socket之后的回调函数,它有一个参数,表示当前创建的服务端socket,

function (socket) {    // to do sth..}

最后这个方法返回的是被创建的服务器对象。

对于这个对象,它是继承了EventEmitter类,它具有几个重要的事件和方法,如下:

connection事件,用来监听客户端socket连接到这个TCP服务器的时候触发

server.on(‘connection‘,function(socket){
   // to do sth...
});

close事件,TCP服务器被关闭的时候触发。

server.on(‘close‘,function(){
    console.log(‘TCP服务器被关闭‘);
});

error事件,TCP连接出现错误的时候触发

listen方法,用来监听来自客户端的TCP连接请求。

下面是一个完整的创建TCP服务端的例子。

var net=require(‘net‘);
var server=net.createServer(function(socket){
    console.log(‘客户端和服务端建立连接‘);
    server.getConnections(function(err,count){
       console.log("当前连接数为%d",count);
    });
    server.maxConnections=2;
    console.log(‘tcp最大连接数为%d‘,server.maxConnections);
});
server.on(‘error‘,function(e){
    if(e.code==‘EADDRINUSE‘){
        console.log(‘地址和端口被占用‘);
    }
});
server.listen(2000,‘localhost‘,function(){
    //console.log(‘服务器端开始监听...‘);
    var address=server.address();
    console.log(address);
});

这段代码创建了一个TCP服务端并将该服务端指定最多连接两个客户端,并监听本地的2000端口等待客户端连接,接着我们可以使用远程登录(Telnet基于TCP协议)来测试这个服务端,分别在两个命令行中输入

telnet loclhost 2000 

结果如下:

当使用第三个命令行窗口进行登陆的时候,发现无法连接到服务端,因为这里我们设置了连接到服务端的TCP连接只能为最大两个。

2.创建TCP客户端并进行服务端与客户端的通信

创建一个独立的客户端需要调用net模块的Socket构造方法,如下:

var client=new net.Socket([options]);

这个构造函数接受一个可选的参数,是一个对象,它里面包含如下几个属性:

fd:用来制定一个已经存在的socket文件描述符来创建socket;

allowHalfOpen:作用同上

readablewriteable,当使用fd来创建socket的时候指定该socket是否可读写,默认为false。

实际上该client就是一个独立的socket对象。这个socket对象通常具有如下比较重要的方法和属性:

connect方法,用来连接指定的TCP服务端。

socket.connect(port,[host],[listener])

write方法,向另外一端的socket写入数据

socket.write(data,[encoding],[callback])

其中data可以是字符串,也可以是Buffer数据,如果是字符串需要指定第二个参数用来指定其编码方式。第三个参数为回调函数。

以下是一个完整的创建TCP客户端的代码:

var net=require(‘net‘);
var client=new net.Socket();
client.setEncoding(‘utf8‘);
client.connect(2000,‘localhost‘,function(){
   console.log(‘已连接到服务端‘);
   client.write(‘hello!‘);
    setTimeout(function(){
        client.end(‘bye‘);
    },10000);
});
    client.on(‘error‘,function(err){
        console.log(‘与服务端连接或通信发生错误,错误编码为%s‘,err.code);
        client.destroy();
    });

client.on(‘data‘,function(data){
   console.log(‘已接收到服务端发送的数据为:‘+data);
});

该段代码创建了一个TCP客户端,并且连接本地2000端口的服务器,向服务器发送hello数据,然后过十秒之后再发送bye,最后关闭该TCP客户端的连接。并且监听它的data事件,当收到服务端发送来的数据时打印出来。

与该客户端对应的一个TCP服务端代码如下:

var net=require(‘net‘);
var server=net.createServer({allowHalfOpen:true});
server.on(‘connection‘,function(socket){
    console.log(‘客户端已经连接到服务器‘);
    socket.setEncoding(‘utf8‘);
    socket.on(‘data‘,function(data){
        console.log(‘接收到客户端发送的数据为:‘+data);
        socket.write(‘确认数据:‘+data);
    });
    socket.on(‘error‘,function(err){
        console.log(‘与客户端通信过程中发生错误,错误码为%s‘,err.code);
        socket.destroy();
    });
    socket.on(‘end‘,function(){
        console.log(‘客户端连接被关闭‘);
        socket.end();
        //客户端连接全部关闭的时候退出引用程序
        server.unref();
    });
    socket.on(‘close‘,function(has_error){
        if(has_error){
            console.log(‘由于一个错误导致socket连接被关闭‘);
            server.unref();
        }else{
            console.log(‘socket连接正常关闭‘);
        }

    });
});
server.getConnections(function(err,count){
    if(count==2){
        server.close();
    }
});
server.listen(2000,‘localhost‘);
server.on(‘close‘,function(){
    console.log(‘TCP服务器被关闭‘);
});

该服务端接收到客户端发送来的数据之后再回发回去,并且当连接到该TCP服务端的所有socket连接都断开时,自动退出应用程序。

运行这两段代码,结果如下:

服务端:

客户端

从以上我们可以看出,基于TCP连接的通信具有以下特点:

1)面向连接,必须建立连接后才能够互相通信;

2)TCP连接是一对一的,就是说在TCP中,一个客户端socket连接一个服务端socket,并且两者可以相互通信,通信是双向的。

3)TCP连接关闭的时候是可以只关闭一方的连接而保留单向通信;

4)一个特定的IP加端口可以连接多个TCP客户端,也可以通过编程指定连接上限。

3.创建UDP的客户端和服务端

在Node.js中,提供了dgram模块用来处理UDP相关的操作与调用,我们知道UDP是一种非连接不可靠但高效的传输协议,所以这里实际上创建一个TCP客户端和服务端在函数调用上是没有区别的,

采用dgram模块的createSocket方法,如下所示:

var socket=dgram.createSocket(type,[callback])

该方法有两个参数,分别如下:

type:采用的udp协议类型,可以是udp4或udp6,该参数必须

callback:创建完成之后的回调函数,该参数可选。回调函数中有两个参数

function (msg,rinfo) {
 // 回调函数代码
}

msg为一个Buffer对象,表示接收到的数据,rinfo也是一个对象,表示发送者的信息,它含有如下信息:

address:发送者IP

port:发送者端口

family:发送者IP地址类型,如IPV4或IPv6

size:发送者发送信息的字节数

调用创建方法之后返回一个UDP scoket,它 拥有如下几个重要方法和事件:

message事件,当接收到发送来的信息的时候触发,如下:

socket.on(‘message‘,function (msg,rinfo){
  // 回调函数代码
});

bind方法:为该socket绑定一个端口和ip,如下:

socket.bind(port,[address],[callback])

listening事件,当第一次接收到一个UDP socket发送来的数据的时候触发,如下:

socket.on(‘listening‘,function (){
 // 回调函数代码
});

send方法,向指定udp socket发送信息。如下:

socket.send(buf,offset,length,port,address,[callback])

该方法有六个参数,buf是一个Buffer对象或者字符串,表示要发送的数据,offset表示从哪个字节开始发送,length表示发送字节的长度,port表示接收socket的端口,address表示接收socket的IP,callback为回调函数,其中callback为可选的之外,其他参数都是必须的。

以下创建一个UDP客户端的完整代码

var dgram=require(‘dgram‘);
var message=new Buffer(‘hello‘);
var client=dgram.createSocket(‘udp4‘);
client.send(message,0,message.length,2001,"localhost",function(err,bytes){
    if(err) console.log(‘数据发送失败‘);
    else console.log("已发送%d字节数据",bytes);
});
client.on("message",function(msg,rinfo){
    console.log("已接收到服务端发送的数据%s",msg);
    console.log("服务器地址信息为%j",rinfo);
    client.close();
});
client.on("close",function(){
   console.log("socket端口被关闭");
});

这段代码创建一个客户端socket并向另外一个客户端发送hello,并将其他socket发送来的数据打印出来,然后关闭客户端socket。

下面是相应的服务端socket的代码:

var dgram=require(‘dgram‘);
var server=dgram.createSocket(‘udp4‘);
server.on("message",function(msg,rinfo){
    console.log(‘已接收到客户端发送的数据为‘+msg);
    console.log("客户端地址新信息为%j",rinfo);
    var buff=new Buffer("确认信息"+msg);
    server.send(buff,0,buff.length,rinfo.port,rinfo.address);
    setTimeout(function(){
        server.unref();
    },10000);
});
server.on("listening",function(){
    var address=server.address();
    console.log("服务器开始监听,地址信息为%j",address);
});
server.bind(2001,‘localhost‘);

该段代码创建一个服务端socket,并将它绑定到本地2001端口上,监听它的listening事件,打印出客户端信息,并将接收到的客户端信息打印出来并回送给客户端,同时在10秒之后如果所有客户端关闭则退出应用程序。

结果如下,客户端:

服务端:

从上面我们可以看出,与TCP不同的是,我们不需要专门创建一个socket监听客户端连接,客户端也不用经过连接而是直接向指定服务端socket发送信息,这证明了socket是无连接的。

同时,对于udp来讲,它的无连接特性使得它能够一对一,多对多,一对多和多对一,这和TCP连接的一对一是有很大区别的。基于UDP这种特性,我们可以使用UDP来实现数据的广播和组播。

4.使用UDP来进行数据广播

在dgram模块中,使用socket的setBroadcast方法开启该socket的广播,如下:

socket.setBroadcast(flag)

其中flag默认为false,表示不开启广播,true表示开启。

所谓广播,指的是一个主机向本网络的其他主机上发送数据,本网络内的其他主机都可以接收到,同时按照对IP地址的分类,对于A,B,C类地址来讲,其所在网段的主机号全1的地址就是一个广播地址,我们需要将该数据广播到这个地址上,而不是直接发送给某个指定IP的主机。

基于以上认识,我们编写一个广播服务端如下:

var dgram=require(‘dgram‘);
var server=dgram.createSocket("udp4");
server.on("message",function(msg){
    var buff=new Buffer("已接收到客户端数据为:"+msg);
    server.setBroadcast(true);
    server.send(buff,0,buff.length,2002,"192.168.56.255");
});
server.bind(2001,"192.168.56.1");

该段代码创建一个服务端socket,绑定IP和端口,接受客户端数据,并将客户端数据广播到本网络的广播地址上。

客户端代码如下:

var dgram=require(‘dgram‘);
var client=dgram.createSocket(‘udp4‘);
client.bind(2002,‘192.168.56.2‘);
var buf=new Buffer("client");
client.send(buf,0,buf.length,2001,‘192.168.56.1‘);
client.on("message",function(msg,rinfo){
    console.log(‘接收到的服务端数据为%s‘,msg);
});

这段代码表示创建一个客户端socket,并为该socket绑定IP和端口,同时向服务端发送数据,并将接收到的数据打印出来。

在本地主机上运行服务端代码,并将客户端代码部署在不同主机上并修改客户端socket的IP地址和端口,则任意客户端发送来的消息都会广播给所有和该服务器通信的客户端。

5.使用UDP进行组播

所谓组播是指任意主机都可以加入到一个组中,这个组的地址是一个特殊的D类IP地址,范围为224.0.0.0--239.255.255.255,发送者只需要将发送的数据发送给一个组播地址,那么所有加入改组的主机都可以收到发送者的数据(注意这里不是该网络上的所有主机)。

对于组播地址,通常如下:

•局部组播地址:224.0.0.0~224.0.0.255,这是为路由协议和其他用途保留的地址。

•预留组播地址:224.0.1.0~238.255.255.255,可用于全球范围(如Internet)或网络协议。

•管理权限组播地址:239.0.0.0~239.255.255.255,可供组织内部使用,类似于私有IP地址,不能用于Internet,可限制组播范围。

Node.js中使用addMembership来让主机加入到该组中,从而实现IP组播,如下:

socket.addMembership(multicastAddress, [multicastInterface])

该方法第一个参数是组播地址,第二个参数可选,表示socket需要加入的网络接口IP地址,如果不指定,则会加入到所有有效的网络接口中。

一个socket加入组播组之后,可以使用dropMembership退出该组播组,如下:

socket.dropMembership(multicastAddress, [multicastInterface])

下面是一个完整的发送组播数据的udp服务端

var dgram=require(‘dgram‘);
var server=dgram.createSocket(‘udp4‘);
server.on(‘listening‘,function(){
    server.setMulticastTTL(128);
    server.addMembership(‘230.185.192.108‘);
});
setInterval(broadCast,1000);
function broadCast(){
    var buf=new Buffer(new Date().toLocaleString());
    server.send(buf,0,buf.length,8088,‘230.185.192.108‘);
}

这段代码创建一个发送组播数据的socket服务端,加入组播组230.185.192.108,并每隔一秒向该组发送服务端时间信息。

对应客户端代码如下:

var PORT=8088;
var HOST="192.168.56.2";
var dgram=require(‘dgram‘);
var client=dgram.createSocket(‘udp4‘);
client.on(‘listening‘,function(){
    client.addMembership(‘230.185.192.108‘);
});
client.on(‘message‘,function(msg,remote){
    console.log(msg.toString());
});
client.bind(PORT,HOST);

客户端创建一个socket并绑定自己的端口和IP,接收来自服务端发送的数据。在listening事件中将它加入该组播组之中。

在本地主机上运行服务端代码,在不同的网络主机上运行客户端代码并修改其IP和端口为不同主机自己的IP和端口,所有加入到该组播的客户端都会收到服务端发送的时间信息。

6.总结

综上所述,在Node.js中,我们把可以使用net模块来创建基于TCP的服务端和客户端的连接和通信,同时也可以使用dgram模块来处理基于UDP客户端和服务端的通信。

时间: 2024-11-12 03:46:43

Node.js网络编程的相关文章

深入理解node.js异步编程

1. 概述目前开源社区最火热的技术当属Node.js莫属了,作为使用Javascript为主要开发语言的服务器端编程技术和平台,一开始就注定会引人瞩目. 当然能够吸引众人的目光,肯定不是三教九流之辈,必然拥有独特的优势和魅力,才能引起群猿追逐.其中当属异步IO和事件编程模型,本文据Node.js的异步IO和事件编程做深入分析. ##2. 什么是异步同步和异步是一个比较早的概念,大抵在操作系统发明时应该就出现了.举一个最简单的生活中的例子,比如发短信的情况会比较好说明他们的区别:同步:正在处于苦逼

理解Node.js事件驱动编程

Node.js现在非常活跃,相关生态社区已经超过Lua(基本上比较知名的功能都有nodejs模块实现). 但是我们为何要使用Node.Js?相比传统的webserver服务模式,nodejs有什么优点优势? Node.Js是基于javascript语言,建构在google V8 engine以及Linux上的一个非阻塞事件驱动IO框架.nodejs是单进程单线程,但是基于V8的强大驱动力,以及事件驱动模型,nodejs的 性能非常高,而且想达到多核或者多进程也不是很难(现在已经有大量的第三方mo

node.js高级编程|node.js 视频教程_基于node.js+Express.js+Jade+MongoDB实战开发

基于node.js+Express.js+Jade+MongoDB开发Web即时聊天系统课程讲师:幻星课程分类:前端开发适合人群:初级课时数量:36课时更新程度:完成用到技术:Node.js NPM. Express.NoSQL,MongoDB涉及项目:匿名聊天网站系统node.js视频教程:http://www.ibeifeng.com/goods-462.htmlnode.js 教程适合人群:node.js视频教程要求学员了解JavaScript,node.js入门教程适合希望更深入的学习N

精通Node.js: 你应该阅读的书籍

最开始的几年,在应用服务器编程领域,我存在着一个选择.那时候,我已经远离了C一些时间,喜欢上JavaScript很长时间. 我喜欢JavaScript是因为JavaScript很轻,很优雅,很容易表达我的想法.并且如果我想实现一个可视化的内容,我可以在半小时内通过HTML Css写出一个漂亮的.生动的交互工具,然后把我任何想到的东西扔进去给别人看. 我很喜欢这样写javascript,虽然我知道道上这样写:JavaScript.但是javascript这样的写法让我觉得更加的轻快,虽然javas

Node.js中REST API使用示例——基于云平台+云服务打造自己的在线翻译工具

做为一个程序员可能在学习技术,了解行业新动态,解决问题时经常需要阅读英文的内容:而像我这样的英文小白就只能借助翻译工具才能理解个大概:不禁经常感慨,英文对学习计算机相关知识太重要了!最近发现IBM的云平台Blumemix,并且提供语言翻译的服务,感觉不错,就拿来研究学习一下:这里就分享一下我的研究学习过程,如何使用Node.js调用REST API打造自己的在线翻译工具,并演示如何把它发布到云平台上,让每个人都可以通过网络访问使用它. 应用效果展示 您可以通过点击效果图片的链接访问它. 构建一个

node.js中的回调

同步和阻塞:这两个术语可以互换使用,指的是代码的执行会在函数返回之前停止.如果某个操作阻塞,那么脚本就无法继续,这意味着必须等待. 异步和非阻塞:这两个术语可以互换使用,指的是基于回调的.允许脚本并行执行操作的方法.脚本无需等待某个操作的结果才能继续前进,因为操作结果会在事件发生时由回调来处理.使用异步方法,操作无需一个接一个地发生(自己注:就是并行了). @1 同步和阻塞的例子: function sleep(milliseconds) { var start = new Date().get

Node.js 回调函数

callback: A callback is a function that is passed as an argument to another function and is executed after its parent function has completed. Node.js 异步编程的直接体现就是回调. 异步编程依托于回调来实现,但不能说使用了回调后程序就异步化了. 回调函数在完成任务后就会被调用,Node 使用了大量的回调函数,Node 所有 API 都支持回调函数.

node.js学习笔记(二)——回调函数

Node.js 异步编程的直接体现就是回调. 那什么是回调呢?回调指的是将一个函数作为参数传递给另一个函数,并且通常在第一个函数完成后被调用.需要指明的是,回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应.回调函数在完成任务后就会被调用,Node 使用了大量的回调函数,Node 所有 API 都支持回调函数.例如,我们可以一边读取文件,一边执行其他命令,在文件读取完成后,我们将文件内容作为回调函数的参数返回.这样在执行代码时就没有阻

Node.js【5】核心模块

笔记来自<Node.js开发指南>BYVoid编著 第4章 Node.js核心模块 4.1.全局对象 Node.js中的全局对象是global,所有全局变量(除了global本身以外)都是global对象的属性.我们在Node.js中能够直接访问到对象通常都是global的属性,如console.process等. 永远使用var定义变量以避免引入全局变量,因为全局变量会污染命名空间,提高代码的耦合风险. process用于描述当前Node.js进程状态的对象,提供了一个与操作系统的简单接口.