继上一篇介绍了基于Nodejs的http服务和文件操做的内容后,本篇内容主要介绍前端工程师在平常工做中较少接触到的TCP相关知识内容,从Nodejs的TCP模块入手,经过实例看看TCP是怎么一回事。javascript
tcp是什么?
tcp是一种面向链接的、可靠的、基于字节流的传输层通讯协议,TCP层是位于IP层之上,应用层之下的中间层。与咱们接触最频繁的http请求就是基于它,相比于http,它没有超时时间,正常状况下它能够一直保持链接。前端
tcp的特色
1.三次握手java
不少人都知道TCP经典的”三次握手四次挥手“,如此繁琐的确认发包所带来了什么呢? node
首先要明确三次握手到底要干什么。客户端要与服务器进行数据交换,可是服务器在云端,客户端也不知道服务器在不在线,因此要寻找一种方式核验一下远端的服务器在不在线,”三次握手“正是核验的方式。nginx
再来看看步骤,先是客户机发起一个请求链接包,代表本身要链接到服务器上,而后服务器收到请求后,会回复一个请求,这个请求会作两件事,先要告诉远端的客户机你刚刚连了个人那步操做我收到了,还要肯定本身也能连上远端的客户机。客户端接收到后,先知道了本身发过的第一步请求没问题,知道本身能够放心的给服务器发请求了,可是服务器殊不知道能不能给客户端放心的发数据,因此客户端还要发起一次回答服务器的请求,此次请求的目的就是让服务器肯定本身是能够连上客户端的。三次握手完成了,能够开心的发送数据了。git
2.四次挥手github
四次挥手我理解更多的意义是在于机器资源的释放。web
来看看步骤,当客户端与服务端完成数据传输后,客户端发出请求包,代表个人数据传输完了,可是服务器并无传输完成,因此会一边传输本身的数据一边给客户端确认收到结束的标志,从而释放本身与客户端的相关等待资源,而后服务端继续发本身未完成的数据,发送完成后,再次发送一个请求包,服务端的数据也发完了,客户端此时收到请求包后进行确认,客户端确认完成回复客户端,链接可断开,资源释放。typescript
为何更多的意义是一种资源释放的做用呢,若是两端把数据都发完了后均只发送一次包告诉对方数据完了,而不发送给对方确认包能够吗?我理解是能够的,可是为了保证发的第一次结束确认包能获得对方回复确实收到了而不是丢失,因此各自要多一次确认包,若是丢失了回传的确认包,则发起的一方不论是过去时候丢了仍是回来的时候丢了都会从新发起确认,从而耗费资源。shell
Hello World入门
使用Nodejs的net模块来创建一个TCP服务器。
const net = require('net');
net.createServer(function(socket){ console.log('recive a connect'); console.log(socket);}).listen(8000, function(){ console.log('TCPServer listen on 8000');})
使用telnet发起TCP请求进行测试。
telnet 127.0.0.1 8000#Trying 127.0.0.1...#Connected to localhost.#Escape character is '^]'.
telnet命令简介
telnet命令通常用来作远程登陆,跟ssh命令一个做用,属因而TCP/IP协议族中的一员,是Internet远程登录服务的标准协议和主要方式。可是为何不多见甚至没听过telnet还能远程登陆呢?是由于telnet采用明文传送报文,安全性很差,因此如今用的多且熟悉的远程登陆都是经过ssh(security shell)来完成的。
而telnet命令还有一个很是强大的做用,用来肯定远程服务端口是否开启可用,它的实质其实就是发起一个的数据包而后经过可否接收到回传的包来进行测试。
价值过亿的AI机器人核心代码
在上面tcp服务器的代码之上稍稍修改一下,一段价值过亿的AI机器人代码写好了。
// tcpServer.jsconst net = require('net');
net.createServer(function(socket){ console.log('recive a connect'); /* * @description 添加事件监听器,当client发送数据给服务器时,事件会触发 */ socket.on('data', function (data) { const message = data.toString().trim(); let response = `机器人:${message}`; if (response.indexOf('?') > -1) { response = String.prototype.slice.apply(response, [0, -2]) + '!'; } // 过滤空消息 if (message) { socket.write(response, function(){ console.log(`${response} has send!`); }) } });}).listen(8000, function(){ console.log('TCPServer listen: 8000');})
使用node tcpServer.js启动TCP服务器,进行输入内容测试。
这里由于在telnet命令下,这里输入中文会乱码,因此笔者使用nc命令进行测试,nc是一个更强大的网络工具命令,被称之为网络工具界的”瑞士军刀“,这里只用了简单的探测功能,笔者以前使用过它作端口扫描与文件传输,强大到使人惊艳,后续有机会专门介绍一下这个命令,没有安装nc的能够先安装一下,固然若是你的机器telnet下不乱码的话,也可使用telenet进行测试。
实现一个简易版的“微信”
这里简单作一个相似于微信实时通信工具,来看看 TCP连接下多客户端之间的通讯是怎么作的。
服务器端代码
const net = require('net');
// 缓存在线的用户const users = {};
// 建立TCP服务器net.createServer(function(socket){ /* 发送数据 */ socket.write(JSON.stringify({ type: 'system', message: '你已经成功链接了!' }));
/* 监听data事件 */ socket.on('data',function(data){ const msg = JSON.parse(data.toString()); if (msg.type === 'registe') { // 注册用户,暂存socketj进全局变量users users[msg.userId] = socket; } else if(msg.type === 'singleMsg') { // 发送消息 if (users[msg.targetId]) { //首先查看全局变量里是否存在用户 users[msg.targetId].write(data, function(){ console.log(`${JSON.stringify(msg)} 已经被发送!`); }); } else { //不存在则认为不在线 const resMsg = JSON.stringify({ type: 'error', message: `发送失败了~,${msg.targetId}用户不在线!` }) users[msg.userId].write(resMsg, function(){ console.log(`${JSON.stringify(resMsg)} 已经被发送!`); }); } } })}).listen(8000,function(){ console.log('TCPServer listen: 8000');})
客户端代码:
/** * 构建TCP客户端 */const client = require('net').Socket();
function genClient(userId) { return new Promise(resolve => { // 设置链接的服务器 client.connect(8000, '127.0.0.1', function () { // 向服务器发送数据 client.write(JSON.stringify({"type": "registe", userId })); resolve(client); })
// 监听服务器传来的data数据 client.on('data', function (data) { let msg; try { msg = JSON.parse(data.toString()) } catch(err) { msg = data.toString(); } const { type, userId, message } = msg; // 判断是哪类消息,除了系统消息与错误消息均认为用户消息 if (type === 'error') { console.log(message); } else if (type === 'system') { console.log(`系统消息:${message}`); } else { console.log(`${userId}用户说:${message}`); } }) })}
module.exports = genClient;
const genClient = require('./tcpClient');
const userId = 'userId:100001';genClient(userId).then(client => { const msg = { userId, type: 'singleMsg', targetId: 'userId:100002', message: '你好' }; // 用定时器模拟用户在发送消息 setInterval(()=>{ client.write(JSON.stringify(msg)); }, 3000)})
代码运行结果:
这里模拟了两个用户之间的即时消息通信,比较简单。简单说一下思路,当一个新用户来的时候,将其带来的userId做为主键,存进全局变量中,当有另外一用户要发消息时,先从在线用户缓存之中查找其带来的接收方ID中是否存在,存在即表明在线,能够发送消息,不然告知用户,接收方不在线。
本文所用的的代码都可在下面找到,有兴趣的clone下来动手练习。
文章用到的代码都可在此获取:
https://github.com/FantasyGao/Practice-book/tree/master/nodejs/tcp
若是你、喜欢探讨技术,或者对本文有任何的意见或建议,你能够扫描下方二维码,关注微信公众号“鱼头的Web海洋”,随时与鱼头互动。欢迎!衷心但愿能够碰见你。
本文分享自微信公众号 - 鱼头的Web海洋(krissarea)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。