三次握手
简单点说,三次握手是在TCP协议中用于建立一个可靠的连接的过程,也就是客户端和服务端连接。这个过程通常涉及到客户端和服务器之间的通信。
图示:
第一次握手
客户端发送连接请求报文到服务端,客户端状态进入 SYN-SEND 状态,报文包含了一些连接的初始信息,例如序列号等。那么这个过程也叫“SYN”(同步)。
这个过程保证客户端能够到达服务器。
第二次握手
服务端接收到请求连接报文后,如果接受连接,就会返回一个确认报文(包含ACK序号),服务端进入 SYN-RECEIVED状态。这个过程通常称为“SYN-ACK”(同步-确认)。
这个过程确保服务器能够接收来自客户端的连接。
第三次握手
客户端接收到了同意连接的报文后,还要向服务端发送一个确认收到的报文。客户端进入ESTABLISHED状态,表示正式连接成功。这个过程通常称为“ACK”(确认)。
这一步确保客户端知道服务器已经接受了连接,同时也确保服务器知道客户端已经接受了确认。
拓展
那么三次握手的基本知识就这样了,但是面试官可能会觉得还不够,可能还会来一句:“那你知道为什么一定要三次握手吗?两次可以吗?” 很明显两次肯定不行,那么到底如何表达呢?
正解: 不行,假设客户端给服务端发送了一个建立连接请求A,但是因为网络环境差,这个请求A超时了,那么TCP会启动超时重传机制,再发送一个新的建立连接请求B,服务端接收到B请求后应答,如果此时就完成建立连接的话,当客户端和服务端通信完成后,便释放了连接,双方都进入Closed状态。
假设此时A请求又抵达了服务端,那么服务端会认为客户端又要建立新的链接从而应答该请求并进入RECEIVED状态,而此时客户端是Close状态,那么服务端就会一直等待,造成资源浪费。所以这三步缺一不可。
这个时候面试官可能还会来一句:“你懂的这么多,那你讲讲四次挥手吧!” 没事,我马上给你讲明白。
四次挥手
握手代表着连接,那么挥手就代表着要说再见了。没错,挥手其实就是这个意思!四次挥手是在TCP连接关闭时,用于释放连接资源的过程。 这个过程通常涉及到客户端和服务器之间的通信。
图示:
第一次挥手(客户端发送)
客户端A认为数据发送完成,则向服务端B发送释放连接请求。也就是发送请求关闭连接的报文。
第二次挥手(服务器确认)
B收到释放连接请求后,返回一个ACK(确认)报文,并进入到CLOSE_WAIT状态,此时B不再接受A发送的数据,但是B仍然可以给A发送数据。
第三次挥手(服务器发送)
B如果此时还有没发完的数据,就会继续发送,发完后向A发送释放连接的请求,B进入到LAST-ACK(最终确认)状态。
第四次挥手(客户端确认)
A收到释放连接的请求,向B发送应答,进入CLOSED状态,B接受到该应答进入CLOSED状态。 完成四次挥手后,TCP连接就被完全关闭,双方释放了连接资源,可以安全地结束通信。
这时面试官应该很满意了,但是他可能还想知道你是否了解过跨域,那么下面我们来聊聊跨域。
跨域
跨域相信大家都遇到过吧,请求后端的数据时,报错:
跨域是指在浏览器中,当一个页面加载来自不同域名、协议或端口的资源时,就会发生跨域。这是由于浏览器的同源策略(Same-Origin Policy)所限制的安全机制。
什么是同源策略?
这是一个很普遍的url地址:https://192.168.31.45:8080/user
https:// 是它的协议号,192.168.31.45是它的域名(IP地址),8080是它的端口号,/user是路径。
而同源策略要求两个url地址的协议号-域名-端口均要相同,否则不同源。 与路径没有关系。当我们加载来自不同域名、协议或端口的资源时,后端返回给浏览器的数据会被浏览器的同源策略给拦截下来。
同源策略的主要目的就是保证数据的安全性,防止恶意网站通过跨域请求获取用户的敏感信息。虽然同源策略非常有用,但是对我们实际开发就没有那么友好了,那么我们该如何应对跨域问题呢?
解决跨域
1.JSONP
利用 <script> 标签的跨域解决方案,该标签的 src 属性指向服务器端的资源地址(向后端发送请求),由于浏览器不会对 <script> 标签加载的数据进行同源策略的检查,因此可以跨域请求数据。
前端:
function jsonp(url, cb){
return new Promise((resolve,reject) => { //为了.then
//原理
const script = document.createElement('script')
script.src = `${url}?cb=${cb}`;
document.body.appendChild(script)
window[cb] = (data) => {
resolve(data);
}
})
}
后端(基于koa):
const main = (ctx,next) => {
console.log(ctx.query);
const cb = ctx.query.cb
const data = '给前端的数据'
const str = `${cb}('${data}')` // 'callback("给前端的数据")'
ctx.body = str
}
- 先返回一个Promise对象处理异步,再去创建一个script标签,利用src属性给后端发请求,
?cb=${cb}代表携带一个参数'callback'。 - 后端接受到了这个参数 'callback' 后(cb就是前端传递的参数),将要返回给前端的数据 和 这个参数'callback' 进行拼接成
'callback(data)'字符串并返回。 - 后端传回的数据要在全局使用,所以
window[cb] = (data) => {..},前端在 Window对象上添加了一个 callback函数,因为后端返回了一个形如'callback(data)'的响应体,浏览器会将该字符串执行成callback的调用。
JSONP的巧妙之处就在于 <script> 标签中的src属性不受同源策略的影响这一机制,来实现跨域。那么不止有<script> 标签,<img> 标签也不受同源策略的影响,也可以用于跨域。
但是该方法也有一些缺点:1. 必须要后端配合。 2. 因为浏览器加载数据默认只能是GET,所以只能用于发GET请求。
2.CORS(Cross-Origin Resource Sharing)
这个方法只需要后端操作即可。后端通过设置响应头(在响应头中设置白名单)来告诉浏览器不要拒绝接受后端的响应。
const server = http.createServer((req,res) =>{
//设置响应头
res.writeHead(200, {
// 设置白名单,*代表所有,不在乎源了
// 'Access-Control-Allow-Origin': '*'
'Access-Control-Allow-Origin': 'http://127.0.0.1:5500'
})
let data = {
msg: "hello cors"
}
res.end(JSON.stringify(data)) //向前端返回数据
})
后端需要在响应头中设置特定的 CORS 头信息,例如 Access-Control-Allow-Origin,用于指定允许访问数据的源(上述例子是允许 http://127.0.0.1:5500 的源访问)。
3.node代理(vite 、开发环境下生效)
基于vue项目,在项目挂载中利用axios发送请求:
// 发请求
import axios from 'axios'
import { onMounted } from 'vue';
onMounted(() => {
axios.get('/api').then(res => {
console.log(res);
})
})
再去配置项目的开发环境(vite.config.js文件),就可实现跨域。
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
// node代理
server: {
proxy: { //配置一个代理
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/,'')
}
}
}
})
vite帮我们启动了一个node服务,且帮我们朝 http://localhost:3000 发起请求,因为后端没有同源策略,所以很轻易的请求到了数据。前端只需要向'/api'发请求即可。vite中的node服务能直接请求到数据,再提供给前端使用,起到了代理的作用,并规避了同源策略。
4.nginx代理
这个办法类似CORS,也是白名单的配置,生产环境下常用,这里就不给大家展示代码了。
5.postMessage (在iframe中)
父级页面中(a.html)
<body>
<h2>a.html</h2>
<iframe src="http://127.0.0.1:5500/%E7%99%BE%E5%BA%A6%E9%9D%A2%E8%AF%95%E9%A2%98/postMessage/b.html" frameborder="0" id="iframe"></iframe>
<script>
// 给b发送数据
let iframe = document.getElementById('iframe')
iframe.onload = function() {
let data = {
name: 'Tom'
}
iframe.contentWindow.postMessage(JSON.stringify(data),'http://127.0.0.1:5500')
}
// 监听b传过来的数据
window.addEventListener('message',(e) => {
console.log(e.data);
})
</script>
</body>
子级页面中(b.html)
<body>
<h4>b.html</h4>
<script>
window.addEventListener('message', (e) => {
console.log(JSON.stringify(e.data));
if(e.data) {
setTimeout(function() {
window.parent.postMessage('我接受到','http://127.0.0.1:5500')
},1000)
}
})
</script>
</body>
最后
本篇比较干,希望能让大家在春招中不再被这些问题难住。
欢迎在评论区发言!