1.首先我们看下效果图
附上github地址,github.com/windlany/ha…,
运行node server.js,打开localhost:3000


2.目录结构

服务端代码
项目用到的技术主要是nodejs+express,socket.io,fileReader,
server.js:服务端代码
chat-client.js:客户端代码
3.搭建服务器
搭建服务器有两种方式,
(1)通过nodejs
var http = require('http');
http.createServer(function (request, response) {
// 发送 HTTP 头部
// HTTP 状态值: 200 : OK
// 内容类型: text/plain
response.writeHead(200, {'Content-Type': 'text/plain'});
// 发送响应数据 "Hello World"
response.end('Hello World\n');
}).listen(8888);
// 终端打印如下信息
console.log('Server running at http://127.0.0.1:8888/');(2)nodejs+express
var express = require('express');
var app = express();
var http = require('http').Server(app);
// 路由为/默认www静态文件夹
app.use('/', express.static(__dirname + '/www'));
http.listen(3000, function() {
console.log('listen 3000 port.');
});4.客户端和服务端建立连接
和服务器建立连接需要用到实时通信工具socket.io,通过npm等包管理工具安装,
服务端通过connection监听客户端的连接
io.on('connection', (socket)=> {
//
})客户端通过socket.io模块的实例化对象io建立与服务端的连接
var socket = io(); socket.io的语法前后端通用,通过socket.emit() 触发事件,通过socket.on() 来监听和处理事件,通过传递的参数进行通信。每个客户端有专门的socket。
io.emit(foo); //会触发所有用户的foo事件
socket.emit(foo); //只触发当前用户的foo事件
socket.broadcast.emit(foo); //触发除了当前用户的其他用户的foo事件5.登录
client:
输入姓名登录,触发服务端的login事件,并注册了登录成功loginSuc和登录失败loginError两个事件
$('#nameBtn').click(inputName);
socket.on('loginSuc', ()=> {
$('.name').hide();
}) socket.on('loginError', ()=> {
alert('用户名已存在,请重新输入!');
$('#name').val(''); });
socket.on('system', (user)=> {
var data = new Date().toTimeString().substr(0, 8);
$('#messages').append(`<p class='system'><span>${data}</span><br /><span>${user.name}
${user.status}了聊天室<span></p>`);
// 滚动条总是在最底部
$('#messages').scrollTop($('#messages')[0].scrollHeight);
});
socket.on('disUser', (usersInfo)=> {
displayUser(usersInfo);
});
function inputName() {
var imgN = Math.floor(Math.random()*4)+1; // 随机分配头像
if($('#name').val().trim()!=='')
socket.emit('login', {
name: $('#name').val(),
img: 'image/user' + imgN + '.jpg'
}); // 触发登录事件
return false;
}
function displayUser(users) {
$('#users').text('');
// 每次都要重新渲染
if(!users.length) {
$('.contacts p').show();
} else {
$('.contacts p').hide();
}
$('#num').text(users.length);
for(var i = 0; i < users.length; i++) {
var $html = `<li>
<img src="${users[i].img}">
<span>${users[i].name}</span>
</li>`;
$('#users').append($html);
}
server:
服务端监听到login事件,
(1)判断当前用户名是否注册过,注册过,触发客户端的loginError事件,
未注册过,触发客户端的loginSuc,system,disUser事件,向所有客户端广播xxx进入聊天室,
右侧在线人员展示等
socket.on('login', (user)=> {
if(users.indexOf(user.name) > -1) {
socket.emit('loginError');
} else {
users.push(user.name);
usersInfo.push(user);
socket.emit('loginSuc');
socket.nickname = user.name;
io.emit('system', {
name: user.name,
status: '进入'
});
io.emit('disUser', usersInfo);
console.log(users.length + ' user connect.');
}
});6.发送消息
client:
客户端发送消息,触发服务端的sendMsg事件
$('#sub').click(sendMsg);var color = '#000000'; function sendMsg() { if($('#m').val() == '') { alert('请输入内容!'); return false; } color = $('#color').val(); socket.emit('sendMsg', { msg: $('#m').val(), color: color, type: 'text' }); $('#m').val(''); return false; }
socket.on('receiveMsg', (obj)=> {
// 发送为图片
if(obj.type == 'img') {
$('#messages').append(`
<li class='${obj.side}'>
<img src="${obj.img}">
<div>
<span>${obj.name}</span>
<p style="padding: 0;">${obj.msg}</p>
</div>
</li>
`);
$('#messages').scrollTop($('#messages')[0].scrollHeight);
return;
}
// 提取文字中的表情加以渲染
var msg = obj.msg;
var content = '';
while(msg.indexOf('[') > -1) {
// 其实更建议用正则将[]中的内容提取出来
var start = msg.indexOf('[');
var end = msg.indexOf(']');
content += '<span>'+msg.substr(0, start)+'</span>';
content += '<img src="image/emoji/emoji%20('+msg.substr(start+6, end-start-6)+').png">';
msg = msg.substr(end+1, msg.length); } content += '<span>'+msg+'</span>';
$('#messages').append(` <li class='${obj.side}'> <img src="${obj.img}">
<div> <span>${obj.name}</span> <p style="color: ${obj.color};">${content}</p>
</div> </li> `); // 滚动条总是在最底部
$('#messages').scrollTop($('#messages')[0].scrollHeight);
});
server:
服务端监听到sendMsg事件,触发客户端的receiveMsg事件,当前客户端消息显示在右侧,其他客户端显示在左侧
socket.on('sendMsg', (data)=> {
var img = '';
for(var i = 0; i < usersInfo.length; i++) {
if(usersInfo[i].name == socket.nickname) {
img = usersInfo[i].img;
}
}
socket.broadcast.emit('receiveMsg', {
name: socket.nickname,
img: img,
msg: data.msg,
color: data.color,
type: data.type,
side: 'left'
});
socket.emit('receiveMsg', {
name: socket.nickname,
img: img,
msg: data.msg,
color: data.color,
type: data.type,
side: 'right'
});
}); 7.发送图片
client:
使用FileReader对象,web应用程序可以异步的读取存储在用户计算机上的文件(或者原始数据缓冲)内容,可以使用File对象来指定所要处理的文件或数据。
FileReader通过异步的方式读取文件内容,结果均是通过事件回调获取
$('#file').change(function() {
var file = this.files[0]; // 上传单张图片
var reader = new FileReader();
//文件读取出错的时候触发
reader.onerror = function(){
console.log('读取文件失败,请重试!');
};
// 读取成功后
reader.onload = function() {
var src = reader.result; // 读取结果
var img = '<img class="sendImg" src="'+src+'">';
socket.emit('sendMsg', { // 发送
msg: img,
color: color,
type: 'img'
});
};
reader.readAsDataURL(file); // 读取为64位
});