背景
TCP 握手和 HTTPS 握手分别属于 OSI 网络模型中的不同层次,并且在网络通信中扮演着重要的角色。TCP(传输控制协议)是 OSI 模型中的传输层协议
,负责提供可靠的数据传输。TCP 握手是 TCP 协议在建立连接时的一部分。HTTPS 握手属于应用层
(Application Layer),HTTPS(安全的超文本传输协议)是一个基于 HTTP 协议的加密通信协议,用于在客户端和服务器之间建立安全的通信通道。
一、TCP 握手过程(三次握手):
过程
-
第一次握手(SYN):客户端发送一个带有 SYN(同步)标志的 TCP 数据包给服务器,请求建立连接。此时客户端进入 SYN_SENT 状态。
-
第二次握手(SYN + ACK):服务器收到客户端的请求后,发送一个带有 SYN 和 ACK(确认)标志的 TCP 数据包作为响应。此时服务器进入 SYN_RCVD 状态。
-
第三次握手(ACK):客户端收到服务器的响应后,发送一个带有 ACK 标志的 TCP 数据包给服务器,确认连接建立。此时客户端和服务器都进入 ESTABLISHED 状态,可以开始传输数据。
注意: 这里的握手次数指的是建立连接所需的握手次数。关闭连接时,还需要进行四次挥手过程来正常关闭连接。
示例
下面是一个使用Node.js的示例代码,演示了TCP客户端和服务器之间的握手过程:
// TCP服务器代码
const net = require('net');
const server = net.createServer((socket) => {
socket.on('data', (data) => {
console.log('Received data:', data.toString());
});
socket.on('end', () => {
console.log('Connection closed by client');
});
});
server.listen(3000, () => {
console.log('TCP server listening on port 3000');
});
// TCP客户端代码
const net = require('net');
const client = net.createConnection({ port: 3000 }, () => {
console.log('Connected to TCP server');
client.write('Hello server!');
});
client.on('data', (data) => {
console.log('Received data:', data.toString());
});
client.on('end', () => {
console.log('Connection closed by server');
});
二、HTTPS 握手过程(四次握手):
HTTPS握手过程在建立安全连接之前需要进行TCP握手来建立基本的网络连接。HTTPS是在HTTP协议上添加了TLS/SSL加密层的协议。TLS/SSL协议需要在TCP连接建立之后才能进行握手过程,确保通信双方建立了安全的加密通道。
过程
-
第一次握手:客户端向服务器发送一个加密套件列表和一个随机数,请求建立连接。
-
第二次握手:服务器选择一个加密套件和一个随机数,并发送服务器证书给客户端。
-
第三次握手:客户端验证服务器证书的合法性,并生成一个用于对称加密的密钥,并将密钥加密后发送给服务器。
-
第四次握手:服务器解密客户端发送的密钥,并使用该密钥进行加密和解密通信。
在 HTTPS 握手过程中,客户端和服务器之间进行了四次握手,确保建立了安全的加密通道。完成握手后,客户端和服务器可以开始进行加密通信。
示例
下面是一个使用Node.js的示例代码,演示了HTTPS客户端和服务器之间的握手过程:
// HTTPS服务器代码
const https = require('https');
const fs = require('fs');
const options = {
key: fs.readFileSync('server.key'),
cert: fs.readFileSync('server.crt'),
};
const server = https.createServer(options, (req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello, HTTPS!');
});
server.listen(443, () => {
console.log('HTTPS server listening on port 443');
});
// HTTPS客户端代码
const https = require('https');
const options = {
hostname: 'localhost',
port: 443,
path: '/',
method: 'GET',
};
const req = https.request(options, (res) => {
console.log(`Server responded with status code: ${res.statusCode}`);
res.on('data', (data) => {
console.log('Received data:', data.toString());
});
});
req.on('error', (error) => {
console.error('Request error:', error);
});
req.end();
三、TCP与UDP
在涉及到可靠性,实时性,资源消耗,网络环境等要求时,我们需要评估TCP还是UDP作为传输层协议的适用性。这里不得不提一下UDP,它作为会是一种无连接的传输协议,没有内置的握手过程。
与TCP对比
下面的表格对比了UDP和TCP在几个关键方面的特性:
特性 | UDP | TCP |
---|---|---|
连接性质 | 无连接 | 面向连接 |
可靠性 | 不可靠,数据可能丢失或乱序 | 可靠,提供错误检测、重传和排序 |
传输速度 | 快速,无握手和拥塞控制延迟 | 较慢,有握手和拥塞控制延迟 |
数据流 | 不支持多路复用 | 支持多路复用 |
错误恢复 | 不提供内置的错误恢复机制 | 提供内置的错误恢复机制 |
拥塞控制 | 不提供内置的拥塞控制机制 | 提供内置的拥塞控制机制 |
安全性 | 不提供内置的加密机制 | 提供内置的加密机制 |
适用场景 | 实时音视频传输、游戏通信等 | 文件传输、网页浏览等 |
UDP是一种无连接的协议,不提供可靠性保证,传输速度快,适用于对实时性要求较高的应用场景。它不支持多路复用、错误恢复和拥塞控制,也不提供内置的加密机制。
示例
当使用Node.js进行UDP通信时,以下是一个简单的代码示例,演示了UDP连接的过程:
const dgram = require('dgram');
// 创建UDP套接字
const socket = dgram.createSocket('udp4');
// 监听消息接收事件
socket.on('message', (msg, rinfo) => {
console.log(`Received message: ${msg} from ${rinfo.address}:${rinfo.port}`);
});
// 绑定套接字到指定端口和地址
socket.bind(1234, 'localhost', () => {
console.log('UDP socket is listening');
});
// 发送消息到目标地址和端口
const message = Buffer.from('Hello, UDP server!');
socket.send(message, 0, message.length, 4321, 'localhost', (err) => {
if (err) {
console.error('Error sending message:', err);
} else {
console.log('Message sent successfully');
}
// 关闭套接字
socket.close();
});
在这个示例中,首先我们创建了一个UDP套接字,使用dgram.createSocket()
方法,并指定了UDP协议版本(udp4
表示IPv4)。然后,我们通过调用socket.bind()
方法将套接字绑定到指定的端口和地址(在本例中是localhost的1234端口)。
接下来,我们监听message
事件,当接收到消息时,会触发回调函数并打印接收到的消息和发送方的地址和端口。
然后,我们创建一个消息(使用Buffer.from()
方法将字符串转换为Buffer),并使用socket.send()
方法将消息发送到目标地址和端口(在本例中是localhost的4321端口)。
最后,我们在发送完成后调用socket.close()
方法关闭套接字。
四、对称加密与非对称加密
因为前面提到加密与解密,所以这里作为补充温故下这两个知识点
1. 对称加密
对称加密使用相同的密钥
(称为密钥)来进行加密和解密数据。发送方
使用密钥将明文数据加密,接收
方使用相同的密钥解密数据。对称加密算法的优势在于处理速度快,适用于大量数据的加密和解密。常见的对称加密算法有AES(高级加密标准)和DES(数据加密标准)等。
下面是一个使用 Node.js 进行对称加密的示例代码:
const crypto = require('crypto');
// 定义密钥和明文数据
const key = 'SecretKey12345678';
const plaintext = 'Sensitive data';
// 创建加密器对象
const cipher = crypto.createCipher('aes-256-cbc', key);
// 加密数据
let encrypted = cipher.update(plaintext, 'utf8', 'hex');
encrypted += cipher.final('hex');
console.log('Encrypted data:', encrypted);
// 创建解密器对象
const decipher = crypto.createDecipher('aes-256-cbc', key);
// 解密数据
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
console.log('Decrypted data:', decrypted);
2. 非对称加密
非对称加密使用一对密钥
,分别是公钥
和私钥
。发送方使用接收方的公钥加密数据,接收方使用自己的私钥解密数据。非对称加密算法的优势在于提供了更高的安全性,可用于密钥交换和数字签名等场景。常见的非对称加密算法有RSA和ECC等。
下面是一个使用 Node.js 进行非对称加密的示例代码:
const crypto = require('crypto');
// 生成密钥对
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
});
// 定义明文数据
const plaintext = 'Sensitive data';
// 使用公钥加密数据
const encrypted = crypto.publicEncrypt(publicKey, Buffer.from(plaintext));
console.log('Encrypted data:', encrypted.toString('base64'));
// 使用私钥解密数据
const decrypted = crypto.privateDecrypt(privateKey, encrypted);
console.log('Decrypted data:', decrypted.toString());
注意
HTTPS(安全的超文本传输协议)使用对称加密和非对称加密的混合方式来实现通信的安全性。对称加密算法用于加密和解密实际的数据传输,而非对称加密算法用于安全地交换对称加密所需的密钥。
写在最后
其实TCP 握手和 HTTPS 握手是软件开发者基本都会了解并且在面试中经常会被问到的问题,本文旨在温故而知新,学习的过程是一个反复确认和实践的过程,也是一个需要被记录和可视化的过程,共勉~