后端服务的一个重要部分是通过套接字进行通信的能力。套接字允许一个进程通过一个IP地址和端口与另一个进程通信。当你实现对运行在同一台服务器上的两个不同进程的进程间通信(IPC)或访问一个完全不同的服务器上运行的服务时,这很有用。Node.js提供net模块,它允许你既创建套接字服务器有创建可以连接到套接字服务器的客户端。对于安全连接,Node.js提供tls模块,它可以让你实现安全TLS套接字服务器和客户端
1,了解网络套接字
网络套接字是跨整个计算机网络流动的通信的端点。套接字位于HTTP层下面并提供服务器之间的点对点通信。几乎所有的互联网通信都是基于在互联网上两点之间传输数据的Internet套接字。
套接字使用套接字地址开展工作,这是IP地址和端口的组合。在套接字连接中有两种类型的点:一类是服务器,它监听连接;另一类是客户端,它打开一个到服务器的连接。服务器和客户端都需要一个唯一的IP地址和端口的组合。
Node.js net模块套接字使用传输控制协议(TCP)通过发送原始数据来通信。这个协议负责包装数据并保证它从一点成功发送到另一点。Node.js套接字实现了Duplex(双工流),它允许你读取和写入服务器与客户端之间的数据流。
套接字是http模块的底层结构。如果你不需要处理如GET和POST的Web请求的功能,你只需要点对点的传输数据,那么使用套接字就能为你提供一个轻量级的解决方案和更多的控制。
在与同一台计算机上运行的其他进程进行通信时,套接字也很方便。进程不能直接共享内存,所以如果你想从另一个进程访问在一个进程中的数据,则可以在每个进程中打开同一个套接字来读取和写入两个进程之间的数据。
2,了解TCP服务器和Socket对象
要在Node.js应用程序中开始使用net模块,你首先要了解TCP Server和Socket对象。这些对象提供了启动TCP服务器来处理请求并实现TCP套接字客户端来对套接字服务器发出请求的框架。一旦你了解了这些对象的事件,属性,方法和行为,就将很容易实现自己的TCP套接字服务器和客户端。
以下各节介绍了net.Socket和net.Server对象的目的和行为。你将了解它们最重要的事件,属性和方法。
2.1,net.Socket对象
Socket对象同时在套接字服务器和客户端套接字上创建,并允许数据在它们之间来回写入和读取。Socket对象实现Duplex流,所以它提供了Writable和Readable数据流提供的所有功能。例如,你可以使用write()方法把数据以流式写入服务器或客户端,而data事件处理程序把来自服务器或客户端的数据传输。
在套接字客户端,当你调用net.connect()或net.createConnection()时,Socket对象在内部创建。这个对象是为了表示到服务器的套接字连接。你使用Socket对象来监控该连接,将数据发送到服务器并处理来自客户端的响应。在Node.js net模块中没有明确的客户端对象,因为Socket对象充当完整的客户端,让你能够发送/接收数据并终止连接。
在套接字服务器上,当客户端连接到服务器时,Socket对象被创建,并被传递到连接事件处理程序。这个对象是为了表示对客户端的套接字连接。在服务器上,使用Socket对象来监控客户端连接,并对客户端发送和接收数据。
要创建一个Socket对象,可使用下列方法之一:
- net.connect(options,[connectionListener])
- net.createConnection(options,[connectionListener])
- net.connect(port,[host],[connectListener])
- net.createConnection(post,[host],[connectListener])
- net.connect(path,[connectListener])
- net.createConnection(path,[connectListener])
所有的调用都将返回一个socket对象,唯一的区别是它们接受的第一个参数。所有这些方法的最后一个参数都是一个当连接对服务器打开时执行的回调函数。注意,对于每一种方法,都有一种net.connect()的形式和net.createConnection()的形式。这些形式的工作方式完全相同。
创建一个Socket对象的第一种方式是通过一个options参数,它是一个包含了定义套接字连接的属性的对象。下表列出了创建Socket对象时可以指定的属性。第二种方法接收port和host的值,作为直接的参数,如下表所述。第三个选项接收指定文件系统位置的path参数,这个位置是一个Unix套接字在创建Socket对象时使用的。
属性 | 说明 |
port | 客户端应连接到的端口号。此选项是必需的 |
host | 客户端应该连接到的服务器的域名或IP地址。默认为localhost |
localAddress | 客户端应该绑定的用于网络连接的本地IP地址 |
allowHalfOpen | 一个布尔值,如果为true,则表示当套接字的另一端发送一个FIN数据包时,该套接字将不会自动发送一个FIN数据包,从而是Duplex流的一般保持开放。默认为false |
一旦Socket对象被创建,它就提供了在连接到服务器的生命周期中发生的几个事件。例如,套接字连接时触发的connect事件,当有数据在Readable流中待读出时,发出data事件,以及到该服务器的连接关闭时发出的close事件。当你实现套接字服务器时,可以注册这些事件被发出来时要执行的回调函数,用来处理打开和关闭套接字,读/写数据等。下表列出了可以对Socket对象触发的事件。
事件 | 说明 |
connect | 成功建立与服务器的连接时发出。回调函数不接受任何参数 |
data | 在套接字上受到数据时发出。如果没有数据事件处理程序被连接,那么数据可能会丢失。回调函数必须接受一个Buffer对象作为参数,它包含从套接字读取的数据的块。例如:function(chunk){} |
end | 当服务器通过发送一个FIN终止连接时发出。回调函数不接受任何参数 |
timeout | 由于不活动,因此到服务器的连接超时时发出 |
drain | 在写入缓冲区变为空时发出。你可以使用此事件截回被写入套接字中的数据流。回调函数不接收任何参数 |
error | 在套接字连接上发生错误时发出。回调函数应该接受错误的唯一参数。例如:function(error){} |
close | 套接字已完全关闭时发出,它可能是由一个end()方法关闭的,或者是因为发生错误而关闭。回调函数不接受任何参数 |
Socket对象还包括了几种方法,让你能够做诸如读取和写入套接字,以及暂停或结束数据流之类的事情。其中许多方法是从Duplex流对象继承的,所以你对它们应该是熟悉的。下表中列出了可用在Socket对象上的方法。
方法 | 说明 |
setEncoding([encoding]) | 当这个函数被调用时,从套接字的流返回的数据是被编码的String,而不是一个Buffer对象。 |
write(data,[encoding],[callback]) | 把一个数据缓冲区或字符串写入套接字的Writable流,如果指定了编码,就使用该编码。一旦数据被写入,就执行回调函数 |
end([data],[encoding]) | 写入一个数据缓冲区或字符串到套接字的Writable流,然后刷新流并关闭连接 |
destory() | 强制套接字连接关闭。你应该只需要在出现故障时使用这个 |
pause() | 暂停在套接字的Readable流上发出data事件。这可以让你中止对流上传数据 |
resume() | 恢复在套接字的Readable流上发出data事件 |
setTimeout(timeout,[callback]) | 指定一个单位为毫秒的timeout,它是套接字处于非活动状态时,服务器在发出超时事件之前会等待的时间。回调函数作为一次事件监听器被触发。 |
setNoDelay([noDelay]) | 禁用/启用在发送数据之前缓冲它的Nagle算法。将此设置为false来禁用数据缓冲 |
setKeepAlive([enable],[initialDelay]) | 在连接上启用/禁用保持活动的功能。可选的initialDelay参数指定以毫秒为单位的时间,表示在套接字中发送第一个保持活动数据包前的闲置时间 |
address() | 返回绑定的地址,该地址族的名字和套接字的端口,正如操作系统所报告的那样。返回值是一个对象,它包含port,famile和address属性。 |
unref() | 如果此套接字是事件队列中的唯一事件,则允许Node.js应用程序终止 |
ref() | 重新应用套接字,以使如果此套接字是事件队列中唯一的东西,Node.js应用程序不会终止 |
Socket对象还提供了可以访问以获得关于该对象的信息的几个属性,例如,套接字通信的地址和端口,被写入的数据流,以及缓冲区大小。下表列出了可用的套接字对象的属性。
属性 | 说明 |
bufferSize | 当前已缓冲并等待写入套接字的流中的字节数 |
remoteAddress | 套接字连接到的远程服务器的IP地址 |
remotePort | 套接字连接到的远程服务器的端口 |
localAddress | 远程客户端用于套接字连接的本地IP地址 |
localPort | 远程客户端用于套接字连接的本地端口 |
bytesRead | 由套接字读取的字节数 |
bytesWritten | 由套接字写入的字节数 |
为了说明流过一个Socket对象的数据,下面的代码显示了实现客户端上的Socket对象的基本知识:
var net = require('net');
var client = net.connect({port:8017,host:'localhost'},function(){
console.log('Client connected');
client.write('Some Data\r\n');
});
client.on('data',function(data){
console.log(data.toString());
client.end();
});
client.on('end',function{
console.log('Client disconnected');
});
2.2,net.Server对象
你可以使用net.Server对象创建一个TCP套接字服务器,并开始监听对它的连接,你将能够读取和写入数据。当你调用net.createServer()时,Server对象在内部创建。这个对象用来表示套接字服务器并处理监听连接,然后发送和接收那些连到服务器连接的数据。
当服务器接收到一个连接的时候,服务器会创建一个Socket对象,并把它传递给正在监听的任何事件处理程序。由于Socket对象实现了Duplex流,因此你可以将它与write()方法连用,以把数据的写入传回给客户端,并用一个数据事件处理程序来传输来自客户端的数据。
要创建一个服务器对象,使用net.createServer()方法:
net.createServer([options],[connectionListener])
options参数是一个对象,它指定创建套接字Server对象时要使用的选项。下表列出了Server对象的选项。第二个参数是connection事件的回调函数,它在接收到连接时被执行。connectionListener回调函数被传递给用于连接中的客户端的Socket对象。
一旦Server对象被创建,它就提供了在该服务器的生命周期中触发的一些事件。
事件 | 说明 |
listening | 当服务器调用listen()方法在一个端口上开始监听时发出。回调函数不接受任何参数 |
connection | 当连接从套接字的客户端收到时发出。回调函数必须接受一个参数,它是一个Socket对象,表示到连接中的客户端的连接。例如:function(client){} |
close | 当服务器正常关闭或由于出错而关闭时发出。除非所有的客户端连接都已经结束,否则该事件不发出 |
error | 当发出错误时发出。 |
Server对象还包括了几种方法,让你从做与读取和写入套接字,以及暂停或结束数据流一样的事情。其中许多方法是从Duplex流对象继承的,所以你对它们应该是熟悉的。下表列出了可用在Socket对象上的方法。
方法 | 说明 |
listen(port,[host],[backlog],[callback]) | 打开服务器上的端口。并开始监听连接 |
listen(path,[callback]) | 同上,只是启动的是一个Unix套接字,监听的是指定的文件系统路径上的连接 |
listen(handle,[callback]) | 同上,只是一个到Server或Socket对象的句柄,它具有指向该服务器上的文件的描述符句柄的底层_handle成员。它假定文件描述符指向一个已经绑定到端口的套接字文件 |
getConnections(callback) | 返回当前连接到服务器的连接数量。callback在连接数被计算出来时被执行,并接收一个error参数和count参数。 |
close([callback]) | 阻止服务器接收新的连接。当前连接被允许保留,直到它们完成 |
address() | 返回绑定的地址,该地址族的名字和套接字的端口,正如操作系统所报告的一样。返回值是一个对象,它包含port,family和address属性。例如:{port:8107,famile:'IPV4',address:'127.0.0.1' } |
unref() | 如果服务器是在事件队列中的唯一事件,则调用此方法允许Node.js应用程序终止 |
ref() | 引用套接字,以使如果服务器是在事件队列中的唯一的东西,Node.js应用程序不会终止 |
Server对象还提供了maxConnections属性,它允许你设置服务器拒绝连接之前接受的最大连接数目。如果一个进程使用child_process.fork()被派生给一个子进程处理,则你不应该使用这个选项。
下面的代码显示了实现Server对象的基础知识:
var net = require('net');
var server = net.createServer(function(client){
console.log('Client connected');
client.on('data',function(data){
console.log('Client sent ' + data.toString());
});
client.on('end',function(){
console.log('Client disconnected');
});
client.write('Hello');
});
server.listen(8107,function(){
console.log('Server listening for connections');
});
3,实现TCP套接字服务器和客户端
现在你理解了net.Server和net.Socket对象,你就可以深入下去,并实现一些Node.js TCP客户端和服务器。这将引导你完成在Node.js中实现基本的TCP客户端和服务器的过程。
3.1,实现TCP套接字客户端
在最基本的层面上,实现TCP套接字的客户端包括创建连接到服务器的Socket对象,然后将数据写入服务器,并处理返回的数据。此外,你应该建立套接字,使它也可以处理错误,缓冲区已满,以及超时等情况。本节讨论使用Socket对象实现套接字客户端所包括的步骤。
tcp_server.js
var net = require('net');
var server = net.createServer(function(client){
console.log('Client connection:');
console.log(' local = %s:%s',client.localAddress,client.localPort);
console.log(' remote = %s:%s',client.remoteAddress,client.remotePort);
client.setTimeout(500);
client.setEncoding('utf8');
client.on('data',function(data){
console.log('Received data from client on port %d:%s',client.remotePort,data.toString());
console.log(' Bytes received: '+client.bytesRead);
writeData(client,'Sending: '+data.toString());
console.log(' Bytes sent:' + client.bytesWritten);
});
client.on('end',function(){
console.log('Client disconnected');
server.getConnections(function(err,count){
console.log('Remaining Connections: ' + count);
});
});
client.on('error',function(err){
console.log('Socket Error: ',JSON.stringify(err));
});
client.on('timeout',function(){
console.log('Socket Timed out');
});
});
server.listen(8107,function(){
console.log('Server listening: '+JSON.stringify(server.address));
server.on('close',function(){
console.log('Server Terminated');
});
server.on('error',function(err){
console.log('Server Error : '+JSON.stringify(err));
});
});
function writeData(socket,data){
var success = !socket.write(data);
if(!success){
(function(socket,data){
socket.once('drain',function(){
writeData(socket,data);
});
})(socket,data);
}
}
tcp_client.js
var net = require('net');
function getConnection(connName){
var client = net.connect({port:8107,host:'localhost'},function(){
console.log(connName + ' Connected:');
console.log(' local=%s:%s',this.localAddress,this.localPort);
console.log(' remote = %s:%s',this.remoteAddress,this.remotePort);
this.setTimeout(500);
this.setEncoding('utf8');
this.on('data',function(data){
console.log(connName + " From Server: " + data.toString());
this.end();
});
this.on('end',function(){
console.log(connName + 'Client disconnected');
});
this.on('error',function(err){
console.log('Socket Error: ',JSON.stringify(err));
});
this.on('timeout',function(){
console.log('Socket Timed Out');
});
this.on('close',function(){
console.log('Socket Closed');
});
});
return client;
}
function writeData(socket,data){
var success = !socket.write(data);
if(!success){
(function(socket,data){
socket.once('drain',function(){
writeData(socket,data);
});
})(socket,data);
}
}
var Dwarves = getConnection("Dwarves");
var Elves = getConnection("Elves");
var Hobbits = getConnection("Hobbits");
writeData(Dwarves,"More Axes");
writeData(Elves,"More Arrows");
writeData(Hobbits,"More Pipe Weed");
server.视图
client 视图