Node.js为javascript语言提供了一个在服务端运行的平台,它以其事件驱动,非阻塞I/O机制使得它自己很是适合开发运行在在分布式设备上的I/O密集型应用,分布式应用要求Node.js必须对网络通讯支持友好,事实上Node.js也提供了很是强大的网络通讯功能,本文就主要探讨如何使用Node.js进行网络编程。javascript
首先,网络编程的概念是"使用套接字来达到进程间通讯的目的"。一般状况下,咱们要使用网络提供的功能,能够有如下几种方式:html
1.使用应用软件提供的网络通讯功能来获取网络服务,最著名的就是浏览器,它在应用层上使用http协议,在传输层基于TCP协议;java
2.在命令行方式下使用shell 命令获取系统提供的网络服务,如telnet,ftp等;node
3.使用编程的方式经过系统调用获取操做系统提供给咱们的网络服务。shell
本文主要目的就是要探讨如何在Node.js中经过编程来获取操做系统提供的网络服务来达到不一样主机进程间通讯。如今回过头来在看看网络编程的概念,这里涉及到一个套接字(socket)的概念,所谓套接字,其实是两个不一样进 程间进行通讯的端口(这里的端口有别于IP地址中经常使用的端口),它是对网络层次模型中网络层及其下面各层操做的一个封装,为了让开发者可以使用各类语言调用操做系统提供的网络服务,在不一样服务端语言中都使用了套接字这个概念,开发者只要得到一个套接字(socket),就可使用套接字(socket)中各类方法来建立不一样进程之间的链接进而达到通讯目的。一般状况下,咱们使用如下网络层次模型,编程
所谓的socket(套接字)就是将操做系统中对于传输层及其如下各层中对于网络操做的处理进行了封装,而后提供一个socket对象,供咱们在应用程序中调用这个对象及其方法来达到进程间通讯的目的。api
基于以上概念,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:做用同上
readable和writeable,当使用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客户端和服务端的通讯。