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 相关文章推荐
Javascript匿名函数的一种应用 代码封装
Jun 27 Javascript
javascript笔记 String类replace函数的一些事
Sep 22 Javascript
jquery select动态加载选择(兼容各种浏览器)
Feb 01 Javascript
js控制滚动条缓慢滚动到顶部实现代码
Mar 20 Javascript
Flexigrid在IE下不显示数据的处理的解决方法
Oct 24 Javascript
jQuery实现菜单式图片滑动切换
Mar 14 Javascript
浅析ES6的八进制与二进制整数字面量
Aug 30 Javascript
js判断数组是否包含某个字符串变量的实例
Nov 24 Javascript
微信小程序生成二维码的示例代码
Mar 29 Javascript
小程序的上传文件接口的注意要点解析
Sep 17 Javascript
Node.js API详解之 net模块实例分析
May 18 Javascript
解决Vue watch里调用方法的坑
Nov 07 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
一个从别的网站抓取信息的例子(域名查询)
2006/10/09 PHP
PHP大批量数据操作时临时调整内存与执行时间的方法
2011/04/20 PHP
thinkphp框架下404页面设置 仅三步
2016/05/14 PHP
Yii框架核心组件类实例详解
2019/08/06 PHP
jquery 批量上传图片实现代码
2010/01/28 Javascript
jQuery数据显示插件整合实现代码
2011/10/24 Javascript
浅谈Javascript事件处理程序的几种方式
2012/06/27 Javascript
js实现浏览器的各种菜单命令比如打印、查看源文件等等
2013/10/24 Javascript
详解AngularJS中$http缓存以及处理多个$http请求的方法
2016/02/06 Javascript
使用BootStrap实现悬浮窗口的效果
2016/12/13 Javascript
详解webpack+vue-cli项目打包技巧
2017/06/17 Javascript
React学习笔记之条件渲染(一)
2017/07/02 Javascript
详解如何搭建mpvue框架搭配vant组件库的小程序项目
2019/05/16 Javascript
使用 Vue 实现一个虚拟列表的方法
2019/08/20 Javascript
关于JavaScript中异步/等待的用法与理解
2020/11/18 Javascript
python实现的防DDoS脚本
2011/02/08 Python
python kmeans聚类简单介绍和实现代码
2018/02/23 Python
python判断一个集合是否为另一个集合的子集方法
2018/05/04 Python
python selenium 对浏览器标签页进行关闭和切换的方法
2018/05/21 Python
python列表list保留顺序去重的实例
2018/12/14 Python
浅析Python 读取图像文件的性能对比
2019/03/07 Python
Django框架验证码用法实例分析
2019/05/10 Python
Python 一键获取百度网盘提取码的方法
2019/08/01 Python
Python中 CSV格式清洗与转换的实例代码
2019/08/29 Python
pytorch 实现在预训练模型的 input上增减通道
2020/01/06 Python
Python pandas对excel的操作实现示例
2020/07/21 Python
html5利用canvas实现颜色容差抠图功能
2019/12/23 HTML / CSS
美国最大的在线水培用品商店:GrowersHouse.com
2018/08/14 全球购物
印尼综合在线预订网站:Tiket.com(机票、酒店、火车、租车和娱乐)
2018/10/11 全球购物
行政管理人员精品工作推荐信
2013/11/04 职场文书
人力资源管理专业应届生求职信
2014/04/24 职场文书
三字经教学反思
2014/04/26 职场文书
安全检查汇报材料
2014/12/26 职场文书
实习工作表现评语
2014/12/31 职场文书
小学语文复习计划
2015/01/19 职场文书
Redis可视化客户端小结
2021/06/10 Redis