Node.js websocket使用socket.io库实现实时聊天室


Posted in Javascript onFebruary 20, 2017

认识websocket

WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duple)。一开始的握手需要借助HTTP请求完成。

其实websocket 并不是很依赖Http协议,它也拥有自己的一套协议机制,但在这里我们需要利用的socket.io 需要依赖到http 。
之前用java jsp写过一个聊天,其实实现逻辑并不难,只是大部分时间都用在UI的设计上,其实现原理就是一个基于websocket的通信,要想做一个好的聊天室,我觉得大部分精力可能更应该花在与用户的视觉层交互上。

废话不闲扯,我们先来看一下websocket 与传统的ajax 有什么不同之处。
在之前,如果我们想要获取到服务器更新的信息,我们可以使用ajax 轮询来完成,然而,这样做的弊端是增大了我们与服务器的交互次数,然而极大部分的交互都是无意义的,因为我们只是做一个询问,如果没有任何新的信息,我们几乎什么都不用做,因此这样会极大的浪费服务器资源和带宽。
然而使用websocket 会使客户端与服务器建立一个长连接,并且,当服务器有新消息时可以主动推送到客户端,所以我们可以不用一次次的去询问服务器是否有新消息,而是直接由服务器主动推送到客户端,这样在无消息的状态下,客户端不会频繁的去请求服务器。
使用websocket 的特点在于服务器可以主动推送消息到客户端。

使用socket.io 库实现实时聊天

这也是这篇博文的主题之处。socket.io发布到npm 平台上,我们可以直接用npm 来安装到**当前**node_modules目录下。

npm install socket.io --save

下面我们就可以直接使用require 方法来将这个模块引入

const socket = require("socket.io");

在创建此websocket 服务器之前,它需要依赖于一个已经创建好的http服务器。

let socketServer = socket.listen(require("http").createServer((req,resp) => {
 //返回页面
 resp.end(require("fs").readFileSync("./socketIOTest1.html"));
}).listen(9999,"localhost",() => {console.log("listening");}));

在上述代码中socketIOTest1.html 是在当前目录下的一个html文件,在下面我会贴上详细的代码,这里先稍稍带过。

在websocket 服务器对象中有一个connection事件,这个事件在有客户端连接到socket服务器时被触发。下面我们监听这个事件,打印一句话来表示有用户连接。

//监听connection 事件
socketServer.on("connection",socket => {
 console.log("有一用户连接");
}

上述代码中,callback有一个参数socket为连接到客户端的一个socket端口对象,这个对象有一个message 事件,当客户端有消息推送到服务器时,事件循环会取出这个事件与之对应的回调函数并执行。

socket.on("message",msg => {
 console.log(msg);
});

同时,socket对象还可以监听disconnect 事件,来监听用户断开连接的情况

socket.on("disconnect",() => {
 console.log("有一用户退出连接");
});

因为我们这次的主题是要创建一个能够实时聊天的聊天室,因此光有这些是不够的,我们还需要一个能够与用户交互的客户端。
下面是我的socketIOTest代码:

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>Document</title>
</head>
<body>
 <textarea name="" id="content" cols="30" rows="10" ></textarea>
 <input id="write" type="text" placeholder="please write content here">
 <input id="send" type="button" value="send" />
 <script src="./socket.io/socket.io.js"></script>
 <script>
  let send = document.getElementById("send");
  let write = document.getElementById("write");
  let content = document.getElementById("content");
  let socket = io.connect();
  //发送消息
  send.onclick = () => {
   let msg = write.value;
   // content.innerHTML = content.value + msg + "\n";
   socket.send(msg);
  };
  //接收到消息
  socket.on("message",msg => {
   console.log("从服务器接收到的消息 : " + msg);
   //更新内容
   content.innerHTML = content.value + msg + "\n";
  });
  socket.on("disconnect",() => {
   console.log("与服务器断开连接");
  });
 </script>
</body>
</html>

在上述代码中,我用script标签引入了一个socket.io.js文件,这个文件不需要另外去下载,而直接引入即可,因为socket.io.js是被包含于socket.io模块中,在上面node的程序代码中,我们通过require方法引入了socket.io模块,因此我们可以直接通过相对路径访问到它。

<script src="./socket.io/socket.io.js"></script>

接下来我们就可以在script标签中使用如同服务端的代码。

let socket = io.connect();

使用io.connect()方法连接到websocket服务器,该方法返回一个与连接的服务器与之对应的一个socket端口对象。

下面我们同样监听message 和 disconnect事件。

//接收到消息
socket.on("message",msg => {
 console.log("从服务器接收到的消息 : " + msg);
 //更新内容
 content.innerHTML = content.value + msg + "\n";
});
socket.on("disconnect",() => {
 console.log("与服务器断开连接");
});

为了更能突出websocket的作用,在html代码中,我只使用了一个textarea标签来显示内容,两个input标签用于发送。
使用socket对象的send方法就能使消息在服务器与客户端进行消息传递。

websocket群聊实现

现在我们假设一个场景,有u1和u2两个用户,同时连接到服务器,那么我们怎么使他们互相通信呢,实现的方法及其简单。当u1连接到服务器,在服务器中,使用一个map键值对把与u1对应的socket对象进行保存。

//创建一个用于放置用户对象的map
let map = new Map();
//用于记录用户数量的变量,并初始化为0
let userCount = 0;
//监听connection 事件
socketServer.on("connection",socket => {
 console.log("有一用户连接");
 map.set(++userCount,socket);
 //...
});

与此同时,u2也连接上服务器,也由该map把与u2与之对应的socket对象进行储存。
现在,u1点击了send按钮发送一条消息至服务器,服务器收到消息后遍历map,转发给所有socket对象,实现群聊的实时通信。

socketServer.on("connection",socket => {
 console.log("有一用户连接");
 map.set(++userCount,socket);
 //监听客户端来的信息
 socket.on("message",msg => {
  //从客户端接收的消息
  //遍历所有用户
  map.forEach((value,index,arr) => {
   value.send(msg);
  });
 });
});

下面我贴上服务端的完整代码,仅供参考

const socket = require("socket.io");
//创建一个websocket服务器
let socketServer = socket.listen(require("http").createServer((req,resp) => {
 //返回页面
 resp.end(require("fs").readFileSync("./socketIOTest1.html"));
}).listen(9999,"localhost",() => {console.log("listening");}));

//创建一个用于放置用户对象的map
let map = new Map();
//用于记录用户数量的变量,并初始化为0
let userCount = 0;

//监听connection 事件
socketServer.on("connection",socket => {
 console.log("有一用户连接");
 map.set(++userCount,socket);
 //监听客户端来的信息
 socket.on("message",msg => {
  //从客户端接收的消息
  //遍历所有用户
  map.forEach((value,index,arr) => {
   value.send(msg);
  });
 });
 //监听客户端退出情况
 socket.on("disconnect",() => {
  console.log("有一用户退出连接");
 });
});

Node.js websocket使用socket.io库实现实时聊天室

websocket私聊实现

在说私聊的实现之前,我们首先要找到对于每一个用户的唯一标识,在通常的项目开发中,我们都使用用户的用户名进行标识,每个用户通过注册获得与之对应的用户名。将用户名保存在数据库中利用主键防止重复。

实现私聊的方法有很多种,这里我的实现方法是这样的:

① 当用户连接时,把用户的socket端口对象使用map进行储存,储存的key 为用户的socket对象,value为用户的用户名,写一个方法用于更新客户端列表
② 用户默认用户名为 <未命名>,指定自定义用户名时,使用socket.emit方法触发服务端的某个事件,遍历map找到与之对应的key,进行value修改
③ 发送消息时,根据选择列表来指定要发送的人,在服务端,遍历map,找到要发送到的用户名,进行发送,同时更新到自己的聊天框

以上就是私聊的简单实现。

下面看一下具体代码:

//Node.js

const socket = require("socket.io");
//创建一个websocket服务器
let socketServer = socket.listen(require("http").createServer((req,resp) => {
 //返回页面
 resp.end(require("fs").readFileSync("./socketIOTest1.html"));
}).listen(9999,"localhost",() => {console.log("listening");}));

//创建一个用于放置用户对象的map
let map = new Map();
//用于记录用户数量的变量,并初始化为0
let userCount = 0;
//遍历map 
let scanMap = func => {
 try{
  map.forEach((value,index,arr) => {
   func(value,index,arr);
  });
 }
 catch(e){
  if(e.message == "break"){
   return;
  }
  else{
   throw e;
  }
 }
}

//通知客户端弹出对话框
let showDialog = (socket,msg) => {
 socket.emit("showDialog",msg);
}

//更新用户列表
let updateList = socket => {
 let userArr = [];
 scanMap((value,index) => {
  if(value != undefined){
   userArr.push(value);
  }
 });
 socket.emit("newUser",userArr);
}

//监听connection 事件
socketServer.on("connection",socket => {
 console.log("有一用户连接");
 //初始化存储当前socket对象
 map.set(socket,"<未命名>");
 //将用户信息写入map
 socket.on("getUser",user => {
  //修改名称
  map.set(socket,user);
  scanMap((value,index) => {
   updateList(index);
  });
 });
 //通知所有客户端更新列表
 scanMap((value,index) => {
  updateList(index);
 });
 //监听客户端来的信息
 socket.on("message",msg => {
  //从客户端接收的消息
  let sender;
  //遍历所有用户
  scanMap((value,index) => {
   if(index == socket){
    sender = value;
   }
  });
  scanMap((value,index) => {
   if(msg.person == "all"){
    index.send(sender + " : " + msg.msg);
   }
   else if(msg.person == value){
    socket.send(sender + " : " +msg.msg);
    index.send(sender + " : " +msg.msg);
    throw new Error("break");
   }
  });
 });
 //监听客户端退出情况
 socket.on("disconnect",() => {
  //用户退出,从map里删除该用户
  map.set(socket,undefined);
  //通知所有用户更新列表
  scanMap((value,index) => {
   updateList(index);
  });
  console.log("有一用户退出连接");
 });
});

客户端:

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>Document</title>
</head>
<body>
 <textarea name="" id="content" cols="30" rows="10" ></textarea>
 <input id="write" type="text" placeholder="please write content here">
 <input id="send" type="button" value="send" />
 <input type="text" id="user" placeholder="user">
 <select style="width: 100px;" size="2" name="" id="userList">
  <option value="all">群聊</option>
 </select>
 <script src="./socket.io/socket.io.js"></script>
 <script>
  let send = document.getElementById("send");
  let write = document.getElementById("write");
  let content = document.getElementById("content");
  let user = document.getElementById("user");
  //用户列表
  let userList = document.getElementById("userList");
  let socket = io.connect();
  //判断用户名是否为空
  let isUserEmpty = () => {
   if(user.value == ""){
    alert("请填写用户名");
    return false;
   }
   else {
    return true;
   }
  }
  //监听用户名变化
  let oldUser;
  user.onblur = () => {
   if(isUserEmpty()){
    //防止重复发射
    if(oldUser == user.value){return;}
    oldUser = user.value;
    socket.emit("getUser",user.value);
   }
  }  
  //发送消息
  send.onclick = () => {
   if(isUserEmpty()){
    let msg = write.value;
    // content.innerHTML = content.value + msg + "\n";
    socket.send({msg:msg,person:userList.value});
   }
   if(select.value == ""){
    alert("请选择一个聊天对象");
   }
  };
  //接收到消息
  socket.on("message",msg => {
   console.log("从服务器接收到的消息 : " + msg);
   //更新内容
   content.innerHTML = content.value + msg + "\n";
  });
  socket.on("disconnect",() => {
   console.log("与服务器断开连接");
  });
  //新用户加入聊天室
  socket.on("newUser",arr => {
   userList.innerHTML = "";
   let all = document.createElement("option");
   all.innerHTML = "群聊";
   all.setAttribute("value","all");
   userList.appendChild(all);
   //添加新用户
   arr.forEach((value,index) => {
    console.log("value :" + value + "index :" + index);
    let option = document.createElement("option");
    option.innerHTML = value;
    option.setAttribute("value",value);
    userList.appendChild(option);
    userList.setAttribute("size",userList.children.length);
   });
   //默认选中群聊
   userList.value = "all";
  });
  //接收服务器需要弹出对话框的需求
  socket.on("showDialog",msg => {
   alert(msg);
  });
 </script>
</body>
</html>

Node.js websocket使用socket.io库实现实时聊天室

代码的具体我就不在详细讲解,都标有注释,由于只是用于博文,整体代码没有重构优化,大家看不懂的可以回复我,或者有什么地方错误请指出,我会及时改正。

另外在这个聊天室中,当用户刷新频率较快时,websocket会出现伪连接现象。

下面附上我的github地址,大家可以下载我的源码进行修改学习,共勉。
https://github.com/HaoDaWang/chat

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
js模仿jquery的写法示例代码
Jun 16 Javascript
js动态创建表格,删除行列的小例子
Jul 20 Javascript
jQuery 鼠标经过(hover)事件的延时处理示例
Apr 14 Javascript
js实现图片拖动改变顺序附图
May 13 Javascript
JavaScript 学习笔记之操作符
Jan 14 Javascript
JavaScript动态修改背景颜色的方法
Apr 16 Javascript
javascript自定义in_array()函数实现方法
Aug 03 Javascript
JS实现单击输入框弹出选择框效果完整实例
Dec 14 Javascript
jQuery Mobile中的button按钮组件基础使用教程
May 23 Javascript
bootstrap导航条实现代码
Dec 28 Javascript
让div运动起来 js实现缓动效果
Jul 06 Javascript
去掉vue 中的代码规范检测两种方法(Eslint验证)
Mar 21 Javascript
Angular在一个页面中使用两个ng-app的方法(二)
Feb 20 #Javascript
Angular在一个页面中使用两个ng-app的方法
Feb 20 #Javascript
JS高仿抛物线加入购物车特效实现代码
Feb 20 #Javascript
ES6中Math对象的部分扩展
Feb 20 #Javascript
微信小程序左滑删除效果的实现代码
Feb 20 #Javascript
JavaScript轮播图简单制作方法
Feb 20 #Javascript
Ajax异步获取html数据中包含js方法无效的解决方法
Feb 20 #Javascript
You might like
浅谈php7的重大新特性
2015/10/23 PHP
PHP代码维护,重构变困难的4种原因分析
2016/01/25 PHP
Yii2单元测试用法示例
2016/11/12 PHP
jQuery 操作XML入门
2008/12/25 Javascript
将jQuery应用于login页面的问题及解决
2009/10/17 Javascript
两种WEB下的模态对话框 (asp.net或js的分别实现)
2009/12/02 Javascript
js中eval()函数和trim()去掉字符串左右空格应用
2013/02/02 Javascript
nodejs实现黑名单中间件设计
2014/06/17 NodeJs
jQuery实现带水平滑杆的焦点图动画插件
2016/03/08 Javascript
Angular组件化管理实现方法分析
2017/03/17 Javascript
jQuery实现可拖动进度条实例代码
2017/06/21 jQuery
VsCode插件整理(小结)
2017/09/14 Javascript
JS基于递归实现网页版计算器的方法分析
2017/12/20 Javascript
JavaScript动态加载重复绑定问题
2018/04/01 Javascript
Vue自定义指令上报Google Analytics事件统计的方法
2019/02/25 Javascript
javascript实现弹出层效果
2019/12/10 Javascript
JavaScript交换变量常用4种方法解析
2020/09/02 Javascript
Vue 修改网站图标的方法
2020/12/31 Vue.js
[47:02]2018DOTA2亚洲邀请赛3月29日 小组赛B组 VP VS paiN
2018/03/30 DOTA
Python使用sqlalchemy模块连接数据库操作示例
2019/03/13 Python
python实现矩阵和array数组之间的转换
2019/11/29 Python
python GUI库图形界面开发之PyQt5打印控件QPrinter详细使用方法与实例
2020/02/28 Python
html5+css3进度条倒计时动画特效代码【推荐】
2016/03/08 HTML / CSS
Aerosoles爱柔仕官网:美国舒软女鞋品牌
2017/07/17 全球购物
Beach Bunny Swimwear官网:设计师泳装和性感比基尼
2019/03/13 全球购物
June Jacobs尊积帕官网:知名的spa水疗护肤品牌
2019/03/21 全球购物
如果Session Bean得Remove方法一直都不被调用会怎么样
2012/07/14 面试题
村优秀党员事迹材料
2014/01/15 职场文书
给校长的建议书
2014/03/12 职场文书
妇女工作先进事迹
2014/08/17 职场文书
2014大学生中国梦主题教育学习思想汇报
2014/09/10 职场文书
2014年后勤工作总结范文
2014/12/16 职场文书
2015年国际护士节演讲稿
2015/03/18 职场文书
爱心捐助活动总结
2015/05/09 职场文书
交心谈心活动总结
2015/05/11 职场文书
校园广播稿范文
2015/08/19 职场文书