Java后端面试系列-计算机网络篇

Java后端面试系列-计算机网络篇

1 OSI七层概念模型

在这里插入图片描述

1.1 物理层

	目的:解决两台物理机的通信需求,传递的是比特流,也就是010101...二进制数据,先转
成电流的强弱进行传输,达到后转成010101...(也就是所谓的数模转换和模数转换)。网卡
就是工作在这一层。

1.2 数据链路层

	在传输比特流的过程中,会产生错传、数据传输不完整等情况。针对这些情况,数据链路层
定义了如何格式化数据,控制对物理介质的访问,还提供错误检测和纠正以保证数据传输的可
靠性,其中将比特流组成了帧。交换机就是工作在这一层,对帧解码,并将帧中的数据发送到
正确的接收方。

1.3 网络层

	主要工作是将网络地址翻译成对应的物理地址,并决定如何将数据从发送方路由到接收方。
路由器属于网络层。此层的数据成为数据包,需要关注的协议是IP协议。

1.4 传输层

	传输层解决了主机间的数据传输,同时解决了传输质量的问题,该层需要关注的是TCP协议
和UDP协议。

1.5 会话层

	作用是建立和管理应用程序之间的通讯。

1.6 表示层

	解决信息的语法语义及它们的关联,如加密解密、转换翻译、压缩解压缩。

1.7 应用层

	规定应用发送方和接收方必须使用固定长度的消息头,且有固定规则,需要关注的是HTTP
协议。

2 TCP/IP

OSI只是一个概念模型,TCP/IP可以看成OSI的“实现”

OSI七层模型TCP/IP模型功能TCP/IP协议族
应用层应用层文件传输,电子邮件、文件服务,虚拟终端TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet
表示层数据格式化,代码转换,数据加密没有协议
会话层解除或建立与别的接点的联系没有协议
传输层传输层提供端对端的接口TCP,UDP
网络层网络层为数据包选择路由IP,ICMP,RIP,OSPF,BGP,IGMP
数据链路层链路层传输有地址的帧以及错误检测功能SLIP,CSLIP,PPP,ARP,RARP,MTU
物理层以二进制数据形式在物理媒体上传输数据ISO2110,IEEE802,IEEE802.2

3 说说TCP的三次握手

3.1 传输控制协议TCP简介

  • 面向连接的、可靠的、基于字节流的传输层通信协议
  • 将应用层的数据流分割为报文段并发送给目标节点的TCP层
  • 数据包都有序号,对方收到则发送ACK确认,未收到则重传
  • 使用校验和来检验数据在传输过程中的是否有误

3.2 TCP报文头

在这里插入图片描述

  1. 源端口,目的端口:各占2个字节
  2. 序列号:占4个字节,因为字节流中每个字节都按顺序标号,这就是那个号
  3. 确认号:占4个字节,是期望收到的下一个字节的序号
  4. Offset:指出TCP数据距离TCP报文的起始处有多远
  5. Reserved:保留域,默认为0
  6. TCP Flags:占一个字节,详情见3.3
  7. Window:代表滑动窗口的大小,用来告知发送端接收端的缓存大小,以此控制发送端的发送速率,从而达到流量控制。
  8. 校验和:占2个字节
  9. 紧急指针:仅当URG为1时有效,指出报文的紧急数据的字节数
    10.TCP可选项:定义其他的可选参数

3.3 TCP Flags

  • URG:紧急指针标志
  • ACK:确认序号标志(1表示有,0则忽略)
  • PSH:push标志
  • RST:重置连接标志
  • SYN:同步序号,用于建立连接过程
  • FIN:finish标志,用于释放连接

3.4 三次握手流程

在这里插入图片描述
注意:前两次握手不可以携带数据,第三次握手可带可不带,若不带,则不会消耗序号。
(1)第一次握手:建立连接时,客户端发送SYN包(syn=j)到服务端,并进入SYN_SEND状态,等待服务端确认;
(2)第二次握手:服务端接收到SYN包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RCVD状态;
(3)第三次握手:客户端接收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器今日ESTABLISHED状态,完成三次握手

3.5 为什么需要三次握手才能建立连接

为了初始化Sequence Number的初始值
通信双方要通知给对方自己初始化的Sequence Number,这个序列号要作为以后数据通讯的序号以保证传输层接受到的数据不会因网络上的传输问题而乱序,即TCP会用这个序号来拼接数据,因此在服务器回发它的Sequence Number,即第二次握手之后,还需要发送确认报文给服务器,告知服务器客户端已经收到你的Sequence Number了。

3.6 首次握手的隐患——SYN超时

问题起因分析

  • Service收到Client的SYN,回复SYN-ACK的时候未收到ACK
  • Service不断重试直至超时,Linux默认等待63秒才断开连接(重试5次,每个等待时间翻倍,即等待1s,2s,4s,8s,16s,发送五次后,会在等待32秒才判定超时,总共63s)
    针对SYN Flood的防护措施
  • SYN队列满后,TCP会通过源地址端口、目的地址端口、时间戳打造一个特殊的Sequence NUmber,即tcp_syncookies参数(简称SYN Cookie),发回去。
  • 若为攻击者,不会有相应;若为正常连接,则会将这个SYN Cookie发回来,直接建立连接。
  • 通过SYN Cookie,即便SYN队列满了,本请求不在队列中,依然能够建立连接。

3.7 建立连接后,Clinet出现故障怎么办

保活机制

  • 在一段时间内,即保活时间,称为keep alive time,连接处于非活动状态,开启保活机制的一方将向对方发送保活探测报文,如果未收到相应则重发
  • 直到尝试次数达到保活探测数,仍未收到相应则中断连接

4 谈谈TCP的四次挥手

在这里插入图片描述
注1:这一过程又客户端或服务端任意一端执行close来触发,这里假设客户端主动触发close。
注2:MSL代表最大报文生存时间

4.1 四次挥手流程

(1)第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态
(2)第二次挥手:Server收到FIN后,发送一个ACK给Cient,确认确认序号为收到序号+1,Server进入CLOSE_WAIT状态
(3)第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态
(4)第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。

4.2 为什么会有TIME_WAIT状态

原因:

  • 确保有足够的时间让对方收到ACK包
  • 避免新旧连接混淆

4.3 为什么需要四次挥手才能中断连接

因为全双工(即:客户端和服务端都可向对方发送数据),发送方和接收方都需要FIN报文和ACK报文

4.4 服务器大量出现CLOSE_WAIT状态的原因

对方关闭socket连接,我方忙于读或写,没有及时关闭连接
如何处理?

  • 检查代码,特别是释放资源的代码
  • 检查配置,特别是处理请求的线程配置(比如线程池)
// 查看CLOSE_WAIT的数量
$ netstat -n | awk '/^tcp/{++S[$NF]}END{for(a in S) print a,S[a]}'

在这里插入图片描述

5 说说UDP

5.1 UDP报文结构

在这里插入图片描述

5.2 UDP特点

  • 面向无连接
  • 不维护连接状态,支持同时向多个客户端传输相同的消息
  • 数据包报头只有8个字节,额外开销小
  • 吞吐量只受限于数据生成速率、传输速率以及机器性能
  • 尽最大努力交付,不保证可靠交付,不需要维持复杂的链接状态表
  • 面向报文,不对应用程序提交的报文信息进行拆分或者合并

6 TCP和UDP的区别

  1. 面向连接 vs 无连接
  2. 可靠性
  3. 有序性
  4. 速度
  5. 量级

7 TCP的滑动窗口

前置概念

  • RTT:发送一个数据包到收到对应的ACK,所花费的时间
  • RTO:重传时间间隔(不固定,由RTT计算而来)

TCP使用滑动窗口做流量控制与乱序重排,有两个作用:

  1. 保证TCP的可靠性
  2. 保证TCP的流控制特性

TCP头部中的Window字段用于接受方告知发送方自己缓存的剩余空间,发送方根据接受方的处理能力发送数据,不会导致接受方处理不过来。

窗口数据的计算过程
在这里插入图片描述
首先明确左边缓冲中的这些段的意思:
LastByteAcked之前:已发送且已确认的数据
LastByteAcked 到 LastByteSent:已发送但未确认的数据
LastByteSent 到 LastByteWritten:上层应用已写完,但还未发送的数据

在明确右边缓冲中的这些段的意思:
LastByteRead之前:上层应用已经读完,也都发送回执的数据
LastByteRead 到 LastByteExpected:已经收到,但还未发送回执的数据
LastByteRcvd:接收到的最后一个字节的位置

  • 接收方还可以处理的量: AdvertisedWindow = MaxRcvBuffer - (LastByteRcvd - LastByteRead)
  • 窗口内剩余可发送数据的大小:EffectiveWindow = AdvertisedWindow - (LastByteSent - LastByteAcked)

TCP滑动窗口原理


TCP会话的发送方:
在这里插入图片描述
其中,类别2发送但未确认这段和类别3未发送但允许发送这段组成的这段连续空间成为发送窗口
在这里插入图片描述
注意:只有当左边数据(上图是指32到35数据都被确认了)被连续确认之后,滑动窗口才会向右移动,新包括进来的数据才可以发送。


TCP会话的接收方 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200512204450104.png) 其中,类别3未接收但准备接收的这段窗口成为**接收窗口**。只有当左边都接受到数据后,才会向右滑动。

8 HTTP协议

8.1 超文本传输协议HTTP的主要特点

  • 支持客户/服务器模式
  • 简单快速(仅需要请求方法和地址)
  • 灵活(传输类型多)
  • 无连接
  • 无状态(对事务处理没有记忆)

HTTP1.1相较于HTTP1.0最大的特点是:增加了keep-alive

8.2 HTTP请求结构

在这里插入图片描述

8.3 HTTP响应结构

在这里插入图片描述
注意:响应头部与相应正文必有一个空行

8.4 请求/相应步骤

  • 客户端连接到Web服务器
  • 发送HTTP请求
  • 服务器接受请求并返回HTTP响应
  • 释放连接TCP连接
  • 客户端浏览器解析HTML内容

问:HTTP请求方法有哪些?

共支持9种请求方法。分别是:

  1. OPTIONS:返回服务器对特定资源支持的HTTP方法
  2. HEAD:只返回头部响应报文信息
  3. GET:获取特定资源
  4. POST:提交数据进行处理,可能会导致新的资源建立、已有资源修改。
  5. PUT:上传数据
  6. DELETE:删除资源
  7. TREACE:回显服务器收到的请求
  8. CONNECT:将服务器作为代理服务器使用

追问:GET和POST有哪些区别?

  1. POST相对于GET更加安全
  2. POST不具有幂等性,GET具有幂等性
  3. GET可以被缓存
  4. GET发送的数据大小受到浏览器限制,因此小于POST

问:HTTP1.0与HTTP1.1的区别

  1. HTTP1.1默认支持长连接,而HTTP1.0需要设置Connection: keep-alive参数。
  2. HTTP1.1支持至发送头部信息(可用于探测是否有权限访问服务器,若返回100,再发送body;若返回401,则无权限,节约带宽)
  3. HTTP1.1有Host域
  4. HTTP支持请求资源部分内容。

问:HTTP2.0与HTTP1.0区别

  1. HTTP2.0采用二进制格式解析,HTTP1.x是文本解析
  2. HTTP2.0支持多路复用
  3. HTTP2.0实现了头部信息压缩
  4. HTTP2.0服务推送,会将请求相关内容一起发送出去

问:转发与重定向区别?

转发是服务器行为,重定向是客户端行为,转发是客户端连接的地址不变,而重定向时会改变

9 在浏览器地址栏键入URL,按下回车之后经历的流程

  1. DNS解析。服务器会依据URL逐层查询DNS服务器缓存,解析URL域名所对应的IP地址。DNS缓存由近到远依次是:浏览器缓存、系统缓存、路由器缓存、IPS服务器缓存、根域名服务器缓存、顶级域名服务器缓存。从哪个缓存找到对应的IP,就直接返回,不再继续查询
  2. TCP连接。即:三次握手
  3. 发送HTTP请求
  4. 服务器处理请求并返回HTTP报文
  5. 浏览器解析渲染页面
  6. 连接结束。即:四次挥手

注:第5步与第6步可同时发生,哪个在前没有特别要求。

10 HTTP状态码

10.1 五种可能取值

  1. 1××:指示信息——表示请求已接收,继续处理
  2. 2××:成功——表示请求已被成功接受、理解、接受
  3. 3××:重定向——要完成请求必须进行更进一步的操作
  4. 4××:客户端错误——请求有语法错误或请求无法实现
  5. 5××:服务端有错误——服务器未能实现合法的请求

问:常见状态码

200 OK:正常返回信息
204:无响应内容
206:仅返回部分内容
301:永久重定向
302:临时重定向(请求方法可能会从POST变为GET)
304:自上次访问后,网页未修改
307:临时重定向(请求方法不会改变)
400 Bad Request:客户端请求有语法错误,不能被服务器所理解
401 Unauthorized:请求未经授权,这个状态码必须和WWW-Authenticate报头域一起使用
403 Forbidden:服务器收到请求,但是拒绝提供服务
404 Not Found:请求资源不存在,比如输错了URL
500 Internal Server Error:服务器发生了不可预期的错误
503 Server Unavailable:服务器当前不能处理客户端的请求,一段时间后可能恢复正常。

11 GET请求和POST请求的区别

从三个层面来解答:

  1. HTTP报文层面:GET将请求信息放在URL,POST放在报文体中(因此GET对数据长度具有长度限制、POST没有限制)
  2. 数据库层面:GET符合幂等性(即:对数据库的一次操作和多次操作获得数据是一致的)和安全性(即:没有改变数据库中的数据),POST不符合
  3. 其他层面:GET可以被缓存、被存储(大部分请求被CDN处理了,大大减少服务器压力),而POST不行

12 Cookie和Session

12.1 Cookie简介

  • 是由服务器发送给客户端的特殊信息,以文本的形式存放在客户端
  • 客户端再次请求的时候,会把Cookie回发
  • 服务器接收到后,会解析Cookie生成与客户端相对应的内容

12.2 Cookie的设置以及发送过程

在这里插入图片描述

12.3 Session简介

  • 服务器端的机制,在服务器上保存的信息(类似于散列形式)
  • 解析客户端请求并操作session id,按需保存状态信息

先检查请求里是否包含一个session id,若有,则使用此id获取信息;若没有,则创建一个session,并创建一个对应的session id,并返回回去。

12.4 Session的实现方式

  1. 使用Cookie来实现(服务器为每个客户端分配一个JSESSIONID,客户端发起请求时,会在Cookie头中携带这个JSESSIONID)
  2. 使用URL回写来实现(服务器在给客户端发送的每个页面都携带一个JSESSION,客户端点击任意一个连接都会将JSESSIONID带回去)
    在这里插入图片描述
    注:Tomat同时使用这两种方式,若支持Cookie则使用第一种,若不支持,则使用第二种。

12.5 Cookie和Session的区别

  • Cookie数据存放在客户的浏览器上,Session数据放在服务器
  • Session相对于Cookie更安全
  • 若考虑减轻服务器负担,应当使用Cookie

13 HTTPS

13.1 HTTPS简介

在这里插入图片描述
保护交换数据隐私、完整性以及身份认证的功能

13.2 SSL(Security Sockets Layer,安全套接层)

  • 为网络通信提供安全及数据完整性的一种安全协议
  • 是操作系统对外的API,SSL3.0后更名为TLS
  • 采用身份验证数据加密保证网络通信的安全和数据的完整性

13.3 加密方式

  • 对称加密:加密和解密都使用同一个密钥
  • 非对称加密:加密使用的密钥和解密使用的密钥是不相同的
  • 哈希算法:将任意长度的信息转换成固定长度的值,算法不可逆
  • 数字签名:证明某个消息或者文件是某人发出/认同的

13.4 HTTPS数据传输流程

  1. 浏览器将支持的加密算法信息发送给服务器
  2. 服务器选择一套浏览器支持的加密算法,以证书的形式回发浏览器
  3. 浏览器验证证书合法性,并结合证书公钥加密信息发送给服务器
  4. 服务器使用私钥解密信息,验证哈希,加密响应消息回发浏览器
  5. 浏览器解密响应信息,并对消息进行验证,之后进行加密交互数据

13.5 HTTP和HTTPS的区别

  • HTTPS需要到CA申请证书,HTTP不需要
  • HTTPS密文传输,HTTP明文传输
  • 连接方式不同,HTTPS默认使用443端口,HTTP使用80端口
  • HTTPS=HTTP+加密+认证+完整性保护,较HTTP安全

13.6 HTTPS真的很安全吗

那倒未必

  • 浏览器默认填充http://,请求需要进行跳转,又被劫持的风险
  • 可使用HSTS(HTTP Strict Transport Security)优化

14 Socket

14.1 Socket简介

Socket是对TCP/IP协议的抽象,是操作系统对外开放的接口
在这里插入图片描述

14.2 Socket通信流程

在这里插入图片描述

14.3 Socket相关的面试题

	编写一个网络应用程序,有客户端与服务器端,客户端向服务器发送一个字符串,服务器收到
该字符串后将其打印到命令行上,然后向客户端返回该字符串的长度,最后,客户端输出服务器
端返回的该字符串的长度,分别用TCP和UDP两种方式去实现

TCP:
LengthCalculator.java

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class LengthCalculator extends Thread{
    // 以socket为成员变量
    private Socket socket;

    LengthCalculator(Socket socket){
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            // 获取socket的输出流
            OutputStream os = socket.getOutputStream();
            // 获取socket的输入流
            InputStream is = socket.getInputStream();
            int ch = 0;
            byte[] buff = new byte[1024];
            // buff主要用来读取输入的内容,存成byte数组,ch主要用来获取读取数组的长度
            ch = is.read(buff);
            // 将接受流的byte数组转换成字符串,这里获取的内容是客户端发送过来的字符串参数
            String content = new String(buff, 0, ch);
            System.out.println(content);
            // 往输出流里写入获得字符串的长度,回发给客户端
            os.write(String.valueOf(content.length()).getBytes());
            // 关闭输入输出流以及socket
            is.close();
            os.close();
            socket.close();
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

TCPServer.java

import java.net.ServerSocket;
import java.net.Socket;

public class TCPServer {
    public static void main(String[] args) throws Exception{
        // 创建socket,并将socket绑定到6499端口
        ServerSocket ss = new ServerSocket(6499);
        // 死循环,使得socket一直等待并处理客户端发送过来的请求
        while (true) {
            // 监听65000端口,直到客户端返回连接信息后才返回
            Socket socket = ss.accept();
            // 获取客户端的请求信息后,执行相关业务逻辑
            new LengthCalculator(socket).start();
        }
    }
}

TCPClient.java

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class TCPClient {
    public static void main(String[] args) throws Exception{
        // 创建socket,并指定连接是本机的端口号为6499的服务器socket
        Socket socket = new Socket("127.0.0.1", 6499);
        // 获取输出流
        OutputStream os = socket.getOutputStream();
        // 获取输入流
        InputStream is = socket.getInputStream();
        // 将要传递给server的字符串参数转换给byte数组,并将数组写入到输入流中
        os.write("hello world".getBytes());
        int ch = 0;
        byte[] buff = new byte[1024];
        // buff主要用来读取输入的内容,存成byte数组,ch主要用来获取读取数组的长度
        ch = is.read(buff);
        // 将接收流的byte数组转换成字符串,这里是从服务端回发回来的字符串参数的长度
        String content = new String(buff, 0, ch);
        System.out.println(content);
        // 关闭输入输出流以及socket
        is.close();
        os.close();
        socket.close();
    }
}

先执行Server,后执行Client,结果如下:
在这里插入图片描述
在这里插入图片描述
UDP:
UDPServer.java

import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class UDPServer {
    public static void main(String[] args) throws Exception{
        // 服务器接受客户端发送的数据报
        DatagramSocket socket = new DatagramSocket(6599); // 监听的端口号
        byte[] buff = new byte[100]; // 存储从客户端接收到的内容
        DatagramPacket packet = new DatagramPacket(buff, buff.length);
        // 接受客户端发送过来的内容,并将内容封装进DatagramPacket对象中
        socket.receive(packet);

        byte[] data = packet.getData(); // 从DatagramPacket对象中获取真正存储的数据
        // 将数据从二进制转换成字符串形式
        String content = new String(data, 0, packet.getLength());
        System.out.println(content);
        // 将要发送给客户端的数据转换成二进制
        byte[] sendedContent = String.valueOf(content.length()).getBytes();
        // 服务器给客户端发送数据报
        // 从DatagramPacket对象中获取到数据的来源地址与端口号
        DatagramPacket packetToClient = new DatagramPacket(sendedContent,
                sendedContent.length, packet.getAddress(), packet.getPort());
        socket.send(packetToClient);
    }
}

UDPClient.java

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class UDPClient {
    public static void main(String[] args) throws Exception{
        // 客户端发送数据报给服务端
        DatagramSocket socket = new DatagramSocket();
        // 要发送给服务端的数据
        byte[] buf = "Hello UDP".getBytes();
        // 将IP地址封装成InetAddress对象
        InetAddress address = InetAddress.getByName("127.0.0.1");
        DatagramPacket packet = new DatagramPacket(buf, buf.length, address,
                6599);
        // 发送数据给服务端
        socket.send(packet);

        // 客户端接受服务端发送过来的数据报
        byte[] data = new byte[100];
        // 创建DatagramPacket对象用来存储服务端发送过来的数据
        DatagramPacket reveivedPacket = new DatagramPacket(data, data.length);
        // 将接受到的数据存储到DatagramPacket对象中
        socket.receive(reveivedPacket);

        // 将服务端发送过来的数据取过来并打印到控制台
        String content = new String(reveivedPacket.getData(), 0,
                reveivedPacket.getLength());
        System.out.println(content);
    }
}

先执行Server,后执行Client,结果如下:
在这里插入图片描述
在这里插入图片描述

  • 9
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值