一次性搞定:三次握手、四次挥手、跨域

412 阅读7分钟

三次握手

简单点说,三次握手是在TCP协议中用于建立一个可靠的连接的过程,也就是客户端和服务端连接。这个过程通常涉及到客户端和服务器之间的通信。

图示:

image.png

第一次握手

客户端发送连接请求报文到服务端,客户端状态进入 SYN-SEND 状态,报文包含了一些连接的初始信息,例如序列号等。那么这个过程也叫“SYN”(同步)。

这个过程保证客户端能够到达服务器。

第二次握手

服务端接收到请求连接报文后,如果接受连接,就会返回一个确认报文(包含ACK序号),服务端进入 SYN-RECEIVED状态。这个过程通常称为“SYN-ACK”(同步-确认)。

这个过程确保服务器能够接收来自客户端的连接。

第三次握手

客户端接收到了同意连接的报文后,还要向服务端发送一个确认收到的报文。客户端进入ESTABLISHED状态,表示正式连接成功。这个过程通常称为“ACK”(确认)。 这一步确保客户端知道服务器已经接受了连接,同时也确保服务器知道客户端已经接受了确认。

拓展

那么三次握手的基本知识就这样了,但是面试官可能会觉得还不够,可能还会来一句:“那你知道为什么一定要三次握手吗?两次可以吗?” 很明显两次肯定不行,那么到底如何表达呢?

正解: 不行,假设客户端给服务端发送了一个建立连接请求A,但是因为网络环境差,这个请求A超时了,那么TCP会启动超时重传机制,再发送一个新的建立连接请求B,服务端接收到B请求后应答,如果此时就完成建立连接的话,当客户端和服务端通信完成后,便释放了连接,双方都进入Closed状态。

假设此时A请求又抵达了服务端,那么服务端会认为客户端又要建立新的链接从而应答该请求并进入RECEIVED状态,而此时客户端是Close状态,那么服务端就会一直等待,造成资源浪费。所以这三步缺一不可。

这个时候面试官可能还会来一句:“你懂的这么多,那你讲讲四次挥手吧!” 没事,我马上给你讲明白。

四次挥手

握手代表着连接,那么挥手就代表着要说再见了。没错,挥手其实就是这个意思!四次挥手是在TCP连接关闭时,用于释放连接资源的过程。 这个过程通常涉及到客户端和服务器之间的通信。

图示:

image.png

第一次挥手(客户端发送)

客户端A认为数据发送完成,则向服务端B发送释放连接请求。也就是发送请求关闭连接的报文。

第二次挥手(服务器确认)

B收到释放连接请求后,返回一个ACK(确认)报文,并进入到CLOSE_WAIT状态,此时B不再接受A发送的数据,但是B仍然可以给A发送数据。

第三次挥手(服务器发送)

B如果此时还有没发完的数据,就会继续发送,发完后向A发送释放连接的请求,B进入到LAST-ACK(最终确认)状态。

第四次挥手(客户端确认)

A收到释放连接的请求,向B发送应答,进入CLOSED状态,B接受到该应答进入CLOSED状态。 完成四次挥手后,TCP连接就被完全关闭,双方释放了连接资源,可以安全地结束通信。

这时面试官应该很满意了,但是他可能还想知道你是否了解过跨域,那么下面我们来聊聊跨域。

跨域

跨域相信大家都遇到过吧,请求后端的数据时,报错:

屏幕截图 2024-03-14 201217.png

跨域是指在浏览器中,当一个页面加载来自不同域名、协议或端口的资源时,就会发生跨域。这是由于浏览器的同源策略(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>

最后

本篇比较干,希望能让大家在春招中不再被这些问题难住。

欢迎在评论区发言!