[Nodejs]初探nodejs学习笔记- 如何使用nodejs搭建简单的UDP聊天功能

何为UDP(User Datagram Protocol)?

从baidu摘过来一段:UDP,用户数据报协议,与所熟知的TCP(传输控制协议)协议一样,UDP协议直接位于IP(网际协议)协议的顶层。根据OSI(开放系统互连)参考模型,UDP和TCP都属于传输层协议。UDP协议的主要作用是将网络数据流量压缩成数据包的形式。一个典型的数据包就是一个二进制数据的传输单位。每一个数据包的前8个字节用来包含报头信息,剩余字节则用来包含具体的传输数据。

UDP报文没有可靠性保证、顺序保证和流量控制字段等,可靠性较差。但是正因为UDP协议的控制选项较少,在数据传输过程中延迟小、数据传输效率高,适合对可靠性要求不高的应用程序,或者可以保障可靠性的应用程序,如DNS、TFTP、SNMP等。

好了,接下来简要说明一下我实现的效果:

????????? ????? 防工具盗链抓取【如果显示此文字,代表来自第三方转发】 freddon所有 ??? ???????????

有Fred、Lenka、Nick三个人,均处于同一聊天室中:

即:Nick发的消息,Fred、Lenka均能收到;

Fred、Lenka只能互相发消息。

OK,就这么简单。为了说明问题,就不过度封装代码,以说明为主。

????????? ????? 防工具盗链抓取【如果显示此文字,代表来自第三方转发】 freddon所有 ??? ???????????

Server端:

server.js
var udp = require(‘dgram‘);

var server = udp.createSocket(‘udp4‘);

/**
 * 用于存储人员之间的离线消息任务
 * @type {{tasks: Array}}
 */
var msgTask = {
    //config: {},
    tasks: []
};

/**
 * 存醋当前在线的用户
 * @type {{online: Array, pool: Array}}
 */
var userPool = {
    online: [],
    pool: []
};

/**
 * 加入某个用户的在线状态
 * @param name
 * @param rinfo
 */
var pushIntoPool = function (name, rinfo) {
    var index = userPool.online.indexOf(name);
    if (index >= 0) {
        userPool.online.splice(index, 1);
        userPool.pool.splice(index, 1);
    }
    userPool.online.push(name);
    userPool.pool.push({name: name, ip: rinfo.address, port: rinfo.port});
};

/**
 * 移除在线状态
 * @param name
 */
var pullFromPool = function (name) {
    var index = userPool.online.indexOf(name);
    if (index >= 0) {
        userPool.online.splice(index, 1);
        userPool.pool.splice(index, 1);
        return;
    }
};

/**
 * 加入离线消息任务
 * @param msg
 */
var addInTask = function (msg) {
    msgTask.tasks.push({msg: msg, expireTime: 7 * 24 * 3600 + new Date().getTime()});
};

/**
 * 发送消息
 * @param m
 * @param rinfo
 */
var sendMsg = function (m, rinfo) {
    process.nextTick(function () {
        if (m.to){
            //获取对方的服务地址\端口
            var index = userPool.online.indexOf(m.to.name);
            if (index >= 0) {
                //在线
                var config = userPool.pool[index];
                var msg = JSON.stringify(m);
                server.send(msg, 0, Buffer.byteLength(msg, encoding = ‘utf8‘), config.port, config.ip, function (err, bytes) {
                    if (err) {
                        //发送失败
                        //缓存数据
                        addInTask(m);
                    }
                });
            } else {
                if (rinfo) {
                    //离线
                    var content = JSON.stringify({content: m.to.name + ‘不在线‘});
                    server.send(content, 0, Buffer.byteLength(content, encoding = ‘utf8‘), rinfo.port, rinfo.address, function (err, bytes) {
                        if (err) {
                            //发送失败
                        }
                    });
                }
                //不在线
                pullFromPool(m.to.name);
                //缓存数据
                addInTask(m);
            }

        } else {
            //群聊
            for (var i = 0; i < userPool.pool.length; i++) {
                var to_cfg = userPool.pool[i];
                if (to_cfg.name == m.from.name) {
                    continue;
                } else {
                    var msg = JSON.stringify(m);
                    server.send(msg, 0, Buffer.byteLength(msg, encoding = ‘utf8‘), to_cfg.port, to_cfg.ip, function (err, bytes) {
                        if (err) {
                            //发送失败
                        }
                    });
                }
            }
        }
    });
};

/**
 * 后台轮询任务
 */
var backgroundTask = function () {
    for (var i = 0; i < msgTask.tasks.length; i++) {
        var m = msgTask.tasks.splice(i, 1)[0];
        sendMsg(m.msg);
    }
    beginTask();
};

var tid;

var beginTask = function () {
    clearTimeout(tid);
    tid = setTimeout(backgroundTask, 1000);
};

server.on(‘message‘, function (msg, rinfo) {
    //注意msg为Buffer对象
    var m = JSON.parse(msg.toString());
    pushIntoPool(m.from.name, rinfo);
    if (m.action == ‘online‘) {
        console.log(‘当前聊天室在线人数%d::%s‘, userPool.online.length,userPool.online.join(","));
        return;
    }
    //发送消息
    sendMsg(m, rinfo);
}).bind(8124, function () {
    console.log(‘服务端启动成功‘);
    //当服务启动后,开启后台消息轮询服务
    beginTask();
});

Client端:

Fred.js 用户fred
var udp=require(‘dgram‘);
var mm=require(‘./msgmodel‘);
var client=udp.createSocket(‘udp4‘);
var from={
    name:‘Fred‘,
    host:client.address,
    port:client.remotePort,
    content:‘‘
};
var msg=new mm.FMsg(from);
process.stdin.resume();
process.stdin.on(‘data‘,function(data){
    msg.setAction(‘chat‘);
    msg.setContent(data.toString(‘utf8‘));
    //设置只能发送给Lenka
    msg.setTo({
        name:‘Lenka‘
    });
    msg.udpSendMsg(client,function(err,bytes){
        if(err){
            //发送失败
        }
    });

});
client.on(‘message‘,function(data){
    var data=JSON.parse(data.toString());
    if(!data.from){
        console.log(data.content);
    }else{
        if(!data.to){
            console.log("[%s]:%s",data.from.name,data.content);
        }else{
            console.log("[%[email protected]%s]:%s",data.from.name,data.to.name,data.content);
        }
    }
});
//默认连接后上线操作
msg.udpSendMsg(client,function(err,bytes){
    if(err==0){
        console.log("Fred上线!");
    }
});
Lenka.js 用户Lenka
var udp=require(‘dgram‘);
var mm=require(‘./msgmodel‘);
var client=udp.createSocket(‘udp4‘);
var from={
    name:‘Lenka‘,
    host:client.address,
    port:client.remotePort,
    content:‘‘
};
var msg=new mm.FMsg(from);
process.stdin.resume();
process.stdin.on(‘data‘,function(data){
    msg.setAction(‘chat‘);
    msg.setContent(data.toString(‘utf8‘));
    //设置只能发送给Fred
    msg.setTo({
        name:‘Fred‘
    });
    msg.udpSendMsg(client,function(err,bytes){
        if(err){
            //发送失败
        }
    });

});
client.on(‘message‘,function(data){
    var data=JSON.parse(data.toString());
    if(!data.from){
        console.log(data.content);
    }else{
        if(!data.to){
            console.log("[%s]:%s",data.from.name,data.content);
        }else{
            console.log("[%[email protected]%s]:%s",data.from.name,data.to.name,data.content);
        }
    }
});
//默认连接后上线操作
msg.udpSendMsg(client,function(err,bytes){
    if(err==0){
        console.log("Lenka上线!");
    }
});
Nick.js 用户Nick
var udp=require(‘dgram‘);
var mm=require(‘./msgmodel‘);
var client=udp.createSocket(‘udp4‘);
var from={
    name:‘Nick‘,
    host:client.address,
    port:client.remotePort,
    content:‘‘
};
var msg=new mm.FMsg(from);
process.stdin.resume();
process.stdin.on(‘data‘,function(data){
    msg.setAction(‘chat‘);
    msg.setContent(data.toString(‘utf8‘));
    //不设置发送给谁,默认发送给所有人
    msg.udpSendMsg(client,function(err,bytes){
        if(err){
            //发送失败
        }
    });

});
client.on(‘message‘,function(datas){
    var data=JSON.parse(data.toString());
    if(!data.from){
        console.log(data.content);
    }else{
        if(!data.to){
            console.log("[%s]:%s",data.from.name,data.content);
        }else{
            console.log("[%[email protected]%s]:%s",data.from.name,data.to.name,data.content);
        }
    }
});
//默认连接后上线操作
msg.udpSendMsg(client,function(err,bytes){
    if(err==0){
        console.log("Nick上线!");
    }
});

消息类msgmodel.js

var host = ‘127.0.0.1‘;//需要连接到服务器提供udp连接的ip
var port = 8124;//需要连接到服务器提供udp连接的端口
var ACTIONS=[‘online‘,‘chat‘,‘request‘,‘stranger‘,‘del‘,‘offline‘];
function FMsg(from, to, content) {
    this.from = from;
    this.to = to;
    this.content = content;
    this.action = ‘online‘;
    this.setAction = function (action) {
        this.action = action;
    };
    this.setTo = function (to) {
        this.to = to;
    };

    this.setContent = function (content) {
        this.content = content;
    };
    this.getMsg = function () {
        var msg = {
            from: this.from,
            to: this.to,
            content: this.content,
            action:this.action
        };
        return JSON.stringify(msg);
    };
    this.udpSendMsg = function (client, callback) {
        var data = this.getMsg();
        client.send(data, 0, Buffer.byteLength(data,encoding=‘utf8‘), port, host, callback);
    };
}
exports.FMsg = FMsg;

接下来分别使用nodejs启动服务端和客户端。

启动服务端后,只启动Fred、Lenka中的一个(比如说启动了Fred):

在Fred控制台进行如下输入:(Lenka不在线,所以该消息未发送成功)

然后启动Lenka,(Lenka收到了离线消息)Lenka的控制台为:

启动Nick,Nick说话,然后观察其他两个客户端,

server:

nick:

fred:

lenka:

Lenka、Fred说话,观察Nick控制台:

Lenka:

fred:

nick:

好啦,相当简单的一个点对点和聊天室的功能就这样搭好了。

????????? ????? 防工具盗链抓取【如果显示此文字,代表来自第三方转发】 freddon所有 ??? ???????????

转载请注明:http://my.oschina.net/freddon/blog/518328

时间: 2024-12-06 05:53:41

[Nodejs]初探nodejs学习笔记- 如何使用nodejs搭建简单的UDP聊天功能的相关文章

NodeJS学习笔记(一)——搭建开发框架Express,实现Web网站登录验证

JS是脚本语言,脚本语言都需要一个解析器才能运行.对于写在HTML页面里的JS,浏览器充当了解析器的角色.而对于需要独立运行的JS,NodeJS就是一个解析器.每一种解析器都是一个运行环境,不但允许JS定义各种数据结构,进行各种计算,还允许JS使用运行环境提供的内置对象和方法做一些事情.例如运行在浏览器中的JS的用途是操作DOM,浏览器就提供了document之类的内置对象.而运行在NodeJS中的JS的用途是操作磁盘文件或搭建HTTP服务器,NodeJS就相应提供了fs.http等内置对象.E

初探排序学习笔记

简单选择排序 思路:选出最小的元素,放在第一个位置,之后在剩下的元素中,选出最小的元素,放在第二个位置.........以此类推,直到完成排序. package h; public class MyA { static void selectOne(int[] a, int begin) { int p = begin; //假设修正法 for(int i=begin+1; i<a.length; i++){ if(a[i] < a[p]) p = i; //记录最小元素所在位置 } {int

初探单例模式学习笔记

一.如何防止一个类产生多个实例呢? 1.不做任何措施,贴出一幅海报,通知所有程序员不能对这个类创建多个实例  (不现实) 2.让这个类无法创建另一个实例    -> 单例模式 二.但是对类进行实例化,它的决定权在类的外部,如何将决定权回归类的自身呢? -> 将构造函数变成private类型  ,不允许外界直接调用构造方法创建实例 三.但我们总要给外界提供一个途径获得类的实例 class T { private T(); public static T getInstance() { retur

Cocos2dx 学习笔记整理----开发环境搭建

最近在学习cocos2dx,预备将学习过程整理成笔记. 需要的工具和环境整理一下: 使用的版本 cocos2dx目前已经出到了v3.1.1,学习和项目的话还是用2.2.3为宜,毕竟不大想做小白鼠,并且学习了几天之后才发出3.X版本的,版本内容变动比较大. 开发环境 1 jdk 1.6以上 2 python 2.7为宜(创建项目要用的) 3 NDT+Android SDK 4 Cygwin或者MinGW 开发工具 1 Eclipse + CDT + ADT 2 VS2010 3 Sublime T

Hadoop学习笔记(3)——分布式环境搭建

Hadoop学习笔记(3) ——分布式环境搭建 前面,我们已经在单机上把Hadoop运行起来了,但我们知道Hadoop支持分布式的,而它的优点就是在分布上突出的,所以我们得搭个环境模拟一下. 在这里,我们采用这样的策略来模拟环境,我们使用3台ubuntu机器,1台为作主机(master),另外2台作为从机(slaver).同时,这台主机,我们就用第一章中搭建好的环境来. 我们采用与第一章中相似的步骤来操作: 运行环境搭建 在前面,我们知道,运行hadoop是在linux上运行的.所以我们单机就在

学习笔记:利用GDI+生成简单的验证码图片

小分享:我有几张阿里云优惠券,用券购买或者升级阿里云相应产品最多可以优惠五折!领券地址:https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userCode=ohmepe03 学习笔记:利用GDI+生成简单的验证码图片 1 /// <summary> 2 /// 单击图片时切换图片 3 /// </summary> 4 /// <param name="sender">&

GDI+学习笔记(七)保存简单图像

请尊重本人的工作成果,转载请留言,并说明转载地址,谢谢.地址如下: http://blog.csdn.net/fukainankai/article/details/27710883 前几节中,我们利用GDI+在窗口中绘制了各种各样的图形.图像,这一节,我们将会将这些图像保存成简单图像.所谓简单图像,指的是bmp/jpg/png等图像或者单帧的gif图像.保存成多帧的gif图像稍微复杂一点,本节中暂时不做说明.保存成动态的tiff文件也比较简单,但这里也不做说明,下次有机会和gif一起介绍. 另

[java基础学习笔记]Java8SE开发环境搭建、第一个Java Hello World、Java程序的编译与执行

本文作者:sushengmiyan 本文地址:http://blog.csdn.net/sushengmiyan/article/details/25745945 主要内容: ---------------------------------------------------| 1.JDK的下载与安装                            | 2.编写第一个java程序HelloWorld     | 3.编译执行HelloWorld                      

iOS学习笔记—— UItableView 控件的简单使用

UITableView 可以说是iOS开发中最常用的控件,除了游戏之外,几乎所有的应用中独会出现他的身影. 使用UITableView控件需要遵守两种协议 UITableViewDelegate和 UITableViewDataSource. 常用方法如下: 1.返回(每个分区)表单元个数(行数) - (NSInteger) tableView: (UItableView *) tableVIew numberOfRowsInSection: (NSInteger)section 2.返回表单元