面试之网络基础

零、基础知识

OSI七层理论体系结构

  1. 物理层:解决两台主机的通信问题——A往B发送比特流(0101),B能接收到这些比特流。定义了物理设备的标准如网线的类型,光纤的接口类型以及传输介质的传输速率等。

  2. 数据链路层:由于物理层上的传输的比特流可能会出现错传、误传等,所以数据链路层定义了如何格式化数据即将比特流封装成,提供了错误检测。

  3. 网络层:随着节点的增加,点对点通信是需要经过多个节点的,如何找到目标节点,如何找到最优路径变成为了首要需求。所以出现了网络层,主要目的是将网络地址翻译成对应的物理地址,分组传输、路由选择,本层的传输单位是数据报(分组),本层需要注意的TCP/IP协议中的TCP协议。

  4. 传输层:随着网络需要的进一步扩大,通信过程中需要传输大量的数据,网络可能会发生中断,为了保证传输大量文件时的准确性,需要对发送的数据进行切分,切分成一个个的segment进行发送,考虑如何在接受方拼接切分的segment组成完整的数据,以及发现丢失segment时该如何处理,需要注意的协议TCP、UDP。

  5. 会话层:不同机器上的用户之间建立以及管理会话。用于保证应用程序自动收发包和寻址。

  6. 表示层:信息的语义语法,加密解密,转换翻译,压缩解压缩。

  7. 应用层:规定双方必须使用固定长度的消息头,且消息头必须记录消息长度等信息。需要注意的是TCP/IP协议中的HTTP协议。

TCP/IP四层模型

是OSI的一种实现,包括应用层、运输层、网际层和网络接口层。

一、TCP的三次握手

传输控制协议TCP:

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

序号字段的值则指的是本报文段所发送的数据的第一个字节的序号。

确认号字段——占 4 字节,是期望收到对方的下一个报文段的数据的第一个字节的序号。

确认 ACK —— 只有当 ACK = 1 时确认号字段才有效。

同步 SYN —— 同步 SYN = 1 表示这是一个连接请求或连接接受报文。

终止 FIN (FINish) —— 用来释放一个连接。FIN = 1 表明此报文段的发送端的数据已发送完毕,并要求释放运输连接。

TCP的三次握手回答

  1. 第一次握手:建立连接时,客户端发送请求SYN包,SYN=1,seq=x,客户端进入SYN_SENT状态,等待服务器的确认。

  2. 第二次握手:服务器收到SYN报文段,需要对这个SYN报文段进行确认,设置ack=x+1,同时自己还要发送SYN请求信息给客户端,所以SYN=1,seq=y。服务器将上述信息放到SYN+ACK报文段中一并发给客户端,此时服务器进入SYN_RECV状态。

  3. 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(其中ack=y+1),此包发送完毕后,客户端和服务器进入ESTABLISHED状态,完成三次握手。

二、TCP的四次挥手

TCP的四次挥手回答:TCP采用四次挥手来释放连接。

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

为什么有TIME_WAIT状态?

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

为什么需要四次挥手才能断开连接?

因为全双工,发送方和接收方都需要FIN报文和ACK报文。

三、TCP和UDP的区别

UDP报文示意图:

UDP的特点:

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

TCP和UDP的区别回答:

  1. TCP是面向连接的,UDP是无连接的
  2. TCP比UDP是更可靠的(握手和确认重传机制)
  3. TCP是有序的,UDP是无序的。
  4. TCP速度相比UDP慢(需要建立连接)
  5. TCP相比UDP的开销更大(TCP首部20个字节,UDP首部8个字节)

四、HTTP与HTTPS

HTTP是在TCP/IP四层模型中的协议,超文本传输协议HTTP主要特点如下:

  • 支持客户端/服务器模式
  • 简单快速
  • 灵活
  • 无连接
  • 无状态

面:在浏览器地址栏中输入URL,按下回车之后经历的流程

回答:

  1. DNS解析:首先浏览器会根据URL逐层查询DNS服务器缓存,解析URL中域名所对应的IP地址,DNS缓存从近到远依次是:浏览器缓存,系统缓存,路由器缓存,IPS服务器缓存,根域名服务器缓存,顶级域名服务器缓存,当找到IP后,直接返回无需查询下一层缓存。
  2. TCP连接:根据IP和端口(默认80)和服务器建立TCP连接,通过TCP的三次握手。
  3. 发送HTTP请求
  4. 服务器处理请求并返回HTTP报文
  5. 浏览器解析响应报文并渲染页面
  6. 连接结束,浏览器释放TCP连接,通过TCP的四次挥手。

面:说说常见的HTTP状态码。

答:HTTP响应状态码有5种可能的取值:

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

面:GET请求和POST请求的区别

  1. Http报文层面:GET请求将请求信息放在URL中,POST将请求信息放在报文体中
  2. 数据库层面:GET符合幂等性(对数据库的一次操作或多次操作获得的结果是一致的)和安全性(对数据库的操作没有改变数据库的数据),POST不符合。
  3. 其他层面:GET请求可以被缓存、被存储,而POST不行

Cookie和Session的区别

Cookie简介:

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

Cookie的设置以及发送过程如下:

Session简介:

  • 服务器端的机制,在服务器上保存的信息
  • 解析客户端请求并操作session id,按需保存状态信息

Session的实现方式:

  1. 使用Cookie实现

  2. 使用URL回写来实现(返回给浏览器的所有链接中都携带JSESSIONID参数)

面:Cookie和Session的区别

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

HTTPS

HTTPS相对于HTTP就是多了一层SSL(Security Sockets Layer,安全套接层)。SSL定义如下:

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

加密的方式

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

HTTPS数据传输流程

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

面:HTTP和HTTPS的区别

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

五、Socket

我们知道如果两个进程如果要进行通信的话,那么首先应该能够唯一地标识这2个进程,在本地进程中我们可以使用PID唯一标识进程,那么在网络中呢?在网络中,各个主机的PID可能会重复,因此光靠PID就不能唯一标识进程了。前面我们知道TCP/IP协议中的IP能够唯一标识主机,而TCP/IP中的端口号又能唯一标识相应主机的唯一进程,因此可以通过IP+协议+端口号来唯一标识网络中的一个进程。Socket就是通过这种方式来进行网络中进程的通信的。

Socket是对TCP/IP协议的抽象,是操作系统对外开放的接口。

Socket通信流程图如下:

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

  • TCP方式
package com.yunche.socket;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @ClassName: TCPServer
 * @Description: TCP服务端
 * @author: yunche
 * @date: 2019/03/11
 */
public class TCPServer {

    public static void main(String[] args) throws IOException {
        //创建socket,并将socket绑定到65000端口
        ServerSocket serverSocket = new ServerSocket(65000);
        ExecutorService executor = Executors.newCachedThreadPool();
        while (true) {
            //监听65000端口,直到客户端返回连接信息才返回
            Socket socket = serverSocket.accept();
            //获取客户端的的请求信息,执行相关的业务逻辑
            executor.execute(()->{
                try {
                    service(socket);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
        }
    }

    private static void service(Socket socket) throws IOException {
        //获取socket的输入流
        InputStream is = socket.getInputStream();
        //获取socket的输出流
        OutputStream os = socket.getOutputStream();
        byte[] bytes = new byte[1024];
        //读取的字节数
        int ch = is.read(bytes);
        String content = new String(bytes, 0, ch);
        //输出收到的字符串
        System.out.println(content);
        //往输出流中写服务端收到的客户端的字符串的长度
        os.write(String.valueOf(content.length()).getBytes());

        //关闭流以及socket
        is.close();
        os.close();
        socket.close();
    }
}
package com.yunche.socket;

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

/**
 * @ClassName: TCPClient
 * @Description: TCP客户端
 * @author: yunche
 * @date: 2019/03/11
 */
public class TCPClient {
    public static void main(String[] args) throws IOException {
        //创建socket,并指定连接的是本机的端口为65000的服务器socket
        Socket socket = new Socket("localhost", 65000);
        //获取输出流
        OutputStream os = socket.getOutputStream();
        //获取输入流
        InputStream is = socket.getInputStream();
        //将要传递给server的字符串参数转换成byte数组,并将数组写入到输出流中
        os.write("hello world".getBytes());
        //用来读取输入的内容,即从服务器返回的字符串长度
        byte[] bytes = new byte[1024];
        int ch = is.read(bytes);
        String len = new String(bytes, 0, ch);
        System.out.println(len);

        //关闭相应的流以及socket
        is.close();
        os.close();
        socket.close();
    }
}
  • UDP方式
package com.yunche.socket;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @ClassName: UDPServer
 * @Description:
 * @author: yunche
 * @date: 2019/03/11
 */
public class UDPServer {
    public static void main(String[] args) throws IOException {
        //创建一个底层是UDP的socket
        DatagramSocket socket = new DatagramSocket(65001);
        //存储从客户端接受到的内容
        byte[] bytes = new byte[1024];
        DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
        //接受客户端发送过来的内容,并将内容封装进DatagramPacket对象中
        socket.receive(packet);

        //从DatagramPacket对象中获取到真正存存储的数据
        byte[] data = packet.getData();
        //将数据从二进制转换成字符串
        String content = new String(data, 0, packet.getLength());
        System.out.println(content);

        //将要发送给客户端的数据转为二进制
        byte[] len = String.valueOf(content.length()).getBytes();
        //服务端给客户端发送数据报
        //从DatagramPacket对象中获取到数据的来源地址与端口号
        DatagramPacket packetToClient = new DatagramPacket(len, len.length, packet.getAddress(),packet.getPort());
        socket.send(packetToClient);
        }
}
package com.yunche.socket;

import java.io.IOException;
import java.net.*;

/**
 * @ClassName: UDPClient
 * @Description: UDP客户端
 * @author: yunche
 * @date: 2019/03/11
 */
public class UDPClient {
    public static void main(String[] args) throws IOException {
        //客户端发送数据报给服务端
        DatagramSocket socket = new DatagramSocket();
        byte[] bytes = "Hello world".getBytes();
        //将IP地址封装成InetAddress对象
        InetAddress address = InetAddress.getByName("localhost");
        DatagramPacket packet = new DatagramPacket(bytes, bytes.length, address,65001);
        //发送数据给服务端
        socket.send(packet);

        //客户端接受从服务端返回的数据报
        byte[] data = new byte[1024];
        DatagramPacket receivePacket = new DatagramPacket(data, data.length);
        socket.receive(receivePacket);
        String len = new String(data, 0, receivePacket.getLength());
        System.out.println(len);
    }
}

参考资料

慕课网 剑指Java面试-Offer直通车

OSI七层模型详解

原文地址:https://www.cnblogs.com/yunche/p/10510172.html