当前位置:   article > 正文

Java面经完结版

java面经

1.Java基础

1. 异常机制

image-20220305150136807.png
Error常见的有StackOverFlowError,OutOfMemoryError等等。
Exception可分为运行时异常和非运行时异常。对于运行时异常,可以利用try catch的方式进行处理,也
可以不处理。对于非运行时异常,必须处理,不处理的话程序无法通过编译。

ClassNotFoundException是在类加载第一个阶段,加载这个动作的时候,类加载器不能找到class文件,则会报ClassNotFoundException。比如在A中有段代码使用了Class.forName(B)去加载B,此时B的class文件不见了,则会报错ClassNotFoundException。它报这个错是说B的加载阶段出错了。(A类去加载B类,A类会报ClassNotFound B类)

NoClassDefError这个类是继承LinkageError(连接错误),所以也可以大概的猜出,NoClassDefError是在类加载的连接这个阶段报出来的。当A类中引用了B类,然后A类进行类的加载,加载成功后,然后进行接下来的连接阶段的时候,如果涉及到引用到B类却找不到B类的时候,就会报NoClassDefError。它报这个错是说A的连接阶段出错了。(A类去加载B类,A类会报B类NoClassDef)

Linux实现CAS的指令cmpxchgl

初始化顺序
  • ①默认初始化
  • ②显式初始化/⑤在代码块中赋值
  • ③构造器中初始化
  • ④有了对象以后,可以通过"对象.属性"或"对象.方法"的方式,进行赋值

2. CPU飙高

在 thread dump 中,要留意下面几种状态

  • 死锁,Deadlock(重点关注)
  • 等待资源,Waiting on condition(重点关注)
  • 等待获取监视器,Waiting on monitor entry(重点关注)
  • 阻塞,Blocked(重点关注)
  • 执行中,Runnable
  • 暂停,Suspended
  • 对象等待中,Object.wait() 或 TIMED_WAITING
  • 停止,Parked

3. 红黑树二叉树区别

二叉搜索树:若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉搜索树。搜索复杂度为o(logn)最差为o(n)

平衡二叉搜索树AVL树:本质上是带了平衡功能的二叉查找树,每个结点的左右子树的高度之差的绝对值(平衡因子)最多为1。

红黑树:需要满足以下性质:红黑树能够以O(log2 n)的时间复杂度进行搜索、插入、删除操作
1)每个结点要么是红的,要么是黑的。
2)根结点是黑的。
3)每个叶结点,即空结点(NIL)是黑的。
4)如果一个结点是红的,那么它的俩个儿子都是黑的。也就是说从每个叶子到根的所有路径上不能有两个连续的红色节点
5)对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点。
image.png

4. a+=b和a=a+b

short a = 4;
a += 5;
//a = a + 3会报错
a = (short) (a + 3);
  • 1
  • 2
  • 3
  • 4

+=是一个运算符,编译器会自动转换类型

a = a+b

5. 常见集合

  1. Map接口和Collection接口是所有集合框架的父接口:

image-20220308160440173.png

image-20220308160510098.png

  1. Collection集合主要有List和Set两大接口
  • List:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个null元素,元素都有索引。常用的实现类有 ArrayList、LinkedList 和 Vector。
  • Set:一个无序(存入和取出顺序有可能不一致)容器,不可以存储重复元素,只允许存入一个null元素,必须保证元素唯一性。Set 接口常用实现类是 HashSet、LinkedHashSet 以及 TreeSet。

Map是一个键值对集合,存储键、值和之间的映射。 Key无序,唯一;value 不要求有序,允许重复。Map没有继承于Collection接口,从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。

Map 的常用实现类:HashMap、TreeMap、HashTable、LinkedHashMap、ConcurrentHashMap

  1. ArrayList的优缺点:基于数组实现,适合读多写少。LinkedList基于链表,适合写多读少
  • 优点:
    ArrayList 底层以数组实现,是一种随机访问模式。ArrayList 实现了 RandomAccess 接口,因此查找的时候非常快。
    ArrayList 在顺序添加一个元素的时候非常方便。
  • 缺点:
    删除元素的时候,需要做一次元素复制操作。如果要复制的元素很多,那么就会比较耗费性能。
    插入元素的时候,也需要做一次元素复制操作,缺点同上。
    ArrayList 比较适合顺序添加、随机访问的场

6. IO

阻塞IO

非阻塞NIO

7. 排序算法

堆排序

堆的数据结构

image-20220310214109331.png

8. HashMap

  1. 长度为啥是2的n次幂?
    求hash时,先求出hashCode,再向右移16位,与原来hashcode做&运算(保留高低位的特征,增加散列性)
    2的n次幂(1000),当求索引时,是hash&(length - 1),减一变成0111,能够保持hash值的散列性,减少碰撞
  2. 扩容时为啥是2倍
    为了能够保持长度为2的n次幂
    为了扩容时,快速求出元素扩容后所在的索引,只需要比较hash的前一位是1还是0,如果是0就不变,如果是1就原索引+oldCap。
  3. 链表长度为啥在8时改为红黑树?
    如果 hashCode 分布良好,也就是 hash 计算的结果离散好的话,那么红黑树这种形式是很少会被用到的,因为各个值都均匀分布,很少出现链表很长的情况。在理想情况下,链表长度符合泊松分布,各个长度的命中概率依次递减,当长度为 8 的时候,概率仅为 0.00000006。这是一个小于千万分之一的概率,通常我们的 Map 里面是不会存储这么多的数据的,所以通常情况下,并不会发生从链表向红黑树的转换。
  4. 扩容(加载)因子为何默认是 0.75f
    在空间占用与查询时间之间取得较好的权衡;大于这个值,空间节省了,但链表就会比较长影响性能;小于这个值,冲突减少了,但扩容就会更频繁,空间占用也更多
  5. 为什么红黑树退化为链表的阈值是6
    主要是一个过渡,避免链表和红黑树之间频繁的转换。如果阈值是7的话,删除一个元素红黑树就必须退化为链表,增加一个元素就必须树化,来回不断的转换结构无疑会降低性能,所以阈值才不设置的那么临界

9. 抽象类与接口

相同点

(1)都不能被实例化 (2)接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能实例化。

不同点

(1)接口只有定义,不能有方法的实现,java 1.8中可以定义default方法体,而抽象类可以有定义与实现,方法可在抽象类中实现。

(2)实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。所以,使用接口可以间接地实现多重继承。

(3)接口强调特定功能的实现,而抽象类强调所属关系。

(4)接口成员变量默认为 public static final,必须赋初值,不能被修改;其所有的成员方法都是public、abstract的,1.8 可以有static方法,可以有default方法,子类可以不实现

抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;可以有非抽象方法,抽象方法abstract修饰,不能被private、static、synchronized和native等修饰,必须以分号结尾,不带花括号。抽象类中可以有非抽象方法,有抽象方法的一定是抽象类

image-20220310093416621.png

10. MySQL varchar和text

TEXT类型一般分为 TINYTEXT(255长度)、TEXT(65535)、 MEDIUMTEXT(int最大值16M),和LONGTEXT(long最大值4G)这四种,对于text列,插入时MySQL不会对它进行填充,并且select时不会删除任何末尾的字节。

text的最大限制也是64k个字节,但是本质是溢出存储,innodb默认只会存放前768字节在数据页中,而剩余的数据则会存储在溢出段中。text类型的数据,将被存储在元数据表之外地方,但是varchar/char将和其他列一起存储在表数据文件中,值得注意的是,varchar列在溢出的时候会自动转换为text类型。text数据类型实际上将会大幅度增加数据库表文件尺寸。

除此之外,二者还有以下的区别

1、当text作为索引的时候,必须 制定索引的长度,而当varchar充当索引的时候,可以不用指明。

2、text列不允许拥有默认值。

3、当text列的内容很多的时候,text列的内容会保留一个指针在记录中,这个指针指向了磁盘中的一块区域,当对这个表进行select *的时候,会从磁盘中读取text的值,影响查询的性能,而varchar不会存在这个问题。

11. 单例模式

  1. 双重检验锁+volatile 懒汉式
public final class Singleton {
   
    private Singleton() {
   
    }
    // 问题1:解释为什么要加 volatile?为了保证赋值和构造器不要发送重排序,否则第二个线程可能拿到不完整的对象。
    private static volatile Singleton INSTANCE = null;
    // 问题2:对比实现3, 说出这样做的意义 
    public static Singleton getInstance() {
   
        if (INSTANCE != null) {
   
            return INSTANCE;
        }
        synchronized (Singleton.class) {
   
            // 问题3:为什么还要在这里加为空判断, 之前不是判断过了吗?为了防止第二个线程等第一个线程释放锁后,再次创建新对象
            if (INSTANCE != null) {
    // t2 
                return INSTANCE;
            }
            INSTANCE = new Singleton();
            return INSTANCE;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

image-20220310203210567.png

image-20220310203220589.png

  1. 枚举 饿汉式
// 问题1:枚举单例是如何限制实例个数的 ==static final 变量
// 问题2:枚举单例在创建时是否有并发问题 ==没有并发,jvm保证
// 问题3:枚举单例能否被反射破坏单例 ==不能
// 问题4:枚举单例能否被反序列化破坏单例 不能,枚举类实现了序列化
// 问题5:枚举单例属于懒汉式还是饿汉式 ==饿汉式
// 问题6:枚举单例如果希望加入一些单例创建时的初始化逻辑该如何做== 加构造器
enum Singleton {
   
    INSTANCE;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  1. 静态内部类 懒汉式
public final class Singleton {
   
    private Singleton() {
    }
    // 问题1:属于懒汉式还是饿汉式 ==懒汉式,不调用方法不加载静态类
    private static class LazyHolder {
   
        static final Singleton INSTANCE = new Singleton();
    }
    // 问题2:在创建时是否有并发问题 ==没有,静态成员变量,在类加载的时候完成,jvm保证
    public static Singleton getInstance() {
   
        return LazyHolder.INSTANCE;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

12. 微服务优缺点

单体架构的优缺点如下:

**优点:**架构简单、部署成本低

**缺点:**耦合度高(维护困难、升级困难)
  • 1
  • 2
  • 3

分布式架构的优缺点:

**优点:**降低服务耦合、有利于服务升级和拓展

**缺点:**服务调用关系错综复杂
  • 1
  • 2
  • 3

13. 踢人下线/黑名单

当我们需要封禁一个账号时,只需要将其账号的status值修改为0即可,对方再次登录系统时,我们便可以检测到status值不为1禁止登录。由于我们只在登录时检测status值,这也就代表:如果对方不主动注销账号,他的会话还是会一直存在且有效。

那怎么才可以做到在封禁账号后立即生效?

你可能会想到使用拦截器,拦截用户的所有请求检测账号状态:status=0时禁止访问,status=1时再对请求放行。

可以用户数据库+status状态、登录时判断、拦截器请求时判断、redis维护黑名单。

使用Sa-Token框架

14. 设计原则

  1. 开闭原则
    对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。
    想要达到这样的效果,我们需要使用接口和抽象类。
    因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节可以从抽象派生来的实现类来进行扩展,当软件需要发生变化时,只需要根据需求重新派生一个实现类来扩展就可以了。
  2. 里氏代换原则
    任何基类可以出现的地方,子类一定可以出现。通俗理解:子类可以扩展父类的功能,但不能改变父类原有的功能。换句话说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
    如果通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的概率会非常大。
  3. 依赖倒转原则
    高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
  4. 接口隔离原则
    客户端不应该被迫依赖于它不使用的方法;一个类对另一个类的依赖应该建立在最小的接口上。
  5. 迪米特法则
    只和你的直接朋友交谈,不跟“陌生人”说话(Talk only to your immediate friends and not to strangers)。
    其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
    迪米特法则中的“朋友”是指:当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。
  6. 单一职责

该原则提出对象不应该承担太多职责,如果一个对象承担了太多的职责,至少存在以下两个缺点:

  1. 一个职责的变化可能会削弱或者抑制这个类实现其他职责的能力;
  2. 当客户端需要该对象的某一个职责时,不得不将其他不需要的职责全都包含进来,从而造成冗余代码或代码的浪费。

单一职责原则的优点
单一职责原则的核心就是控制类的粒度大小、将对象解耦、提高其内聚性。如果遵循单一职责原则将有以下优点。

  • 降低类的复杂度。一个类只负责一项职责,其逻辑肯定要比负责多项职责简单得多。
  • 提高类的可读性。复杂性降低,自然其可读性会提高。
  • 提高系统的可维护性。可读性提高,那自然更容易维护了。
  • 变更引起的风险降低。变更是必然的,如果单一职责原则遵守得好,当修改一个功能时,可以显著降低对其他功能的影响

15. Unicode UTF-8

Unicode就是一种编码:它包含了世界上所有的符号,并且每一个符号都是独一无二的

UTF-8就是在互联网上使用最广的一种unicode的实现方式。其他实现方式还包括UTF-16和UTF-32,不过在互联网上基本不用。重复一遍,这里的关系是,UTF-8是Unicode的实现方式之一。

UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。

UTF-8的编码规则很简单,只有两条:

1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母UTF-8编码和ASCII码是相同的。

2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码

GBK/GB2312/GB18030

GBK和GB2312都是针对简体字的编码,只是GB2312只支持六千多个汉字的编码,而GBK支持1万多个汉字编码。而GB18030是用于繁体字的编码。汉字存储时都使用两个字节来储存

16.synchronized

修饰普通方法:对象锁

修饰静态方法:类锁

修饰代码块:类锁synchronized(A.class)、对象锁synchronized(this)

17. 线程池的优缺点

为啥先进阻塞队列再创建最大线程

线程池创建线程需要获取mainlock这个全局锁,会影响并发效率,所以使用阻塞队列把第一步创建核心线程与第三步创建最大线程隔离开来,起一个缓冲的作用。引入阻塞队列,是为了在执行execute()方法时,尽可能的避免获取全局锁。

优点:
  1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  2. 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  3. 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控
缺点:
  1. **导致OOM:**都是无界的可能会导致OOM
  2. **死锁:**线程池中的线程都在等待在阻塞队列中另一任务的执行结果,但这一任务却因为没有额外的线程而不能运行,可能会发生死锁。当线程池被用来实现涉及许多交互对象的模拟,被模拟的对象可以相互发送查询,这些查询接下来作为排队的任务执行,查询对象又同步等待着响应时,会发生这种情况。
  3. 线程泄露:当从池中拿一个一个线程去执行一项任务,而在任务完成后该线程却没有返回池时,会发生线程泄露况。发生线程泄漏的一种情形出现在任务抛出一个 RuntimeException 或 Error 时。如果池类没有捕捉到它们,那么线程只会退出而线程池的大小将会永久减少一个。当这种情况发生的次数足够多时,线程池最终就为空,而且系统将停止,因为没有可用的线程来处理任务。
    有些任务可能会永远等待某些资源或来自用户的输入,而这些资源又不能保证变得可用,用户可能也已经回家了,诸如此类的任务会永久停止,而这些停止的任务也会引起和线程泄漏同样的问题。如果某个线程被这样一个任务永久地消耗着,那么它实际上就被从池除去了。对于这样的任务,应该要么只给予它们自己的线程,要么只让它们等待有限的时间
  4. 资源有限:程池的一个优点在于:相对于其它替代调度机制而言,它们通常执行得很好。但只有恰当地调整了线程池大小时才是这样的。线程会消耗内存其它系统资源等大量资源。除了 Thread 对象所需的内存之外,每个线程都需要两个可能很大的执行调用堆栈。除此以外,JVM可能会为每个 Java 线程创建一个本机线程,这些本机线程将消耗额外的系统资源。最后,虽然线程之间切换的调度开销很小,但如果有很多线程,环境切换也可能严重地影响程序的性能。
    如果线程池太大,那么被那些线程消耗的资源可能严重地影响系统性能。在线程之间进行切换将会浪费时间,而且使用超出比您实际需要的线程可能会引起资源匮乏问题,因为池线程正在消耗一些资源,而这些资源可能会被其它任务更有效地利用。除了线程自身所使用的资源以外,服务请求时所做的工作可能需要其它资源,例如 JDBC 连接、套接字或文件。这些也都是有限资源,有太多的并发请求也可能引起失效,例如不能分配 JDBC 连接。

18. 线程池参数设计规则

  • CPU 密集型任务(N+1): 这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N(CPU 核心数)+1,比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。
  • I/O 密集型任务(2N): 这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理 I/O 的时间段内不会占用 CPU 来处理,CPU 不总是处于繁忙状态。例如,当你执行业务计算时,这时候会使用 CPU 资源,但当你执行 I/O 操作时、远程 RPC 调用时,包括进行数据库操作时,这时候 CPU 就闲下来了,你可以利用多线程提高它的利用率。
    经验公式如下 线程数 = 核数 _ 期望 CPU 利用率 _ 总时间(CPU计算时间+等待时间) / CPU 计算时间
    例如 4 核 CPU 计算时间是 50% ,其它等待时间是 50%,期望 cpu 被 100% 利用,套用公式 4 _ 100% _ 100% / 50% = 8
    例如 4 核 CPU 计算时间是 10% ,其它等待时间是 90%,期望 cpu 被 100% 利用,套用公式 4 _ 100% _ 100% / 10% = 40

核心线程数 corePoolSize

image-20220329142804337.png

任务队列长度 workingQueue

image-20220329142907298.png

最大线程数 maximumPoolSize

image-20220329143031802.png

最大空闲时间

image-20220329143059372.png

线程池状态:
状态 描述
RUNNING 能接受新提交的任务,并且也能处理阻塞队列中的任务
SHUTDOWN 关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。在线程池处于 RUNNING 状态时,调用 shutdown()方法会使线程池进入到该状态。(finalize() 方法在执行过程中也会调用shutdown()方法进入该状态)
STOP 不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于 RUNNING 或 SHUTDOWN 状态时,调用 shutdownNow() 方法会使线程池进入到该状态
TIDYING 如果所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态
TERMINATED 在terminated() 方法执行完后进入该状态,默认terminated()方法中什么也没有做

线程池原理

2. 计网

1. 三次握手

过程
  1. TCP服务器进程先创建传输控制块TCB,时刻准备接受客户进程的连接请求,此时服务器就进入了LISTEN(监听)状态;
  2. TCP客户进程也是先创建传输控制块TCB,然后向服务器发出连接请求报文,这是报文首部中的同部位SYN=1,同时选择一个初始序列号 seq=x ,此时,TCP客户端进程进入了 SYN-SENT(同步已发送状态)状态。TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。
  3. TCP服务器收到请求报文后,如果同意连接,则发出确认报文。确认报文中应该 ACK=1,SYN=1,确认号是ack=x+1,同时也要为自己初始化一个序列号 seq=y,此时,TCP服务器进程进入了SYN-RCVD(同步收到)状态。这个报文也不能携带数据,但是同样要消耗一个序号。
  4. TCP客户进程收到确认后,还要向服务器给出确认。确认报文的ACK=1,ack=y+1,自己的序列号seq=x+1,此时,TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态。TCP规定,ACK报文段可以携带数据,但是如果不携带数据则不消耗序号。
  5. 当服务器收到客户端的确认后也进入ESTABLISHED状态,此后双方就可以开始通信了 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BtYK92Q8-1683542094229)(计网操作系统等面经.assets/image-20220305170145297.png#id=WA8GT&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]

第一次握手丢失:客户端重传SYN

第二次握手丢失:客户端重传SYN、服务器都重传SYN-ACK

第三次握手丢失:服务端重传SYN-ACK,客户端的ACK不会重传

序列号可以用来解决网络包乱序的问题,确认号可以⽤来解决网络包丢失的问题

image-20220305170145297.png
image-20220321092956735.png

为啥三次握手

一句话,主要防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。

如果使用的是两次握手建立连接,假设有这样一种场景,客户端发送了第一个请求连接并且没有丢失,只是因为在网络结点中滞留的时间太长了,由于TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送这条报文,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。此时此前滞留的那一次请求连接,网络通畅了到达了服务器,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,这将导致不必要的错误和资源的浪费。

如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。

客户端连续发送多次 SYN 建立连接的报文,在网络拥堵情况下:

  • 一个「旧 SYN 报文」比「最新的 SYN 」 报文早到达了服务端;
  • 那么此时服务端就会回一个 SYN + ACK 报文给客户端;
  • 客户端收到后可以根据自身的上下文,判断这是一个历史连接(序列号过期或超时),那么客户端就会发送 RST 报文给服务端,表示中止这一次连接

2. 四次挥手

过程
  1. 客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
  2. 服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
  3. 客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
  4. 服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
  5. 客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2*MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
  6. 服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。

image-20220305170852425.png

客户端最后等待2MSL

MSL(Maximum Segment Lifetime),TCP允许不同的实现可以设置不同的MSL值。

第一,保证客户端发送的最后一个ACK报文能够到达服务器,因为这个ACK报文可能丢失,站在服务器的角度看来,我已经发送了FIN+ACK报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个2MSL时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL计时器

第二,防止类似与“三次握手”中提到了的“已经失效的连接请求报文段”出现在本连接中。客户端发送完最后一个确认报文后,在这个2MSL时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文

  1. 保证最后的 ACK 报文,接收方能收到,一定能收到(如果收不到,对方会重发 FIN 报文)
  2. 确保在创建新连接时,先前网络中残余的数据都在网络中消失了。
为啥四次挥手

建立连接的时候, 服务器在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。
而关闭连接时,服务器收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送,从而导致多了一次。

已建立连接,客户端故障

TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接

大量close_wait

大量TCP连接处在close_wait状态,形成原因和解决办法

客户端发送FIN客户端收到后,客户端自动ACK后进入close_wait状态,socket.close因为阻塞关闭不及时。

Server端在某些异常情况时,没有关闭Socket。关闭socket不及时:例如I/O线程被意外阻塞,或者I/O线程执行的用户自定义Task比例过高,导致I/O操作处理不及时,链路不能被及时释放。

3. DNS域名解析过程

浏览器–>本地域名服务器缓存–>根域名服务器(全球有13个)–>com顶级域名服务器–>baidu.com主域名(权威)服务器–>找到ip地址 缓存到本地域名服务器

DNS存在着多级缓存,从离浏览器的距离排序的话,有以下几种: 浏览器缓存,系统缓存,路由器缓存,IPS服务器缓存,根域名服务器缓存,顶级域名服务器缓存,主域名服务器缓存。

DNS使用UDP还是TCP区域传送时使用TCP域名解析时使用UDP协议

DNS在进行区域传输的时候使用TCP协议,其它时候则使用UDP协议;
DNS的规范规定了2种类型的DNS服务器,**一个叫主DNS服务器,一个叫辅助DNS服务器。**在一个区中主DNS服务器从自己本机的数据文件中读取该区的DNS数据信息,而辅助DNS服务器则从区的主DNS服务器中读取该区的DNS数据信息。当一个辅助DNS服务器启动时,它需要与主DNS服务器通信,并加载数据信息,这就叫做区传送(zone transfer)。

为什么既使用TCP又使用UDP?
首先了解一下TCP与UDP传送字节的长度限制:
UDP报文的最大长度为512字节,而TCP则允许报文长度超过512字节。当DNS查询超过512字节时,协议的TC标志出现删除标志,这时则使用TCP发送。通常传统的UDP报文一般不会大于512字节。

区域传送时使用TCP,主要有一下两点考虑:
1.辅域名服务器会定时(一般时3小时)向主域名服务器进行查询以便了解数据是否有变动。如有变动,则会执行一次区域传送,进行数据同步。区域传送将使用TCP而不是UDP,因为数据同步传送的数据量比一个请求和应答的数据量要多得多。
2.TCP是一种可靠的连接,保证了数据的准确性。

域名解析时使用UDP协议:
客户端向DNS服务器查询域名,一般返回的内容都不超过512字节,用UDP传输即可。不用经过TCP三次握手,这样DNS服务器负载更低,响应更快。虽然从理论上说,客户端也可以指定向DNS服务器查询的时候使用TCP,但事实上,很多DNS服务器进行配置的时候,仅支持UDP查询包。

4. http请求访问80端口

http请求一定要访问80端口吗

不一定。80端口只是客户端(浏览器)发出的http请求默认去访问的服务器端口,只要请求的端口和服务器程序运行的端口保持一致就可以。我们日常开发使用的tomcat就运行在8080端口,在浏览器输入http://localhost:8080也可以访问tomcat的。只是一般网站服务器程序都运行在80端口上,我们只需要输入网址即可,不用再输入类似ww.google.com:80这样带有端口号的地址,比较方便。

5. Cookie和Session区别

  • Cookie:用来保存用户信息;存储在浏览器端;不安全,加密存到服务端,单个cookie保存的数**<=4KB**,一个站点最多保存20个Cookie。cookie中只能保管ASCII字符串,并需要通过编码方式存储为Unicode字符或者二进制数据
  • Session:通过服务端记录用户的状态;存储在服务器,占用服务器性能。

简单的说,当你登陆一个网站的时候,如果web服务器端使用的是session,那么所有的数据都保存在服务器上,客户端每次请求服务器的时候会发送当前会话sessionid,服务器根据当前sessionid判断相应的用户数据标志,以确定用户是否登陆或具有某种权限。由于数据是存储在服务器上面,所以你不能伪造

  • Token:令牌,是服务器生成的一串字符串,作为客户端进行请求的一个标识。是无状态的,服务器不需要保存会话信息,可以放在Authorization header中或者cookie中,对于token而言,服务器不需要去查看你是谁,不需要保存你的会话。当用户logout的时候cookie和服务器的session都会注销;但是当logout时候token只是注销浏览器信息,不查库。
    最简单的token组成:uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,由token的前几位+盐以哈希算法压缩成一定长的十六进制字符串,可以防止恶意第三方拼接token请求服务器)

假如客户端禁用了cookie,或者不支持cookie,则会话跟踪会失效。关于WAP上的应用,常规的cookie就派不上用场了。运用session需要使用URL地址重写的方式。一切用到session程序的URL都要进行URL地址重写,否则session会话跟踪还会失效。

cookie什么情况下会丢失

1、 Cookie 的Domain设置不正确 ;

2、 Cookie 超时 ;

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/875721
推荐阅读
相关标签
  

闽ICP备14008679号