在上一篇中,我们对websocket的通信方法进行了简单的了解,搭建了最简单的服务器。在本篇中就进行最终的简易聊天室的开发吧。
1. 最终效果预览
2. 过程中使用到的websocket方法简单总结
在JS中以下 API 用于创建 WebSocket 对象。第一个参数 url, 指定连接的 URL。第二个参数 protocol 是可选的,指定了可接受的子协议。
var Socket = new WebSocket(url, [protocol] );
比如我们在程序中使用的:
var ws = new WebSocket('ws://127.0.0.1:12345');
HTML(前端)处理函数:
WebSocket 事件
以下是 WebSocket 对象的相关事件。假定我们使用了以上代码创建了 Socket 对象:
事件 | 事件处理程序 | 描述 |
---|---|---|
open | Socket.onopen | 连接建立时触发 |
message | Socket.onmessage | 客户端接收服务端数据时触发 |
error | Socket.onerror | 通信发生错误时触发 |
close | Socket.onclose | 连接关闭时触发 |
WebSocket 方法
以下是 WebSocket 对象的相关方法。假定我们使用了以上代码创建了 Socket 对象:
方法 | 描述 |
---|---|
Socket.send() | 使用连接发送数据 |
Socket.close() | 关闭连接 |
举个栗子:在建立连接时会出发ws.onopen函数,在收到服务器发来的消息时会自动触发ws.onmessage函数,在用户关闭网页或者手动关闭连接时会触发ws.onclose函数。发送数据的时候使用ws.send函数即可,发送的内容作为参数。具体的使用方法在后面会讲到。
服务器(后端)处理函数:
上一篇讲过,在服务器端的app.js使用类似如下方法来实现服务器端监听,具体的逻辑代码在该函数中实现,服务器端发送和接收消息均在该函数中实现。
- var server = ws.createServer(function(conn) {
- ......
- }).listen(12345);
使用
conn.on('text', function(str){ ... });
来处理接收的消息(str里的内容),这里我们均使用json格式数据交互。
使用
conn.on('close', function(){ ... });
来处理客户端关闭。
另外conn.on('error', function(){ ... });
也必须写出来,否则每当客户端关闭时,后端服务器会崩溃自动关闭。
3. 逻辑简单说明
- 用户新打开一个页面,不会自动向服务器发起连接请求,只有用户按下设置按钮之后才会发起连接(若没有输入用户名,默认用户名为“defaultN”);
- 第一次连接发送的json数据为:{name: "哈哈哈", type: "setname"}
- 服务器端收到类型为“setname”的数据时,会广播该消息,之后再广播一条用户列表json数据,用于更新各个客户端的用户列表。
- 每当有用户按下发送按钮时,若输入框中内容不为空,会向服务器端发送json数据: {message: "你好啊", type: "chat"}
- 之后服务器端同样的广播该条消息给所有客户端。
4. 附上前后端代码
app.js
var ws = require('nodejs-websocket');
var server = ws.createServer(function(conn) {
conn.on('text', function(str) {
var data = JSON.parse(str);
console.log(data);
switch (data.type) {
case 'setname':
conn.nickname = data.name;
boardcast(JSON.stringify({
type: 'serverInformation',
message: data.name + ' 加入房间'
}));
boardcast(JSON.stringify({
type: 'chatterList',
list: getAllChatter()
}))
break;
case 'chat':
boardcast(JSON.stringify({
type: 'chat',
name: conn.nickname,
message: data.message
}));
break;
default:
break;
}
});
conn.on('close', function() {
boardcast(JSON.stringify({
type: 'serverInformation',
message: conn.nickname + ' 离开房间'
}));
boardcast(JSON.stringify({
type: 'chatterList',
list: getAllChatter()
}))
});
conn.on('error', function(err) {
console.log(err);
});
}).listen(12345);
function boardcast(str) {
server.connections.forEach(function(conn) {
conn.sendText(str);
})
}
function getAllChatter() {
var chatterArr = [];
server.connections.forEach(function(conn) {
chatterArr.push({name: conn.nickname});
})
return JSON.stringify(chatterArr);
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>ChatRoom</title>
<link href="css/default.css" rel="stylesheet" type="text/css"></link>
</head>
<body>
<div id="header">
<input type="text" id="name" placeholder="输入用户名">
<button id="setname" >设置</button>
<input type="text" id="text" placeholder="输入发送内容">
<button id="btn">发送</button>
</div>
<div id="body">
<div id="container"></div>
<div id="chatterlist">
<p class="chatterlist-head">在线用户(0)</p>
</div>
</div>
<script>
var ws = null;
document.getElementById('setname').onclick = function () {
var name = document.getElementById('name').value;
if (name === '') {
name = "DefaultN";
}
ws = new WebSocket('ws://127.0.0.1:12345');
ws.onopen = function () {
ws.send(JSON.stringify({
name: name,
type: 'setname'
}));
}
document.getElementById('btn').onclick = send;
document.getElementById('text').onkeyup = function(e) {
if (e.keyCode !== 13) return;
send();
}
ws.onmessage = function (e) {
var data = JSON.parse(e.data);
if(data.type === 'chatterList') {
var list = JSON.parse(data.list);
var oldList = document.getElementById('chatterlist');
oldList.innerHTML = '';
var p_list_head = document.createElement('p');
p_list_head.setAttribute('class', 'chatterlist-head');
p_list_head.innerHTML = "在线用户(" + list.length + ")";
oldList.appendChild(p_list_head);
for(var i = 0; i < list.length; i++) {
var p_user = document.createElement('p');
p_user.innerHTML = list[i].name;
p_user.setAttribute('class', 'userlist-item');
oldList.appendChild(p_user);
}
} else {
console.log(data);
var oldContent = document.getElementById('container');
oldContent.insertBefore(createChatDiv(data), oldContent.children[0]);
}
}
var p_name = document.getElementById('name');
var p_setname = document.getElementById('setname');
p_name.innerHTML = name;
p_setname.setAttribute('disabled', true);
p_name.setAttribute('disabled', true);
p_setname.style.display = "none";
p_name.style.border = "none";
}
function createChatDiv(data) {
var div = document.createElement('div');
var p_time = document.createElement('p');
var p_content = document.createElement('p');
switch (data.type) {
case 'serverInformation':
p_time.innerHTML = new Date().Format("yyyy-MM-dd hh:mm:ss");
p_content.innerHTML = data.message;
break;
case 'chat':
p_time.innerHTML = new Date().Format("yyyy-MM-dd hh:mm:ss");
p_content.innerHTML = data.name + ': ' + data.message;
break;
default:
break;
}
p_time.setAttribute('class', 'time');
p_content.setAttribute('class', 'content');
div.appendChild(p_time);
div.appendChild(p_content);
return div;
}
Date.prototype.Format = function (fmt) { //author: meizz
var o = {
"M+": this.getMonth() + 1, //月份
"d+": this.getDate(), //日
"h+": this.getHours(), //小时
"m+": this.getMinutes(), //分
"s+": this.getSeconds(), //秒
"q+": Math.floor((this.getMonth() + 3) / 3), //季度
"S": this.getMilliseconds() //毫秒
};
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
for (var k in o)
if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
return fmt;
}
function send() {
if(document.getElementById('text').value === '') return;
ws.send(JSON.stringify({
message: document.getElementById('text').value,
type: 'chat'
}));
document.getElementById('text').value = '';
}
</script>
</body>
</html>
default.css
body{
height: 100%;
clear: both;
margin: 0;
}
#header{
background-color: white;
position: fixed;
padding: 10px;
width: 100%;
border-bottom: 1px solid #ddd;
}
#body{
padding-top: 51px;
height: 100%;
width: 100%;
overflow: hidden;
}
#container{
min-width: 500px;
padding-left: 10px;
padding-right: 10px;
width: 80% - 20px;
height: 100%;
float: left;
}
#chatterlist{
position: fixed;
right: 0;
top: 51;
width: 20%;
background-color: #f6f6f6;
height: 100%;
overflow-x: hidden;
overflow-y: scroll;
}
.chatterlist-head{
margin: 0;
padding: 5px 0 5px 10px;
width: 100% - 10px;
color: white;
background-color: #00a1b6;
}
.userlist-item{
margin: 0;
padding-left: 10px;
padding-top: 5px;
padding-bottom: 5px;
font-size: 14px;
border-bottom: 1px solid #ddd;
}
.time{
width: fit-content;
padding: 2px 8px 2px 8px;
font-size: 8px;
color: gray;
background-color: #f6f6f6;
border-radius: 8px;
margin: 0;
margin-top: 10px;
}
.content{
width: fit-content;
padding: 2px 8px 2px 8px;
font-size: 14px;
color: #f6f6f6;
background-color: #00a1b6;
border-radius: 8px;
max-width: 70%;
margin: 0;
margin-top:5px;
}
#btn, #setname{
height: 30px;
font-size: 14px;
border-radius: 10px;
border: 1px solid #00a1b6;
color: white;
padding: 2px 10px 2px 10px;
background-color: #00a1b6;
border: none;
outline: none;
cursor: pointer;
}
#btn:hover{
color: #00a1b6;
background-color: white;
border: 1px solid #00a1b6;
transition: 0.4s;
}
#setname:hover{
color: #00a1b6;
background-color: white;
border: 1px solid #00a1b6;
transition: 0.4s;
}
#name, #text{
height: 25px;
width: 100px;
font-size: 14px;
border-radius: 10px;
border: 1px solid #9fafb1;
box-shadow: #ddd;
padding-left: 10px;
outline: none;
}
#text{
width: 400px;
}
#name:focus{
border: 1px solid #00a1b6;
transition: 1s;
}
#text:focus{
border: 1px solid #00a1b6;
transition: 1s;
}