Node-网络编程-终端聊天室

网络编程

  • 应用层
    • http 协议
    • 在浏览器输入一个地址,浏览器按照 http 协议将输入的地址包装成 http 报文
    • 和数据传输没有任何关系
    • 只负责数据包装
    • 包装目的就是为了区分不同的行为
  • 传输层
    • 将应用层包装好的数据,通过传输层进行传输
    • TCP: 电话机
      • 端到端通信协议,必须知道对方的 ip 地址和端口号
      • TCP/IP 对数据传输有一定的完整性的保障
        • 一旦传输过程发生数据的完整性丢失,则全部丢掉重传
      • 三次握手连接
        • 双方通信必须先建立连接
        • 确保双方都能收到对方的消息
        • 首先呢,有两个概念
          • 客户端 发送一个请求 –> 再回一个 这就是三次握手连接
          • 服务器 回送一个消息
        • 建立三次握手连接之后双方就可以进行有保证的数据通信了
      • 用于 web 服务器 和 客户端浏览器 传输数据
      • 数据安全性,对数据完整性有要求的使用 TCP/IP 协议
    • UDP: 收音机 广播
      • 发送一条数据,谁收到我不关心
      • 例如游戏、在线听歌、看电影 都是使用 UDP 协议
      • 对于数据的完整性没有保证
  • 网络层
    • 路由定位
    • IP 寻址 和 路由定位
  • 链路层
    • “链接层”的功能,它在 “实体层” 的上方,确定了 0 和 1 的分组方式
    • 对要传输的数据,把 0 和 1 进行分组,分成多个数据块进行传输
  • 实体层
    • 光缆、电缆、双绞线、无线电液
    • 高电频、低电频

在终端模拟聊天室

服务端模拟

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
55
56
57
58
59
60
61
62
const net = require('net');
const server = net.createServer();
// 用户昵称: socket对象
// 用户昵称: socket对象
// ......
const users = {};
server.on('connection', socket => {
// 当收到客户端的注册消息的时候,处理注册请求
// 可能昵称被占用了,你就要告诉用户被占用了
// 如果没有被占用,则将昵称保存起来,告诉用户登陆聊天室成功
// 叶良辰
// \list
// 我有一百种方法让你待不下去
// 赵日天: 你等着
socket.json = obj => {
socket.write(JSON.stringify(obj));
}
socket.on('data', data => {
data = data.toString().trim();
try {
data = JSON.parse(data);
switch (data.type) {
case 'signup':
// 如果昵称已存在,则告诉用户昵称已存在
if (users[data.message]) {
return socket.write(JSON.stringify({
type: 'singup',
code: 1001,
message: 'nickname already exists'
}));
}
// 将昵称保存到数据对象中
users[data.message] = socket;
// 给客户端响应消息,告诉客户端登陆成功
socket.write(JSON.stringify({
type: 'singup',
code: 1000,
nickname: data.message,
message: 'success',
}));
break;
case 'broadcast':
for (let nickname in users) {
users[nickname].json({
type: 'broadcast',
from: data.from,
message: data.message
});
}
break;
default:
break;
}
} catch (e) {
}
})
})
server.listen(10000, '192.168.32.96', () => {
console.log('server is running at port 3000')
});

客户端模拟

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
const net = require('net');
const socket = net.createConnection(3000, '192.168.32.96');
socket.json = obj => {
socket.write(JSON.stringify(obj));
};
// 这个变量用来保存用户的昵称同时也作为一个标记
let nickname;
socket.on('connect', () => {
process.stdout.write('请输入你的昵称:');
process.stdin.on('data', data => {
data = data.toString().trim();
if (!nickname) {
const send = {
type: 'signup',
message: data,
};
// 将 json 对象转为字符串就叫做序列化
// 将 json 格式字符串转为对象叫做反序列化
return socket.write(JSON.stringify(send));
}
// 把所有这种指令型的都加上一个 \ 用来区分
// \help
if (data === '\\list') {
return socket.json({
type: 'list';
});
}
// 如果用户输入的 xxx:dsadsadsa
// 就包装成点对点聊天数据格式
const matches = /^(.+):(.+)$/.exec(data)
if (matches) {
return socket.json({
type: 'p2p',
from: nickname,
to: matches[1],
message: matches[2]
});
}
socket.json({
type: 'broadcast',
from: nickname,
message: data
});
});
});
socket.on('data', data => {
// 客户端也要根据服务器响应的消息做一个处理
// 例如,当服务器发送了一个被占用的消息的时候,客户端就提示用户请重新输入
// 当服务器发送一个登陆成功的时候,客户端也提示用户
data = data.toString().trim();
try {
data = JSON.parse(data);
switch (data.type) {
case 'singup':
switch (data.code) {
case 1000:
console.log('恭喜,登陆聊天室成功');
nickname = data.nickname;
break;
case 1001:
console.log('昵称已被占用,大侠请重新来过');
process.stdout.write('请输入你的昵称:');
break;
default:
break;
}
break;
case 'broadcast':
console.log(`${data.from}说:${data.message}`);
break;
case 'list':
console.log('');
console.log('================ 用户列表 ================');
data.message.forEach(n => console.log(n));
console.log('==========================================');
console.log('');
break
case 'p2p':
console.log(`${data.from}对你说:${data.message}`);
break;
default:
break;
}
} catch (e) {
}
});
感谢您的支持!