之前我们在 IM即时聊天室(一):WebSocket 和 IM即时聊天室(二): Socket.io + Node.js
两篇文章中介绍了搭建一个IM的所需的技术栈和通信原理。那在这篇文章里我们就来详细说一下具体的应用并提供完整源码。
个人Blog地址:IM即时聊天室(三):项目详解及源码分析
聊天室在线地址:点此进入
PS: 这是我网络编程课的一个小作业,因为博主比较懒所以这篇文章的大部分由我的课程报告组成(逃)
实现目标
应用实例是一个多人聊天室程序,用户可以通过指定服务器IP进入聊天室和聊天室内其他用户进行聊天,聊天室的功能主要有文本、表情和图片传输,并在Web界面中显示。
需求分析
-
应用采用客户/服务器模式,分为客户端程序和服务器端程序。
-
用户通过客户端可以选择头像,发送信息、表情、图片
-
服务端可以同时接收多个客户端信息,并根据需求进行转发、广播或者回传给客户端特定的消息。
-
客户端程序和服务器程序通过网络交换聊天的字符串内容(其中表情和头像为特殊格式代码,图片为转码后获得的URL格式)
话不多说,先看程序效果展示:
技术实现
-
通信协议选择
Balabalabala… 详见下面两篇文章
IM即时聊天室(一):WebSocket
IM即时聊天室(二):Socket.io + Node.js
-
客户端
UI界面:HTML+CSS+Bootstrap自适应
界面交互:原生Javascript+jQuery
消息的发送和接收:Socket.io
PS:由于客户端代码太太太太太多了,我只挑比较有特点的的讲一下
登录啥的就不讲了,大概思路就是开始时隐藏聊天界面,登录成功后隐藏登录界面显出聊天界面,源码里有完整的注释
先贴一下客户端代码大致结构图:
比较有意思的代码是我仿照微信做了一个回到最新消息处的功能。
当我们在聊天时,我们会希望最新的消息实时显示出来,但是当我们在看历史消息时,可不希望被强行定位到最新消息处。并且在查看历史消息时,可以通过按钮回到最新消息处。
于是我们就要判断用户在查看历史消息还是正在聊天,方法就是判断最新的消息是否出现在了用户窗口里。
关键代码:
```
/*将页面下拉到最新消息处*/function scrollToEnd(){var div = document.getElementsByTagName("div");div_length = div.length-4;div[div_length].scrollIntoView({behavior: "smooth"}); //平滑滚动,提高了用户体验}/*判断当有新信息来时,用户是否在页面底端*/function isNewInWindow(){var div = document.getElementsByTagName("div");div_length = div.length-5;if(isInWindow(div[div_length])){return true;}return false;}/*判定元素是否在界面内*/function isInWindow(x){ if(x.getBoundingClientRect().top > window.innerHeight){console.log("down");return false;}else if(x.getBoundingClientRect().bottom < 0){console.log("up");return false;}return true;}
```
- 服务端
服务端主要使用了Node.js+Socket.io来构建服务端,需要Node环境来运行。
主要的思路:请求HTTP服务 → 在此基础上使用WebSocket → 引入Socket.io模块并创建实例 → 用该实例进行socket监听 → 通过socket进行消息的发送、转发、群发等。
服务端关键代码:
```
var app = require('http').createServer();
var io = require('socket.io')(app);
var PORT = 8081;
/*定义用户数组*/
var users = [];
app.listen(PORT);io.on('connection',function (socket) { //监听//发送、转发、群发消息
})
```
- 图片文件传输
图片传输部分我本来是想用Base64编码,先在客户端读取图片二进制信息并编成Base64格式发送给服务端,服务端转发后别的客户端接收后解码。
但是后面放弃了这一方式,因为直接传输二进制信息太慢了,于是改用了HTML5的Filereader方法。
这个方法有个优点就是其中的readAsDataURL函数可以直接将图片二进制信息转成URL格式,这样子我只用发送并转发一个简短的URL,客户端直接根据URL加载图片,省去了大量传输和解码时间。
关键代码:
```
//图片发送
document.getElementById(‘sendImage’).addEventListener(‘change’,
function() {
//检查是否有文件被选中
if (this.files.length != 0) {
//获取文件并用FileReader进行读取
var file = this.files[0],
reader = new FileReader();
if (!reader) return;reader.onload = function(e) {//读取成功,发送到服务器socket.emit('sendImg',{username:uname,image: e.target.result,date:new Date().toTimeString().substr(0, 8),headnum:headnum});};reader.readAsDataURL(file);};
}, false);```
- 头像选择/表情包传输
头像和表情已经预先放置在客户端中,用户在选择以后将产生特定格式的代码,放置到要发送的信息当中。
别的客户端接收到的消息时候,先用正则表达式检索信息中是否存在该格式代码,若有则加载相应的头像/表情,以达到效果。
关键代码,以表情为例:
//emoji
document.getElementById('emojiWrapper').addEventListener('click',
function(e) {//获取被点击的对象var target = e.target;console.log(target);if (target.nodeName.toLowerCase() == 'img') { //如果是表情图像则发送var sendtxt = document.getElementById('sendtxt');sendtxt.focus();sendtxt.value = sendtxt.value + '[emoji:' + target.num + ']';};
}, false);
源码
Gitbub地址:https://github.com/KMKNKK/Chatroom-WebSocket/tree/homework
(欢迎fork和star)
重要文件结构:
│
├─index.html //客户端
│
├─css
│ bg.jpg //背景图
│ chat.css
│
├─images //图片资源
│ │ logo.png
│ │ toNewMessage.png
│ │
│ ├─emoji //表情
│ │
│ └─user //头像
│
│
├─js
│ │ app.js //服务端JS
│ │ chat.js //客户端JS
│ │ jquery.min.js //依赖
│ │ socket.io.js //依赖
│ │
│ └─.vscode
│ settings.json
│
├─new //BootStrap和jQuery文件
│ │ jquery-3.2.1.min.js
│ │
│ └─bootstrap-3.3.7-dist
│
│
└─node_modules //依赖文件