背景

  • 这次前端课收获颇丰,蚂蚁高层讲师水平高,代码风格棒,教学态度也很好,课堂氛围融洽~~(第一节课的老师甚至跟我聊起了 HHKB)~~

  • 期末还差两门考试在一个星期后,突然想起来博客好像好久没有更新了。

  • 今天大家好像也都不想复习了,就来写这份总结

  • 博客的代码框不知道为啥变回黄色了,可能是 Linux 下 hugo 的锅,反正懒得再改了(

技术栈

  • node.js
  • koa
  • socket.io
  • 客户端
  • 服务端

koa

Koa是一个基于 Node.js 平台的下一代 web 开发框架。

上半节课用原生 node 写的 http 服务器代码忘记放哪里去了,反正 koa 10行代码就能搞定

注意

koa代码全写在后端程序里面

引入

1
2
3
4
const path = require('path');
const http = require('http');
const Koa = require('koa');
const serve = require('koa-static');

模板

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 初始化
const hostname = '127.0.0.1';
const port = 3000;
const publicPath = path.join(__dirname, 'public');

// 创建koa实例
const app = new Koa();

// 创建http server 实例
const server = http.createServer(app.callback());
/*
   code here...
 */

app.use(serve(publicPath));

server.listen(port, hostname, () => {
	console.log('listening...');
});

socket.io

Socket.IO 是一个可以在浏览器与服务器之间实现实时、双向、基于事件的通信的工具库。

写这部分代码太痛苦了,写错了一个对象一直没发现,还以为是 wsl 的问题

注意

socket.io在客户端最好不要使用本地引用,好像会出锅?控制台一直会报错,不知道为啥

服务端

主框架

以下代码写在上面的koa代码code here

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/*
   以下代码是写在上面的koa代码的 code here 处的
 */

//创建 socket.io实例
const io = socketIO(server);

//储存在线所有用户
const users = new Map();
const historys = [];

// 客户端接入
io.use((socket, next) => {
	const { name, password } = socket.handshake.query;
	if (!name) {
		return next(new Error('WRONG_ACCOUNT'));
	}
	if (password !== 'j10c') {
		console.log("wrong password");
		return next(new Error('WRONG_PASSWORD'));
	}
	next();
});

io.on('connection', function(socket) {
	console.log('user connected');
	const name = socket.handshake.query.name;
	users.set(name, socket); // 在表中储存用户消息
	console.log('users====', users.keys());
	io.sockets.emit('online', [...users.keys()]); // 注意 sockets !!!

	socket.on('disconnect', function(socket) {
		console.log(socket); //transport close
		console.log('user connected');
		users.delete(name, socket);
		console.log('users ==== ', users.keys());
		io.sockets.emit('online', [...users.keys()]); // 服务端向所有客户端发送数据 io.sockets.emit
	});

	socket.on('sendMessage', (content) => {
		console.log(name + " send a message: " + content);
		const message = {
			time: Date.now(),
			sender: name,
			content: content
		};
		historys.push(message);
		socket.broadcast.emit('receiveMessage', message);// 服务端向所有其他客户端发送数据
	});

	socket.on('getHistory', (fn) => {
		fn(historys);
	});
});

io实例

主要代码分成两部分io.useio.on

服务端的socketio.use或者io.on中调回函数中的一个参数,所以socket.on是写在io.on(){}函数里面的,注意!!!

发送数据

io.sockets.emit(eventName, data)io.sockets.send(data)

如代码中的io.sockets.emit('online', [...users.keys()]); ,即向所有已经建立连接的客户端发送数据

广播

socket.broadcast.emit或者socket.broadcast.emit,向所有已建立连接的其他客户端发送数据**(注意和io.sockets.emit的区分!!!)**

区分

在这个项目中,用户向房间发送消息之后,他自己的消息同时发到本地缓存和服务器,服务器无需再给该用户发送消息数据!所以用socket.broadcast.emit

客户端

程序流程

登录,检查登录成功与否,获取历史消息

主框架

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
var socket; //开头先声明 socket 为全局变量
window.onload = function() {
	document.querySelector('#submit').addEventListener('click', ()=>{
		socket = io({
			query: {
			name: account,
			password: password
		},
		reconnection: false,
	});
	socket.on('connect', () => {
		socket.emit('getHistory', (data) => {});
	});
	socket.on('connect_error', (err) => {});
	socket.on('disconnect', () => {});
	socket.on('online', (users) => {}); // 监听服务端发来的数据
	socket.on('receiveMessage', (message) => {});
}
                                                       
document.querySelector('#sent_message').addEventListener('click', (e) => {
	socket.emit('sendMessage', text);
})

解析

在第一次用到socket的时候给变量赋值,下面的代码是在**登录(login)**的时候就用到了

注意区分服务端和客户端中的 socket!!!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
login.addEventListener('click', function(){
	var account = document.querySelectorAll('input')[0].value;
	var password = document.querySelectorAll('input')[1].value;
	socket = io({
		query: {
			name: account,
			password: password
		},
		reconnection: false,
	});
});
  • 客户端的socket.emit(eventName, data, [callback])作用为给服务端发送数据,服务端通过socket.on(eventName, (data, fn) => {})创建监听数据事件,两者间通过eventName相互匹配。
  • [callback]为回调函数,用于指定一个当对方确定接收到数据时调用的回调函数。
  • 另外一种监听事件socket.once(eventName, (data, fn) => {}),这个只监听一次,在回调函数执行完毕后,结束监听。

保留字

1
//TODO

一个很有意思的东西

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
//客户端
socket.emit('sendMessage', text);
socket.emit('getHistory', (data) => {});

//服务端
socket.on('sendMessage', (content) => {
	console.log(name + " send a message: " + content);
});
socket.on('getHistory', (fn) => {
	fn(historys);
});

sendMessage中客户端发送了text,服务端中用回调函数中的content参数接收

getHsitory中客户端发送了空内容,来请求数据,服务端用另外一个参数fn(应该是个函数),这个函数可以立即将数据发送回去,这个是不是有点类似 get 请求?

前端代码的一些tips

  1. HTML 结构语义化

  2. 缩进长度两个空格?蚂蚁这位学长的代码风格非常满足我强迫症,然而这篇文章是用 typora写的,代码都是乱贴的,缩进很乱

  3. 元素事件不要直接写在组件上,而是以element.addEventListener('event', function(){})形式写在 js 文件里面

  4. 尽量不要使用双引号,用单引号?

  5. 模板字符串

1
2
3
let name = 'j10c';
console.log('im ' + name);
console.log(`im ${name}`);
  1. 箭头函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
socket.on('receiveMessage', (message) => {
	console.log('received a message broadcast message:', message);
	addMessage(message.sender, message.content);
});
  
// message 是函数的参数,该函数为socket.on(eventName, function(){})函数的一个参数
  
//等价于
socket.on('receiveMessage', function(message){
	// code here...
});
  1. 如何使用好元素的 classList,看到 CSS 课上给元素添加 animation 淡入淡出的类标签的时候还挺震撼的,以前一直以为classid本身就是用来区分唯一元素和泛用元素的标签,现在知道了还能把class看作一种单一的属性。

总结

主要还是学了socket.io相关知识,期待寒假的时候有机会再练习一遍。