聊聊分布式——http通信基础

目录

web通信的简单结构

网络通信基础TCP/IP

TCP/IP 通信传输流

HTTP中的三剑客

负责传输的IP协议

确保可靠性的TCP协议

SYN攻击(SYN Flood Attack)

四次挥手

负责域名解析的DNS服务

基于 TCP 协议实现通信

TCP 协议的通信过程


Web通信的简单结构

Web 使用一种名为 HTTP(HyperText Transfer Protocol,超文本传输协议)的协议作为规范,完成从客户端到服务器端等一系列运作流程。举个例子,我们使用浏览器访问百度:www.baidu.com(Ip:220.181.38.150)。

聊聊分布式——http通信基础_第1张图片

  1. DNS域名解析:

    DNS(Domain Name System)服务是和HTTP协议一样位于应用层的协议。它提供域名到IP之间的解析服务。

  2. http访问web

聊聊分布式——http通信基础_第2张图片

网络通信基础TCP/IP

通常使用的网络(包括互联网)是在TCP/IP协议族的基础上运作的。TCP/IP协议族四层结构分为:应用层、传输层、网络层和数据链路层。

  • 应用层:决定了向用户提供应用服务时通信的活动。

  • 传输层:提供处于网络连接中的两台计算机之间的数据传输。常见的是TCP和UDP两种性质不同的协议。

    • TCP(Transmission Control Protocol)

    • UDP(User Data Protocol)

  • 网络层:用来处理在网络上流动的数据包。

  • 链路层(数据链路层、网络接口层):用来处理连接网络的硬件部分。

当然也存在OSI七层机构和TCP/IP五层结构的划分方式。

聊聊分布式——http通信基础_第3张图片

TCP/IP 通信传输流

发送端在层与层之间传输数据时,每经过一层时必定会被打上一个该层所属的首部信息。反之,接收端在层与层传输数据时,每经过一层时会把对应的首部消去。

聊聊分布式——http通信基础_第4张图片

HTTP中的三剑客

在TCP/IP协议族中与HTTP密不可分的3个协议:IP协议、TCP协议和DNS协议

  • 负责传输的IP协议

    IP 协议的作用是把各种数据包传送给对方。其中两个重要的条件是 IP 地址和 MAC 地址(Media Access Control Address)。IP 地址可以和 MAC 地址进行配对。IP 地址可变换,但 MAC 地址基本上不会更改。

    使用ARP协议凭借MAC地址进行通信,这个过程像快递公司的送货过程。寄件人将货物送到集散中心,快递公司根据收件地址就能确定下一站该送往哪个区域的集散中心。这种机制也叫路由选择、

  • 确保可靠性的TCP协议

    为了准确无误地将数据送达目的地,TCP协议采用了三次握手(three-way handshaking)策略。握手过程中使用了TCP的标志(flag)-SYN(synchronize)和ACK(acknowledgement)。只有三次握手,Client和Server才能相互确认双相连接,实现双工数据传输。

    聊聊分布式——http通信基础_第5张图片
    • 第一次握手(SYN=1,seq=x):客户端发送一个TCP的SYN标志位置1的包,指明要连接的服务器端口以及初始序号x,保存在包头的序列号(Sequence-Number)字段中,发送完毕后,客户端进入SYN-SEND状态。

    • 第二次握手(SYN=1,ACK=1,seq=y,ack=x+1):服务器发回确认包(ACK)应答,即SYN和ACK标志位均为1。服务器将自己ISN(初始序列号)放入Seq中,同时将ack设置为客户端的ISN+1即x+1.发送完毕后,服务器进入SYN-RCVD状态。

    • 第三次握手(ACK=1,seq=x+1,ack=y+1):客户端再次发送确认包(ACK),SYN为0,ACK为1,并且把ack设为服务器的ISN+1即y+1发送给对方,数据发送完毕,客户端进入ESTABLISHED状态,当服务器接收到这个包时,也进入ESTABLISHED状态,TCP握手结束。

    SYN攻击(SYN Flood Attack)

    是一种网络攻击,通常针对TCP协议的三次握手过程中的漏洞进行攻击。SYN攻击旨在使目标服务器耗尽资源,导致其无法正常处理新的连接请求,从而使服务不可用。以下是SYN攻击的工作原理和防御方法:

    工作原理

    1. 攻击者发送大量伪造的TCP连接请求(SYN请求)到目标服务器。

    2. 目标服务器收到这些伪造的连接请求后,会为每个请求分配一些资源,如内存和连接表项,然后回复一个SYN-ACK响应,等待客户端的确认。

    3. 攻击者不回复服务器的SYN-ACK响应,或者回复得很慢,从而使服务器一直等待确认。

    4. 由于服务器需要等待确认,它不断累积未完成的连接请求,消耗了系统资源,最终可能无法再接受新的合法连接请求,导致拒绝服务(DoS)。

      防御方法

    1. SYN Cookies:一种常见的防御方法是使用SYN Cookies。在使用SYN Cookies的情况下,服务器不会为每个连接请求分配资源,而是根据客户端的请求生成一个特殊的标识符(SYN Cookie)。只有在客户端回复ACK时,服务器才会分配资源。这可以减轻攻击对服务器资源的影响。

    2. 调整连接资源限制:可以调整服务器的TCP连接资源限制,限制每个IP地址的并发连接数,以减轻攻击的影响。这可以通过操作系统的设置来实现。

    3. 使用防火墙和入侵检测系统(IDS/IPS):防火墙和IDS/IPS可以用于检测和过滤恶意的SYN请求流量。它们可以识别和拦截大规模的SYN攻击尝试。

    4. 网络流量分析:监控和分析网络流量,及时检测异常流量模式,可以帮助发现SYN攻击。

    5. 升级硬件和网络带宽:增加服务器的硬件资源和网络带宽可以增加其抵御SYN攻击的能力,但这并不是解决问题的根本方法。

    6. 使用CDN:使用内容分发网络(CDN)可以分担攻击流量,减轻服务器的负担,提高服务的可用性。

    四次挥手

    四次挥手的目的是关闭一个连接,连接存在时,客户端和服务器均可主动发起挥手动作(TCP是一个全双工协议),在socket编程中,任何一方执行close()操作即可产生挥手操作。

    聊聊分布式——http通信基础_第6张图片
    • 第一次挥手(FIN=1,seq=u):假设客户端想要关闭连接,客户端发送一个 FIN 标志位置为 1 的包,表示自己已经没有数据可以发送了,但是仍然可以接受数据。发送完毕后,客户端进入 FIN_WAIT_1 状态。

    • 第二次挥手(ACK=1,ack=u+1):服务器端确认客户端的 FIN 包,发送一个确认包,表明自己接受到了客户端关闭连接的请求, 但还没有准备好关闭连接。发送完毕后,服务器端进入 CLOSE_WAIT 状态,客户端接收到这个确认包之后,进入 FIN_WAIT_2 状态,等待服务器端关闭连接。

    • 第三次挥手(FIN=1,seq=w):服务器端准备好关闭连接时,向客户端发送结束连接请求,FIN 置为 1。发送完毕后,服务器端进入 LAST_ACK 状态,等待来自客户端的最后一个 ACK。

    • 第四次挥手(ACK=1,ACKnum=w+1):客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME_WAIT 状态,等待 可能出现的要求重传的 ACK 包。 服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。 客户端等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime) 之后,没有收到服务器端的 ACK,认为服务器端已经正常关闭连接,于是自己也关闭连接, 进入 CLOSED 状态。

    为什么连接的时候是三次握手,关闭的时候却是四次握手?

    三次握手是因为因为当 Server 端收到 Client 端的 SYN 连接请求报文后,可以直接发送 SYN+ACK 报文。其中 ACK 报文是用来应答的,SYN 报文是用来同步的。

    但是关闭连接时, 当 Server 端收到 FIN 报文时,很可能并不会立即关闭 SOCKET(因为可能还有消息没处理 完),所以只能先回复一个 ACK 报文,告诉 Client 端,"你发的 FIN 报文我收到了"。只有等到 我 Server 端所有的报文都发送完了,我才能发送 FIN 报文,因此不能一起发送。故需要四步握手。

  • 负责域名解析的DNS服务

    DNS(Domain Name System)服务是和 HTTP 协议一样位于应用层的协议。它提供域名到 IP 地址之间的解析服务。

基于 TCP 协议实现通信

TCP是一个全双工协议,数据通信允许数据同时在两个方向上传输,因此全双工是两个单工通信方式的结合,它要求发送设备和接收设备都有独立的接收和发送能力。通过Socket简单地模拟下双工通信。

  • server端实现:

    public class ServerSocketDemo {
        public static void main(String[] args) throws IOException {
            try {
                ServerSocket server = null;
                // 创建一个serverSocket在端口8080监听客户端请求
                server = new ServerSocket(8080);
                Socket socket = null;
                try {
                    socket = server.accept(); // 使用accept()阻塞等待客户请求
                } catch (IOException e) {
                    e.printStackTrace();
                }
                String line;
                // 由Socket对象得到输入流,并构造响应的BufferedReader对象
                BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    ​
                // 由Socket对象得到输出流,并构造PrintWriter对象
                PrintWriter os = new PrintWriter(socket.getOutputStream());
                // 由系统标准输入设备构造BufferedReader对象
                BufferedReader sin = new BufferedReader(new InputStreamReader(System.in));
                // 在标准输出上打印从客户端读入的字符串
                System.out.println("Client:" + is.readLine());
                // 从标准输入读入字符串
                line = sin.readLine(); 
                // 读到“bye”停止循环
                while (!line.equals("bye")) {
                    // 向客户端输出该字符串
                    os.println(line); 
                    // 刷新输出流,使Client马上收到该字符串
                    os.flush();
                    // 在系统标准输出上打印读入的字符串
                    System.out.println("Server:" + line);
                    // 从Client读入字符串,并打印到标准输出上
                    System.out.println("Client:" + is.readLine());
                    // 从系统标准输入读入字符串
                    line = sin.readLine(); 
                }
                os.close();
                is.close();
                socket.close();
                server.close();
            } catch (IOException e) {
                System.out.println("Error:" + e);
            }
        }
    }
  • client端实现:

    public class ClientSocketDemo {
        public static void main(String[] args) {
            try {
                // 找到目标serverSocket的地址和端口
                Socket socket = new Socket("localhost", 8080);
    ​
                // 控制台的输入流
                BufferedReader sin = new BufferedReader(new InputStreamReader(System.in));
    ​
                // 在当前连接上写入数据
                PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
    ​
                // 拿到输入流
                BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    ​
                String readline = sin.readLine();
                while (!readline.equals("bye")) {
                    out.println(readline);
                    out.flush();
                    System.out.println("Client:"+readline);
                    System.out.println("Server:"+in.readLine());
                    readline = sin.readLine(); // 重新获取
                }
                out.close();
                in.close();
                socket.close();
            } catch (IOException e) {
                System.out.println("Error" + e);
            }
        }
    }
TCP 协议的通信过程

首先,对于 TCP 通信来说,每个 TCP Socket 的内核中都有一个发送缓冲区和一个接收缓冲 区,TCP 的全双工的工作模式及 TCP 的滑动窗口就是依赖于这两个独立的 Buffer 和该 Buffer 的填充状态。

缓冲区

接收缓冲区把数据缓存到内核,若应用进程一直没有调用 Socket 的 read 方法进行读取,那么该数据会一直被缓存在接收缓冲区内。不管进程是否读取 Socket,对端发来的数据都会经 过内核接收并缓存到 Socket 的内核接收缓冲区。 read 所要做的工作,就是把内核接收缓冲区中的数据复制到应用层用户的 Buffer 里。 进程调用 Socket 的 send 发送数据的时候,一般情况下是将数据从应用层用户的 Buffer 里复 制到 Socket 的内核发送缓冲区,然后 send 就会在上层返回。换句话说,send 返回时,数据不一定会被发送到对端。

聊聊分布式——http通信基础_第7张图片

Socket 的接收缓冲区被 TCP 用来缓存网络上收到的数据,一直保存到应用进 程读走为止。如果应用进程一直没有读取,那么 Buffer 满了以后,出现的情况是:通知对端 TCP 协议中的窗口关闭,保证 TCP 接收缓冲区不会移除,保证了 TCP 是可靠传输的。如果对方无视窗口大小发出了超过窗口大小的数据,那么接收方会把这些数据丢弃。

滑动窗口协议

这个过程中涉及到了 TCP 的滑动窗口协议,滑动窗口(Sliding window)是一种流量控制技术。早期的网络通信中,通信双方不会考虑网络的拥挤情况直接发送数据。由于大家不知道 网络拥塞状况,同时发送数据,导致中间节点阻塞掉包,谁也发不了数据,所以就有了滑动窗口机制来解决此问题;发送和接受方都会维护一个数据帧的序列,这个序列被称作窗口。

发送窗口

就是发送端允许连续发送的帧的序号表。 发送端可以不等待应答而连续发送的最大帧数称为发送窗口的尺寸。

接收窗口

接收方允许接收的帧的序号表,凡落在接收窗口内的帧,接收方都必须处理,落在接收窗口外的帧被丢弃。 接收方每次允许接收的帧数称为接收窗口的尺寸。

接收窗口 接收方允许接收的帧的序号表,凡落在接收窗口内的幀,接收方都必须处理,落在接收窗口外的帧被丢弃。 接收方每次允许接收的帧数称为接收窗口的尺寸。

你可能感兴趣的:(分布式架构,分布式,http,网络协议)