理解网络编程-1

293 阅读3分钟

该文章阅读需要5分钟,更多文章请点击本人博客halu886

前言

Node是一个面向网络而生的平台,具有事件驱动,无阻塞,单线程等特性。具有良好的可伸缩性,适合在分布式式网络中扮演各式各样的角色。同时提供的网络基础API非常贴合网络,非常适合基于基础API构建灵活的网络服务。

利用Node可以很容易的搭建Web服务。像其他语言都需要Web服务器作为容器。如ASP,ASP.NET需要IIS作为服务器。PHP需要搭建Apache或Nginx环境等。JSP需要Tomcat等等。但是对于Node来说,只需要仅仅几行代码就可以构建服务器,不需要额外的容器。

Node提供了net,dgram,http,https这4个模块,分别用来处理TCP,UDP,HTTP,HTTPS,适用于服务端和客户端。

构建TCP服务

TCP在网络中十分常见,目前大部分应用都是构建在TCP上的。

TCP

TCP全称是传输控制协议,在OSI模型(分为七层,物理层,数据链路层,网络层,传输层,会话层,表现层,应用层)位于传输层协议,典型的HTTP,SMPT,IMAP等就是构建在TCP上。

1

TCP是典型的面向连接的协议,特征是在传输之前需要3次握手形成会话。

2

只有会话形成,客户端和服务端才能开始传输数据。创建过程中,服务端和客户端分别提供一个的套接字共同形成一个链接。服务端和客户端通过套接字形成两者链接的操作。

创建TCP服务器端

现在我们来创建一个TCP服务端接受网络请求。

var net = require('net');

var server = net.createServer(function(socket){
     socket.on('data',function(data){
          socket.write("你好");
     })

     socket.on('end',function(){
          console.log('连接断开');
     })
     socket.write("hello world!\n");
});

server.listen(8124function(){
     console.log('server bound');
})

我们通过net.createServer()创建一个TCP服务器,server.listen表示connection的侦听器,也可以用如下方式:

var server = net.createServer();

server.on('connection',function(socket){
     // 新的连接
})

server.listen(8124);

我们可以用telnet作为客户端和刚刚创建的简易服务器进行会话交流。

$ telnet 127.0.0.1 8124
Trying 127.0.0.1
Connected to localhost.
Escape character is '^]'.
hello world
hi

除了端口外,我们还可以对Domain Socket进行监听。

server.listen('/tmp/echo.sock');

通过nc工具进行上述的构建的TCP服务的会话测试。

$ nc -U /tmp/echo.sock
hello world
hi

也能通过net模块自行构建客户端进行会话。

var net = require('net')

var client = net.connect({port:8124},function(){
     console.log('client connected');
     client.write('world!\r\n');
});

client.on('data',function(data){
     console.log(data.toString());
     client.end();
})

client.on('end',function(data){
     console.log('client disconnected');
})

将以上客户端存为client.js,执行如下

$ node client.js
client connected
hello world!

你好
client disconnected

其结果与使用nc和Telnet的结果并无区别。如果是Domain Socket,填写path即可。

var client = net.nonnect({path:'/tmp/echo.sock'});

TCP服务的事件

以上例子中,事件分为服务器事件和连接事件

服务器事件

对于通过net.createServer()创建的服务器而言,它是一个EventEmitter实例,它的自定义事件如下几种。

  • listening:再调用server.listen()绑定端口或者Domain Socket后触发。
  • connect:每个客户端连接到服务端时触发,简洁用法可以用net.createServer(),最后一个参数传递。
  • close:当服务端关闭时触发,当调用sever.close()后,停止接受新的客户端套接字连接,同时等待连接断开后触发该事件。
  • error:当服务器发生异常,则会触发这个事件。例如侦听一个正在使用中的端口,则会触发这个事件。如果没有侦听error事件,服务器则会抛出异常。

连接事件

服务器可以和多个客户端保持连接,对于每个连接来说都存在一个可读可写Stream流用于服务端到客户端的通信。可以侦听data事件进行读取另一端的数据,通过write()像另一端发送数据。

  • data:当一端通过write发送数据,另一端则会触发data事件。接受到数据则是write传入的数据。
  • end:当任何一段发送Fin数据包,则会触发这个事件。
  • connect:用于客户端连接服务端,当套接字与服务端连接成功时触发。
  • drain:当任意一端调用write()时,当前这端触发该事件。
  • error:异常发生时,触发该事件。
  • close:当套接字完全关闭时,触发该事件。
  • timeout:当连接闲置了一段时间后,触发该事件。

另外,TCP套接字是一个可读可写的Stream流,可以通过pipe()实现管道操作。

var net = require('net');
var server = net.createServe(function(socket){
     socket.write('Echo server\r\n');
     socket.pipe(socket);
})

server.listen(1337,'127.0.0.1');

不过,TCP在传输小数据包时存在一个优化策略。Nagle算法,如果不存在这个算法的话,网络传输中全是相同的小数据包,十分浪费网络资源。这个算法是将数据缓存在一个缓冲区到一定数据量或一定时间量时再一起发出去,所以小数据包会被Nagle算法合并,能够起到节省宽带的作用。不过这样的副作用则是有可能数据包会被延迟发送。

在Node中,TCP默认采用了启用Nagle算法,可以通过Socket.setNotDaley(true)去掉Nagle算法。使得write()后立即将数据发出到网络中。

不过,通过write()写入数据后会触发data事件,关闭Nagle算法后,也不意味这每次执行write方法都会触发data事件。另一端将多个小数据包合并,然后只触发一次data事件。

构建UDP服务

UDP称为用户数据包协议,和TCP同属于网络传输层。和TCP最大的不同是UDP不是面向连接。TCP连接一旦建立,会话都是基于连接完成,并且每个不同的连接都需要不同的套接字。但在UDP中,一个套接字可以和多个UPD服务端会话。虽然提供的是面向事务的简单不可靠传输服务,在网络情况差的情况下丢包严重,但是无需连接,资源消耗低,处理快速且灵活。所以常常应用于丢一两个包也不影响的场景,例如音频,视频。UDP目前引用非常广泛,CDN服务就是基于UDP实现的。

创建UPD套接字

创建UPD套接字十分简单,UDP套接字一旦建立,既可以作为客户端发送数据,也可以作为服务端接收数据。

var dgram = require('dgram');
var socket = dgram.createSocket('udp4');

创建UDP服务端

若想让UPD套接字接受网络消息,只需要用dgram.bind(port,address)对网卡和端口进行绑定即可。

var dgram = require("dgram");

var server = dgram.createSocket("udp4");

server.on("message",function(msg,rinfo){
     console.log(msg +" from " + rinfo.address + ":" +rinfo.port);
})

server.on("listening",function(){
     var address = server.address();
     console.log(address.address+ ":" + address.port);
})

server.bind(41234);

创建UDP客户端

创建一个UDP客户端与客户端通信。

var dgram = require('dgram');

var message = new Buffer('hello world');

var client = dgram.createSocket('udp4');

client.send(message,0,message.length,41234,"localhost",function(err,bytes){
     client.close();
})

保存client.js并执行。

$ node server.js
server listening 0.0.0.0:41234
server got:hello world from 127.0.0.1:58682

当套接字用在客户端时,使用send()方法发送消息到网络中。send()方法参数如下:

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

这些参数分别为要发送的Buffer,偏移位,长度,端口,地址以及回调。它与TCP相比,参数相对复杂一点,但是可以随意发送到网络中服务端,但是如果TCP需要重新发送数据另一个服务端的话,则需要通过套接字构造一个新的连接了。

UDP套接字事件

UDP套接字使用起来相对容易一点,只是一个EventEmitter的实例,而非Steam的实例。

  • message:当UDP套接字侦听网口端口后,接收到消息后触发该事件,携带的数据为Buffer数据和和一个远程地址信息。
  • listening:当UDP套接字开始侦听时触发该事件。
  • close:调用close时触发该事件,不在触发message事件,重新侦听地址和端口则可重新触发。
  • error:当异常发生时触发该事件,如果不侦听该事件,则异常抛出,使进程退出。

以上知识点均来自<<深入浅出Node.js>>,更多细节建议阅读书籍:-)