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 js cookie的存储,获取和删除
Dec 29 Javascript
javascript各种复制代码收集
Sep 20 Javascript
简单漂亮的js弹窗可自由拖拽且兼容大部分浏览器
Oct 22 Javascript
浅谈JS正则表达式的RegExp对象和括号的使用
Jul 28 Javascript
javascript 数组的正态分布排序的问题
Jul 31 Javascript
JavaScript日期选择功能示例
Jan 16 Javascript
vue组件如何被其他项目引用
Apr 13 Javascript
Vue进度条progressbar组件功能
Apr 17 Javascript
jQuery length 和 size()区别总结
Apr 26 jQuery
vue中各选项及钩子函数执行顺序详解
Aug 25 Javascript
jQuery实现的中英文切换功能示例
Jan 11 jQuery
jQuery 选择器用法实例分析【prev + next】
May 22 jQuery
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
json的键名为数字时的调用方式(示例代码)
2013/11/15 PHP
PHP include任意文件或URL介绍
2014/04/29 PHP
CentOS 上搭建 PHP7 开发测试环境
2017/02/26 PHP
Thinkphp 框架配置操作之动态配置、扩展配置及批量配置实例分析
2020/05/15 PHP
表单项的name命名为submit、reset引起的问题
2007/12/22 Javascript
javascript 读取xml,写入xml 实现代码
2009/07/10 Javascript
jQuery实现切换页面过渡动画效果
2015/10/29 Javascript
百度坐标(BD09)、国测局坐标(火星坐标,GCJ02)、和WGS84坐标系之间的转换
2016/02/19 Javascript
jQuery基础_入门必看知识点
2016/07/04 Javascript
BootStrap 附加导航组件
2016/07/22 Javascript
javascript实现下雨效果
2017/03/27 Javascript
Vue.js中对css的操作(修改)具体方式详解
2018/10/30 Javascript
解决ie11 SCRIPT5011:不能执行已释放Script的代码问题
2019/05/05 Javascript
JS实现水平移动与垂直移动动画
2019/12/19 Javascript
vue实现购物车加减
2020/05/30 Javascript
js中复选框的取值及赋值示例详解
2020/10/18 Javascript
Python挑选文件夹里宽大于300图片的方法
2015/03/05 Python
用Python进行一些简单的自然语言处理的教程
2015/03/31 Python
Python程序中使用SQLAlchemy时出现乱码的解决方案
2015/04/24 Python
Python中函数参数设置及使用的学习笔记
2016/05/03 Python
Python正规则表达式学习指南
2016/08/02 Python
详解python如何调用C/C++底层库与互相传值
2016/08/10 Python
python将.ppm格式图片转换成.jpg格式文件的方法
2018/10/27 Python
详解Python下载图片并保存本地的两种方式
2019/05/15 Python
解决python gdal投影坐标系转换的问题
2020/01/17 Python
Tensorflow 使用pb文件保存(恢复)模型计算图和参数实例详解
2020/02/11 Python
Python计算指定日期是今年的第几天(三种方法)
2020/03/26 Python
结合CSS3的新特性来总结垂直居中的实现方法
2016/05/30 HTML / CSS
Hawes & Curtis官网:英国经典品牌
2019/07/27 全球购物
数控专业大学生的自我鉴定
2013/11/13 职场文书
自主招生自荐信格式
2013/12/03 职场文书
汉语言文学毕业求职信
2014/07/17 职场文书
个性发展自我评价2015
2015/03/09 职场文书
小学体育跳绳课教学反思
2016/02/16 职场文书
剑指Offer之Java算法习题精讲二叉树专项训练
2022/03/21 Java/Android
详解Mysq MVCC多版本的并发控制
2022/04/29 MySQL