Nodejs使用Net模块创建TCP服务器和客户端

1、前言

这里不得不先说下TCP和websocket通讯的区别:

区别
按照OSI网络分层模型,IP是网络层协议,TCP是传输层协议,而HTTP是应用层的协议。在这三者之间,SPDY和WebSocket都是与HTTP相关的协议,而TCP是HTTP底层的协议。

WebSocket则提供使用一个TCP连接进行双向通讯的机制,包括网络协议和API,以取代网页和服务器采用HTTP轮询进行双向通讯的机制。
本质上来说,WebSocket是不限于HTTP协议的,但是由于现存大量的HTTP基础设施,代理,过滤,身份认证等等,WebSocket借用HTTP和HTTPS的端口。

由于使用HTTP的端口,因此TCP连接建立后的握手消息是基于HTTP的,由服务器判断这是一个HTTP协议还是WebSocket协议。
WebSocket连接除了建立和关闭时的握手,数据传输和HTTP没丁点关系了。

一句话总结下

websocket建立握手是通过http的,后面真正传输不再进行HTTP的交互,而是开始websocket的数据帧协议。实现客户端与服务端的数据交换

那么使用Nodejs创建一个TCP服务器呢?

2、实现要求

服务器可以向客户端群发信息,也要支持定向发送信息

3、服务端简单实现

net模块是Nodejs内置的基础网络模块,通过使用net,可以创建一个简单的tcp服务器

server.listen(port[,host][,backlog][,callback])
port参数为需要监听的端口号,参数值为0的时候将随机分配一个端口号。
host为服务器地址。
backlog为等待队列的最大长度
callback为回调函数

回调事件:
connection:当前新的链接创建时触发,回调函数的参数为socket连接对象,同net.createServer的第二个函数参数。
close:TCP服务器关闭的时候触发,回调函数没有参数
error:TCP服务器发生错误的时候触发,回调函数的参数为error对象
listening:调用server.listen时触发的回调

// 引入NET
const net = require('net');

// 创建TCP服务器
const server = net.createServer(function (client) {
    console.log('someones connects');

    // 接收客户端的数据
    client.on('data', function (data) {
        setTimeout(() => {
            client.write('服务端发送通知');
        }, 3000);
        console.log('服务端接收到来自客户端的数据:', data.toString('utf-8'));
    });

    // 客户端连接关闭
    client.on('close', function (err) {
        console.log('客户端下线');
    });

    // 客户端连接错误
    client.on('error', function (err) {
        console.log('客户端连接错误');
    });
});

// 监听客户端的连接
server.listen(
    {
        port: 9001,
        host: '127.0.0.1',
    },
    function () {
        console.log('server start listening');
    }
);

//设置监听时的回调函数
server.on('listening', function () {
    const { address, port } = server.address();
    console.log('server is running: ' + address + ':' + port);
});

//设置关闭时的回调函数
server.on('close', function () {
    console.log('sever closed');
});

//设置出错时的回调函数
server.on('error', function () {
    console.log('sever error');
});

4、客户端简单实现

const net = require('net');

const sock = net.connect(
    {
        port: 9001,
        host: '127.0.0.1',
    },
    function () {
        console.log('connected to server!');
    }
);

// 连接成功
sock.on('connect', function () {
    console.log('connect success');
    // 向服务端发送数据
    sock.write("测试数据", 'utf8');
});

// 接收来自服务端的信息
sock.on('data', function (data) {
    console.log('接收到来自服务端的信息:', data.toString('utf-8'));
});

// 有错误发生调用的事件
sock.on('error', function (e) {
    console.log('socket error', e);
});

// socket关闭的事件
sock.on('close', function () {
    console.log('socket close');
});

// 对方发送了关闭数据包过来的事件
sock.on('end', function () {
    console.log('socket end');
});

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5、逻辑实现

上面的简单例子,已经实现了群发功能,服务端发送消息后,所有建立连接的客户端都能收到信息。

那么如果一对一发送消息呢?
由于每个客户端和服务端之间的连接没有一个别名,我们只能获取到客户端的IP和PORT,所以索性我们把IP+PORT拼接为SessionId作为唯一ID
返回把所有Client实例存储起来

// 自定义客户端连接SESSIONID
const { remoteAddress, remotePort } = client;
client.name = `${remoteAddress}:${remotePort}`;
/**
 * 存储客户端,一对多,或者一对一,广播
 * clients = [{name: 客户端别名, sessionId: 唯一会话ID,client:客户端连接实例}]
 **/
const clients = [];
// 存储
function storeClient(client, name = '') {
    if (!client || !client.remoteAddress || !client.remotePort) return;
    const { remoteAddress, remotePort } = client;
    const sessionId = `${remoteAddress}:${remotePort}`;
    const existClient = clients.find(m => m.sessionId == sessionId);
    // 如果不存在,存储实例
    if (!existClient) {
        clients.push({
            name,
            sessionId,
            client,
        });
    } else {
        // 如果存在,更新
        existClient.client = client;
        existClient.name = name;
    }
}

如果服务器想通过别名去发送消息呢,该怎么做?
客户端可以在连接成功的第一时间,同步别名给服务器,这样服务器就知道客户端的别名了

// 连接成功调用的事件
sock.on('connect', function () {
    console.log('connect success');
    // 同步服务端,客户端基础信息
    const info = {
        type: 'sync',
        clientName: 'Client-1',
        message: '同步基础信息',
    };
    const message = `${info.clientName}向服务端同步基础信息${info.message}`;
    sock.write(JSON.stringify(info), 'utf8');
});

6、完整代码

服务端

const net = require('net');

/**
 * 存储客户端,一对多,或者一对一,广播
 * clients = [{name: 客户端别名, sessionId: 唯一会话ID,client:客户端连接实例}]
 **/
const clients = [];

// 创建
const server = net.createServer(function (client) {
    // 自定义客户端连接SESSIONID
    // 或者可以在客户端定义UUID,同步信息过来
    const { remoteAddress, remotePort } = client;
    client.name = `${remoteAddress}:${remotePort}`;
    // 存储
    storeClient(client);
    console.log('[客户端已接入]: ', client.name);
    console.log(
        '[目前已接入的所有客户端]: ',
        clients.map(m => m.sessionId)
    );

    // 监听来自客户端的数据
    client.on('data', function (data) {
        try {
            // 一定要约束固定的消息模板,这里data约定是JSON格式的数据
            const _data = JSON.parse(data);
            const { type, clientName, message } = _data;
            if (type == 'sync') {
                // 同步信息
                storeClient(client, clientName);
            } else if (type == 'info') {
                // 消息互传
            }
            console.log(
                '服务端接收到来自客户端' + client.name + '的数据',
                message.toString('utf-8')
            );
        } catch (error) {}
    });

    // 客户端断开连接的时候处理,
    // 提示用户断线离开了,删除客户端连接池
    client.on('close', function () {
        const index = clients.findIndex(m => m.sessionId == client.name);
        if (index >= 0) clients.splice(index, 1);
        console.log(
            '【all clients】',
            clients.map(m => m.sessionId)
        );
        console.log(client.name, '下线了');
    });

    // 客户端连接错误
    client.on('error', function (err) {
        console.log(client.name + ' 连接错误', err);
    });
});

// 广播
const broadcast = ({ clientName, message }) => {
    // 定向广播
    if (clientName) {
        const client = clients.find(m => m.name == clientName);
        client && client.client.write(message);
    } else {
        // 群发
        clients.map(m => {
            m.client.write(message);
        });
    }
};

// 测试
let testTimeout = null;
server.on('connection', function () {
    clearTimeout(testTimeout);
    testTimeout = setTimeout(() => {
        console.log('通知所有人:来领物资了');
        broadcast({ message: '\n\r大家来领物资了' });

        console.log('通知Client-1: 赶紧来做核酸了, 就差你了');
        broadcast({ clientName: 'Client-1', message: '\n\r赶紧来做核酸了, 就差你了' });

        console.log('通知Client-2: 一会儿上门做核酸');
        broadcast({ clientName: 'Client-2', message: '\n\r一会儿上门做核酸' });
    }, 10000);
});

server.on('listening', function () {
    console.log('等待连接...');
});

// 端口监听
server.listen({
    port: 9000,
    host: '127.0.0.1',
});

// 监听发生错误的时候调用
server.on('error', function () {
    console.log('listen error');
});

server.on('close', function () {
    console.log('server stop listener');
});

// 存储
function storeClient(client, name = '') {
    if (!client || !client.remoteAddress || !client.remotePort) return;
    const { remoteAddress, remotePort } = client;
    const sessionId = `${remoteAddress}:${remotePort}`;
    const existClient = clients.find(m => m.sessionId == sessionId);
    // 如果不存在,存储实例
    if (!existClient) {
        clients.push({
            name,
            sessionId,
            client,
        });
    } else {
        // 如果存在,更新
        existClient.client = client;
        existClient.name = name;
    }
}

客户端Client-1

const net = require('net');

// net.Socket,
const sock = net.connect(
    {
        port: 9000,
        host: '127.0.0.1',
    },
    function () {
        console.log('connected to server!');
    }
);

// 连接成功调用的事件
sock.on('connect', function () {
    console.log('connect success');
    // 同步服务端,客户端基础信息
    const info = {
        type: 'sync',
        clientName: 'Client-1',
        message: '同步基础信息',
    };
    const message = `${info.clientName}向服务端同步基础信息${info.message}`;
    console.log(message);
    sock.write(JSON.stringify(info), 'utf8');

    // 延迟发送数据
    setTimeout(() => {
        // 消息模板
        const info = {
            type: 'info',
            clientName: 'Client-1',
            message: '1111',
        };
        const message = `${info.clientName}向服务端发送信息:${info.message}`;
        console.log(message);
        sock.write(JSON.stringify(info), 'utf8');
    }, 3000);
});

// 当有数据发生的时候,调用;
sock.on('data', function (data) {
    console.log('Client-1 接收到来自服务端的信息:', data.toString('utf-8'));
});

// 有错误发生调用的事件
sock.on('error', function (e) {
    console.log('error', e);
});

// socket关闭的事件
sock.on('close', function () {
    console.log('close');
}); 

// 对方发送了关闭数据包过来的事件
sock.on('end', function () {
    console.log('end');
});

客户端Client-2

const net = require('net');

// net.Socket,
const sock = net.connect(
    {
        port: 9000,
        host: '127.0.0.1',
    },
    function () {
        console.log('connected to server!');
    }
);

// 连接成功调用的事件
sock.on('connect', function () {
    console.log('connect success');
    // 同步服务端,客户端基础信息
    const info = {
        type: 'sync',
        clientName: 'Client-2',
        message: '同步基础信息',
    };
    const message = `${info.clientName}向服务端同步基础信息${info.message}`;
    console.log(message);
    sock.write(JSON.stringify(info), 'utf8');

    // 延迟发送数据
    setTimeout(() => {
        // 消息模板
        const info = {
            type: 'info',
            clientName: 'Client-2',
            message: '222',
        };
        const message = `${info.clientName}向服务端发送信息:${info.message}`;
        console.log(message);
        sock.write(JSON.stringify(info), 'utf8');
    }, 3000);
});

// 当有数据发生的时候,调用;
sock.on('data', function (data) {
    console.log('Client-2 接收到来自服务端的信息:', data.toString('utf-8'));
});

// 有错误发生调用的事件
sock.on('error', function (e) {
    console.log('error', e);
});

// socket关闭的事件
sock.on('close', function () {
    console.log('close');
});

// 对方发送了关闭数据包过来的事件
sock.on('end', function () {
    console.log('end');
});

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

  • 6
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值