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 操作Word和Excel的实现代码
Oct 26 Javascript
javascript tips提示框组件实现代码
Nov 19 Javascript
基于Jquery实现表格动态分页实现代码
Jun 21 Javascript
js实现鼠标滑过文字链接色彩变化的效果
May 06 Javascript
JS实现的在线调色板实例(附demo源码下载)
Mar 01 Javascript
JS中取二维数组中最大值的方法汇总
Apr 17 Javascript
利用jQuery实现一个简单的表格上下翻页效果
Mar 14 Javascript
jQuery中 DOM节点操作方法大全
Oct 12 jQuery
Vue基于NUXT的SSR详解
Oct 24 Javascript
微信小程序自定义tab实现多层tab嵌套功能
Jun 15 Javascript
微信小程序封装自定义弹窗的实现代码
May 08 Javascript
详解JS预解析原理
Jun 16 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
星际玩家的三大定律
2020/03/04 星际争霸
php实现mysql事务处理的方法
2014/12/25 PHP
用js判断浏览器是否是IE的比较好的办法
2007/05/08 Javascript
jQuery 判断页面元素是否存在的代码
2009/08/14 Javascript
jquery下动态显示jqGrid以及jqGrid的属性设置容易出现问题的解决方法
2010/10/22 Javascript
HTML5之lang属性与dir属性的详解
2013/06/19 Javascript
Extjs实现进度条的两种便捷方式
2013/09/26 Javascript
基于jQuery实现的幻灯图片切换
2016/12/02 Javascript
JS HTML图片显示Canvas 压缩功能
2017/07/21 Javascript
使用JavaScript实现表格编辑器(实例讲解)
2017/08/02 Javascript
解决webpack dev-server不能匹配post请求的问题
2018/08/24 Javascript
js设计模式之单例模式原理与用法详解
2019/08/15 Javascript
浅谈laytpl 模板空值显示null的解决方法及简单的js表达式
2019/09/19 Javascript
Python中pandas模块DataFrame创建方法示例
2018/06/20 Python
python中plot实现即时数据动态显示方法
2018/06/22 Python
DES加密解密算法之python实现版(图文并茂)
2018/12/06 Python
解决Pytorch自定义层出现多Variable共享内存错误问题
2020/06/28 Python
python程序如何进行保存
2020/07/03 Python
丝芙兰中国官方商城:SEPHORA中国
2018/01/10 全球购物
经济实惠的名牌太阳镜和眼镜:Privé Revaux
2021/02/07 全球购物
心得体会范文
2014/01/04 职场文书
物理研修随笔感言
2014/02/14 职场文书
给校长的建议书
2014/03/12 职场文书
岗位职责怎么写
2014/03/14 职场文书
大学竞选班长演讲稿
2014/04/24 职场文书
网络营销策划方案
2014/06/04 职场文书
社区助残日活动总结
2014/08/29 职场文书
师德自我剖析材料范文
2014/10/06 职场文书
2014年机关党委工作总结
2014/12/11 职场文书
护士个人总结范文
2015/02/13 职场文书
清明节网上祭英烈寄语2015
2015/03/04 职场文书
建筑质检员岗位职责
2015/04/08 职场文书
初中数学教学反思范文
2016/02/17 职场文书
自愿离婚协议书范本2016
2016/03/18 职场文书
残联2016年全国助残日活动总结
2016/04/01 职场文书
Go语言实现一个简单的并发聊天室的项目实战
2022/03/18 Golang