文章目录
node.js 的 net 实现的 tcp 链接
node.js 内置 net 模块来实现面相 tcp 的套接字编程,并且简化了 tcp 协议底层的很多操作,可以快速搭建一个 tcp 服务器或打开一个 tcp 链接。
建立 tcp 服务器
以下代码可以在9000端口上开启一个简单的 tcp 服务器,并且监听获取的数据流并打印到所运行的终端上。
/** @file server.js */
const net = require("net");
const server = net.createServer();
server.on("connection", connection => {
connection.write("发送消息!over!\r\n");
connection.on("data", chunk =>
console.log("这是链接发送的片段:" + chunk.toString("utf8"))
);
});
server.listen(9000);
tcp 协议是可以双向通讯的,不论服务器或是客户端,都可以向对方发送信息,也都可以接收对方的消息。因此,tcp 的链接都继承自双工流。双工流同时实现了可读流和可写流,connection.write
就是可写流的方法,而 connection.on("data")
又是可读流的事件。
可以使用 telnet 等工具来访问上述代码运行的服务器,当然也可以自己写一个客户端。
建立 tcp 客户端
以下代码链接了本机上9000端口上的一个 tcp 服务器,在链接成功时输出已连接,发送数据并在接受第一条数据后关闭链接。
/** @file client.js */
const net = require("net");
const connection = net.createConnection({
host: "127.0.0.1",
port: 9000
});
connection.on("connect", () => {
console.log("已连接");
connection.write("你好,这里是客户端\r\n");
connection.on("data", chunk => {
console.log("这里是链接发来的片段:" + chunk.toString("utf8"));
connection.end();
});
});
同理,在客户端上建立的链接也继承了双工流,直接调用流的方法和注册事件,就可以实现通讯。
然后来走一遍流程:
启动服务器:
PS D:\document\my doc\test\tsScript\src\socket> node .\server.js
运行客户端:
PS D:\document\my doc\test\tsScript\src\socket> node .\client.js
已连接
这里是链接发来的片段:发送消息!over!
服务器输出:
PS D:\document\my doc\test\tsScript\src\socket> node .\server.js
这是链接发送的片段:你好,这里是客户端
栗子
简单的 tcp 聊天室
聊天室需要在服务器端存储来访问的链接,并广播信息。在客户端需要获取命令行的输入流并输出接收到的广播信息。
服务器端实现:
/** @file server.js */
const conf = {
name: "玄晓乌屋",
port: 9000
};
const server = require("net").createServer();
const users = [];
server.on("connection", connection => {
// 存储连接
users.push(connection);
// 广播信息
connection.on("data", msg =>
users.forEach(user => user !== connection && user.write(msg))
);
// 删除断开的链接
connection.on("end", () => users.splice(users.indexOf(connection), 1));
connection.on("error", _error => console.log("意外的错误。"));
});
server.listen(conf.port);
console.log("聊天室启动完毕。");
客户端代码
/** @file client.js */
const option = {
host: "127.0.0.1",
port: 9000
};
const connection = require("net").createConnection(option);
const rl = require("readline").createInterface({
input: process.stdin,
output: process.stdout
});
let username = "";
rl.question("请输入你的大名:", answer => {
username = answer;
connection.write(`${username} 进入了聊天室!`);
});
connection.on("data", chunk => console.log(chunk.toString("utf8")));
rl.on("line", input => connection.write(`${username}:${input}`));
// 此事件监听了命令行 【Ctrl + C】 输入
rl.on("SIGINT", () => {
connection.write(`${username} 退出了聊天室`);
rl.pause();
connection.end();
});
验证:首先启动服务器端,然后启动两个或多个客户端终端测试。
验证过程:略。
结果:
PS D:\document\my doc\test\tsScript\src\socket> node .\client.js
请输入你的大名:1号
2号 进入了聊天室!
hello 2号
2号:hello 1号
2号 退出了聊天室
远程终端连接
前几日整理了远程终端的简单连接,在此不赘述。
点此查看
发送http报文
因为 http 是基于 tcp 建立的无状态请求,固可以用 tcp 服务器来实现发送 http 的报文。
首先需要简要了解 http 报文头的相关信息,可以百度或者谷歌查询,然后建立一个简单的 http 响应报文文件,如下:
HTTP/1.1 200 OK
Content-Type:text/html; charset=utf-8
server:TCPServer
<html>
<head>
<title>test</title>
</head>
<body>
<h1>Hello World!还有中文咯。</h1>
</body>
</html>
第一行为状态,标识了 http 协议版本和状态值,第二行到空行都是响应头,空行后是响应数据。
然后就写一个响应此报文的 tcp 服务器如下:
/** @file server.js */
require("net")
.createServer(connection =>
require("fs")
.createReadStream("./http", { encoding: "utf8" })
.pipe(connection)
)
.listen(9000);
其中“./http”是报文文件,我没有设置后缀名。因为要渲染中文字符,还要注意相关字符集。
启动后使用游览器访问地址 “localhost:9000” 得到如下页面:
然后看看响应的报文:
和之前定义的一样,没有什么差别。
WebSocket
WebSocket 是 html5 新增的特性,使得 javascript 在游览器和服务器建立一个长连接,实现实时通信,虽然是实时通信,但是 WebSocket 却是基于 http 协议的,也就是说,它并不是直接发起一个 tcp 连接,而是建立了一个 http 的长连接。同时协议简写也为 ws ,从 https?://
这样的请求变为了 ws://
这样的请求。
在以下例子中,服务器端使用 ws 库。
建立 ws 服务器
首先需要安装 ws 模块:
npm i ws
然后书写 WebSocket 服务器端代码:
/** @file server.js */
const WebSocket = require("ws");
const server = new WebSocket.Server({ port: 9000 });
server.on("connection", ws => {
ws.on("message", msg => {
ws.send("收到消息:" + msg);
});
});
至此就完成了,但是我们不能用简单的 tcp 客户端或者简单的 http 请求去访问。
建立 ws 客户端
新建一个页面如下:
<!DOCTYPE html>
<html lang="ZH-CN">
<head>
<meta charset="UTF-8" />
<title>WebSocket</title>
</head>
<body>
<p>
<input type="text" />
<button>发送</button>
</p>
</body>
<script>
var input = document.getElementsByTagName("input")[0];
var btSend = document.getElementsByTagName("button")[0];
var ws = new WebSocket("ws://127.0.0.1:9000");
ws.onopen = function() {
btSend.addEventListener(
"click",
function(event) {
if (input.value !== "") {
ws.send(input.value);
}
},
false
);
};
ws.onmessage = function(messageEvent) {
console.log(messageEvent.data);
};
</script>
</html>
然后把这个客户端页面拖拽到游览器当中并打开控制台,注意 WebSocket 是 html5 的内容,所以需要足够高版本的游览器才能支持。
然后在输入框中输入一些文字,并点击发送按钮,就可以在控制台收到服务器端发来的消息了。
小结
WebSocket 让 js 和服务器端进行了长连接,是替换以前轮询的最好方式,但是 WebSocket 虽然已经足够简单了,但是长连接本身不够简单,重连,广播等操作都是 socket 常用的逻辑,还是需要大量代码重构,并且还有兼容性等等遗漏的问题没有解决。
WebSocket 可以实现的东西很多,参照 node 中的 tcp 连接相关的栗子就知道,实时通信,页面上的终端等,使用 WebSocket 都可以实现。
socket.io 库
socket.io 库是 js 和 nodejs 通讯的桥梁,使用简单,兼容性好(低版本游览器降低为轮询)。socket.io 支持自动重连、广播、命名空间和自定义事件等等大多常用通讯逻辑,是 web 通讯中非常实用的库。
socket.io 内容较多,可以上 官网查看更多内容。
建立 socket.io 服务器
/** @file server.js */
const io = require("socket.io")(9000);
io.on("connection", socket => {
socket.on("message", data => console.log(data));
});
虽然代码上看上去并没有比 WebSocket 少多少,但是它内部实现了重连功能。
建立 socket.io 客户端
首先需要建立一个网页文件,并且引入 socket.io 的网页客户端代码,socket.io 的网页客户端代码不用特地去寻找 cdn 等,socket.io 的服务器自带了,所以直接从某个已经建立的 socket.io 服务器获取即可,其路径为:host:port/socket.io/socket.io.js
因为前一节已经在本机的 9000 端口上建立了 socket.io 服务器,直接在网页文件中引入如下字段即可引入 socket.io 网页客户端:
<script src="http://localhost:9000/socket.io/socket.io.js"></script>
网页实现如下:
<!DOCTYPE html>
<html lang="ZH-CN">
<head>
<meta charset="UTF-8" />
<title>socket.io</title>
<script src="http://localhost:9000/socket.io/socket.io.js"></script>
</head>
<body>
<script>
var socket = io("http://localhost:9000");
setInterval(() => {
socket.send("test text!");
}, 1000);
</script>
</body>
</html>
把这个页面直接拖到浏览器中即可,不过要事先启动 socket 服务器。完全启动后服务器端的终端就会每秒打印网页客户端发来的信息。
需要注意的是,服务器端获取到的数据并不是一个流,所以不要指望使用流相关的操作。
socket.io 本身是一个库,官网上有详细文档,本文只是小试牛刀,不再赘述。
总结
nodejs 使得 js拥有了在服务器端运行的能力,同时 node 的网络模块和 html5 的 WebSocket 模块也大大增强了 js 在服务端的通信能力。