<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>408 on SalmoneX Blog</title>
        <link>https://Salmooo.github.io/tags/408/</link>
        <description>Recent content in 408 on SalmoneX Blog</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en</language>
        <copyright>salmone</copyright>
        <lastBuildDate>Fri, 10 Jan 2020 21:56:12 +0800</lastBuildDate><atom:link href="https://Salmooo.github.io/tags/408/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>计算机系统常见问题1</title>
        <link>https://Salmooo.github.io/p/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%981/</link>
        <pubDate>Fri, 10 Jan 2020 21:56:12 +0800</pubDate>
        
        <guid>https://Salmooo.github.io/p/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%981/</guid>
        <description>&lt;h2 id=&#34;1-死锁&#34;&gt;1. 死锁
&lt;/h2&gt;&lt;h2 id=&#34;什么是死锁&#34;&gt;&lt;strong&gt;什么是死锁？&lt;/strong&gt;
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;所谓死锁，是指多个进程在运行过程中因争夺资源而造成的一种僵局&lt;/strong&gt;，当进程处于这种僵持状态时，若无外力作用，它们都将无法再向前推进。&lt;/p&gt;
&lt;p&gt;因此我们举个例子来描述，如果此时有一个线程A，按照先锁a再获得锁b的的顺序获得锁，而在此同时又有另外一个线程B，按照先锁b再锁a的顺序获得锁。如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://img-blog.csdn.net/20180922173936964?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2hkMTIzNzA=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;产生死锁的原因&#34;&gt;&lt;strong&gt;产生死锁的原因？&lt;/strong&gt;
&lt;/h2&gt;&lt;p&gt;可归结为如下两点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;竞争资源&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;系统中的资源可以分为两类：
&lt;ol&gt;
&lt;li&gt;可剥夺资源，是指某进程在获得这类资源后，该资源可以再被其他进程或系统剥夺，&lt;strong&gt;CPU和主存均属于可剥夺性资源&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;另一类资源是不可剥夺资源，当系统把这类资源分配给某进程后，再不能强行收回，&lt;strong&gt;只能在进程用完后自行释放，如磁带机、打印机等&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;产生死锁中的竞争资源之一指的是&lt;strong&gt;竞争不可剥夺资源&lt;/strong&gt;（例如：系统中只有一台打印机，可供进程P1使用，假定P1已占用了打印机，若P2继续要求打印机打印将阻塞）&lt;/li&gt;
&lt;li&gt;产生死锁中的竞争资源另外一种资源指的是&lt;strong&gt;竞争临时资源&lt;/strong&gt;（临时资源包括硬件中断、信号、消息、缓冲区内的消息等），通常消息通信顺序进行不当，则会产生死锁&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;进程间推进顺序非法&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;若P1保持了资源R1,P2保持了资源R2，系统处于&lt;strong&gt;不安全状态&lt;/strong&gt;，因为这两个进程再向前推进，便可能发生死锁&lt;/li&gt;
&lt;li&gt;例如，当P1运行到P1：Request（R2）时，将因R2已被P2占用而阻塞；当P2运行到P2：Request（R1）时，也将因R1已被P1占用而阻塞，于是发生进程死锁&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;死锁产生的4个必要条件&#34;&gt;&lt;strong&gt;死锁产生的4个必要条件&lt;/strong&gt;
&lt;/h2&gt;&lt;p&gt;产生死锁的必要条件：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;互斥条件&lt;/strong&gt;：进程要求对所分配的资源进行排它性控制，即在&lt;strong&gt;一段时间内某资源仅为一进程所占用&lt;/strong&gt;。&lt;strong&gt;互斥条件是指多个线程不能同时使⽤同⼀个资源&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;请求和保持条件&lt;/strong&gt;：当进程因&lt;strong&gt;请求资源而阻塞时，对已获得的资源保持不放&lt;/strong&gt;。当线程 A 已经持有了资源 1，⼜想申请资源 2，⽽资源 2 已经被线程 C 持有了，所以线程 A 就会处于等待状态，但是&lt;strong&gt;线程&lt;/strong&gt; &lt;strong&gt;A&lt;/strong&gt; &lt;strong&gt;在等待资源&lt;/strong&gt; &lt;strong&gt;2&lt;/strong&gt; &lt;strong&gt;的同时并不会释放⾃⼰已经持有的资源&lt;/strong&gt; &lt;strong&gt;1&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不可剥夺条件&lt;/strong&gt;：进程&lt;strong&gt;已获得的资源在未使用完之前，不能剥夺&lt;/strong&gt;，只能在使用完时由自己释放。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;循环等待条件&lt;/strong&gt;：在发生死锁时，必然存在一个进程–资源的环形链。在死锁发⽣的时候，&lt;strong&gt;两个线程获取资源的顺序构成了环形链&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;预防死锁&#34;&gt;&lt;strong&gt;预防死锁：&lt;/strong&gt;
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;资源一次性分配&lt;/strong&gt;：&lt;strong&gt;一次性分配所有资源&lt;/strong&gt;，这样就不会再有请求了：（&lt;strong&gt;破坏请求条件）&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;只要有一个资源得不到分配&lt;/strong&gt;，&lt;strong&gt;也不给这个进程分配其他的资源&lt;/strong&gt;：&lt;strong&gt;（破坏保持条件）&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可剥夺资源&lt;/strong&gt;：即当某进程获得了&lt;strong&gt;部分资源&lt;/strong&gt;，但得不到其它资源，则释放已占有的资源（&lt;strong&gt;破坏不可剥夺条件&lt;/strong&gt;）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;资源有序分配法&lt;/strong&gt;：系统给每类资源赋予一个编号，&lt;strong&gt;每一个进程按编号递增的顺序请求资源&lt;/strong&gt;，释放则相反（&lt;strong&gt;破坏环路等待条件&lt;/strong&gt;）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;解决死锁的基本方法&#34;&gt;&lt;strong&gt;解决死锁的基本方法&lt;/strong&gt;
&lt;/h2&gt;&lt;p&gt;1、&lt;strong&gt;以确定的顺序获得锁&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果必须获取多个锁，那么在设计的时候需要&lt;strong&gt;充分考虑不同线程之前获得锁的顺序。按照上面的例子，两个线程获得锁的时序图如&lt;/strong&gt;下：&lt;/p&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://img-blog.csdn.net/20180922174807514?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2hkMTIzNzA=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;如果此时把获得锁的时序改成：&lt;/p&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://img-blog.csdn.net/20180922174829303?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2hkMTIzNzA=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;那么死锁就永远不会发生。 针对两个特定的锁，&lt;strong&gt;开发者可以尝试按照锁对象的hashCode值大小的顺序，分别获得两个锁&lt;/strong&gt;，这样锁总是会以特定的顺序获得锁，那么死锁也不会发生。&lt;/p&gt;
&lt;p&gt;问题变得更加复杂一些，如果此时有多个线程，都在竞争不同的锁，简单按照锁对象的hashCode进行排序（&lt;strong&gt;单纯按照hashCode顺序排序会出现“环路等待”&lt;/strong&gt;），可能就无法满足要求了，这个时候开发者可以使用银行家算法，所有的锁都按照特定的顺序获取，同样可以防止死锁的发生，该算法在这里就不再赘述了，有兴趣的可以自行了解一下。&lt;/p&gt;
&lt;p&gt;2、&lt;strong&gt;超时放弃&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;当使用&lt;strong&gt;synchronized关键词提供的内置锁时，只要线程没有获得锁，那么就会永远等待下去&lt;/strong&gt;然&lt;/p&gt;
&lt;p&gt;而Lock接口提供了boolean tryLock(long time, TimeUnit unit) throws InterruptedException方法，&lt;strong&gt;该方法可以按照固定时长等待锁，因此线程可以在获取锁超时以后，主动释放之前已经获得的所有的锁&lt;/strong&gt;。通过这种方式，也可以很有效地避免死锁。 还是按照之前的例子，时序图如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;pthread_mutex_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;mutex_A&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;PTHREAD_MUTEX_INITIALIZER&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;pthread_mutex_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;mutex_B&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;PTHREAD_MUTEX_INITIALIZER&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;main&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(){&lt;/span&gt;    &lt;span class=&#34;n&#34;&gt;pthread_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;tidA&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;tidB&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;    &lt;span class=&#34;c1&#34;&gt;//创建两个线程    pthread_create(&amp;amp;tidA, NULL, threadA_proc, NULL);    pthread_create(&amp;amp;tidB, NULL, threadB_proc, NULL);    pthread_join(tidA, NULL);    pthread_join(tidB, NULL);    printf(&amp;#34;exit\n&amp;#34;);    return 0;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;//线程函数 Avoid *threadA_proc(void *data){    printf(&amp;#34;thread A waiting get ResourceA \n&amp;#34;);    pthread_mutex_lock(&amp;amp;mutex_A);    printf(&amp;#34;thread A got ResourceA \n&amp;#34;);    sleep(1);    printf(&amp;#34;thread A waiting get ResourceB \n&amp;#34;);    pthread_mutex_lock(&amp;amp;mutex_B);    printf(&amp;#34;thread A got ResourceB \n&amp;#34;);    pthread_mutex_unlock(&amp;amp;mutex_B);    pthread_mutex_unlock(&amp;amp;mutex_A);    return (void *)0;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-cpp&#34; data-lang=&#34;cpp&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;//线程函数 Bvoid *threadB_proc(void *data){    printf(&amp;#34;thread B waiting get ResourceB \n&amp;#34;);    pthread_mutex_lock(&amp;amp;mutex_B);    printf(&amp;#34;thread B got ResourceB \n&amp;#34;);    sleep(1);    printf(&amp;#34;thread B waiting  get ResourceA \n&amp;#34;);    pthread_mutex_lock(&amp;amp;mutex_A);    printf(&amp;#34;thread B got ResourceA \n&amp;#34;);    pthread_mutex_unlock(&amp;amp;mutex_A);    pthread_mutex_unlock(&amp;amp;mutex_B);    return (void *)0;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;避免死锁:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;使用资源有序分配法，来破环环路等待条件&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;预防死锁的几种策略，会严重地损害系统性能。因此在避免死锁时，要施加较弱的限制，从而获得 较满意的系统性能。由于在避免死锁的策略中，允许进程动态地申请资源。&lt;/p&gt;
&lt;p&gt;因而，&lt;strong&gt;系统在进行资源分配之前预先计算资源分配的安全性&lt;/strong&gt;。若&lt;strong&gt;此次分配不会导致系统进入不安全的状态，则将资源分配给进程&lt;/strong&gt;；否则，进程等待。其中最具有代表性的避免死锁算法是&lt;strong&gt;银行家算法&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;银行家算法：首先需要&lt;strong&gt;定义状态和安全状态的概念&lt;/strong&gt;。系统的状态是当前给进程分配的资源情况。因此，状态包含两个向量&lt;strong&gt;Resource（系统中每种资源的总量）和Available（未分配给进程的每种资源的总量）及两个矩阵Claim（表示进程对资源的需求）和Allocation（表示当前分配给进程的资源）&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;安全状态是指至少有一个资源分配序列不会导致死锁&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当进程请求一组资源时，假设同意该请求，从而改变了系统的状态，然后确定其结果是否还处于安全状态。如果是，同意这个请求；如果不是，阻塞该进程知道同意该请求后系统状态仍然是安全的。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;检测死锁&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;首先为每个进程和每个资源指定一个&lt;strong&gt;唯一的号码&lt;/strong&gt;；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;然后建立资源分配表和进程等待表&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在 Linux 下，我们可以使用 &lt;strong&gt;&lt;code&gt;pstack&lt;/code&gt; + &lt;code&gt;gdb&lt;/code&gt; 工具来定位死锁问题。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;pstack 命令可以显示每个线程的栈跟踪信息（函数调用过程），它的使用方式也很简单，只需要 &lt;code&gt;pstack &amp;lt;pid&amp;gt;&lt;/code&gt; 就可以了。&lt;/p&gt;
&lt;p&gt;可以看到，Thread 2 和 Thread 3 一直阻塞获取锁（&lt;em&gt;pthread_mutex_lock&lt;/em&gt;）的过程，&lt;strong&gt;而且 pstack 多次输出信息都没有变化，那么可能大概率发生了死锁。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;解除死锁:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;当发现有进程死锁后，便应立即把它从死锁状态中解脱出来，常采用的方法有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;剥夺资源&lt;/strong&gt;：&lt;strong&gt;从其它进程剥夺足够数量的资源给死锁进程&lt;/strong&gt;，以解除死锁状态；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;撤消进程&lt;/strong&gt;：&lt;strong&gt;可以直接撤消死锁进程或撤消代价最小的进程&lt;/strong&gt;，直至有足够的资源可用，死锁状态.消除为止；&lt;strong&gt;所谓代价是指优先级、运行代价、进程的重要性和价值等。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;2-用户态和内核态&#34;&gt;2. 用户态和内核态
&lt;/h2&gt;&lt;p&gt;用户态变为内核态：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;系统调用&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这是用户态进程主动要求切换到内核态的一种方式，&lt;strong&gt;用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作&lt;/strong&gt;，比如前例中fork()实际上就是执行了一个创建新进程的系统调用。而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现，例如Linux的int 80h中断。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;异常&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;当CPU在执行运行在用户态下的程序时，发生了&lt;strong&gt;某些事先不可知的异常&lt;/strong&gt;，这时会触发由&lt;strong&gt;当前运行进程切换到处理此异常的内核相关程序&lt;/strong&gt;中，也就转到了内核态，比如缺页异常。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;外围设备的中断&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;当外围设备完成用户请求的操作后，会向CPU发出相应的中断信号&lt;/strong&gt;，&lt;strong&gt;这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序&lt;/strong&gt;，如果先前执行的指令是用户态下的程序，那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成，系统会切换到硬盘读写的中断处理程序中执行后续操作等。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;3-堆和栈的区别&#34;&gt;3. 堆和栈的区别
&lt;/h2&gt;&lt;p&gt;堆与栈的区别有：&lt;/p&gt;
&lt;p&gt;1、栈由系统自动分配，&lt;strong&gt;而堆是人为申请开辟&lt;/strong&gt;；&lt;/p&gt;
&lt;p&gt;2、栈获得的空间较小，&lt;strong&gt;而堆获得的空间较大&lt;/strong&gt;；&lt;/p&gt;
&lt;p&gt;3、栈由系统自动分配，&lt;strong&gt;速度较快&lt;/strong&gt;，但程序员是无法控制的. &lt;strong&gt;而堆是由new分配的内存, 一般速度比较慢&lt;/strong&gt;,而且容易产生内存碎片,不过用起来最方便；&lt;/p&gt;
&lt;p&gt;4、栈是连续的空间，&lt;strong&gt;而堆是不连续的空间&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;1、栈区（stack）— 由编译器自动分配释放 ，&lt;strong&gt;存放函数的参数值，局部变量的值等&lt;/strong&gt;。其 操作方式类似&lt;strong&gt;于数据结构中的栈。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;2、堆区（heap） — 一般由程序员分配释放， &lt;strong&gt;若程序员不释放，程序结束时可能由OS回收&lt;/strong&gt; 。注意它与数据结构中的堆是两回事，&lt;strong&gt;分配方式倒是类似于链表&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;使用栈就象我们去饭馆里吃饭，只管点菜（发出申请）、付钱、和吃（使用），吃饱了就走，不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作，他的好处是快捷，但是自由度小。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;使用堆就象是自己动手做喜欢吃的菜肴，比较麻烦，但是比较符合自己的口味，而且自由度大。比喻很形象，说的很通俗易懂，不知道你是否有点收获。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;栈： &lt;strong&gt;在函数调用时，第一个进栈的是主函数中后的下一条指令&lt;/strong&gt;（函数调用语句的下一条可 执行语句）的地址，&lt;strong&gt;然后是函数的各个参数&lt;/strong&gt;，&lt;strong&gt;在大多数的C编译器中，参数是由右往左入栈 的&lt;/strong&gt;，然后&lt;strong&gt;是函数中的局部变量&lt;/strong&gt;。&lt;strong&gt;注意静态变量是不入栈的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;当本次函数调用结束后，局部变量先出栈，然后是参数，最后栈顶指针指向最开始存的地址，也就是主函数中的下一条指令，程序由该点继续运行。&lt;/p&gt;
&lt;p&gt;JVM&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;栈内存:栈内存首先是一片内存区域，存储的都是局部变量，凡是定义在方法中的都是局部变量（方法外的是全局变量），**for循环内部定义的也是局部变量**，是先加载函数才能进行局部变量的定义，所以方法先进栈，然后再定义变量，变量有自己的作用域，一旦离开作用域，变量就会被释放。**栈内存的更新速度很快，因为局部变量的生命周期都很短。**
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;   堆内存:存储的是数组和对象（其实数组就是对象），凡是new建立的都是在堆中，**堆中存放的都是实体（对象**），实体用于封装数据，而且是封装多个（实体的多个属性），如果一个数据消失，这个实体也没有消失，还可以用，
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;   所以堆是不会随时释放的，但是栈不一样，栈里存放的都是单个变量，变量被释放了，那就没有了。**堆里的实体虽然不会被释放，但是会被当成垃圾**，**Java有垃圾回收机制不定时的收取。**
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id=&#34;4-程序进程线程&#34;&gt;4. 程序、进程、线程
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;程序&lt;/strong&gt;是含有&lt;strong&gt;指令和数据的文件&lt;/strong&gt;，被存储在磁盘或其他的数据存储设备中，也就是说&lt;strong&gt;程序是静态的代码&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;进程&lt;/strong&gt;是程序的&lt;strong&gt;一次执行过程&lt;/strong&gt;，是&lt;strong&gt;系统运行程序的基本单位&lt;/strong&gt;，因此进程是动态的。系统运行一个程序&lt;strong&gt;即是一个进程从创建，运行到消亡的过程&lt;/strong&gt;。简单来说，一个进程就是一个执行中的程序，它在计算机中一个指令接着一个指令地执行着，同时，每个进程还占有某些系统资源如CPU时间，内存空间，文件，文件，输入输出设备的使用权等等。换句话说，&lt;strong&gt;当程序在执行时，将会被操作系统载入内存中&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;线程&lt;/strong&gt; 是 &lt;strong&gt;进程&lt;/strong&gt; 划分成的更小的运行单位。线程和进程&lt;strong&gt;最大的不同在于基本上各进程是独立的&lt;/strong&gt;，而各线程则不一定，因为同一进程中的线程极有可能会相互影响。从另一角度来说，进程属于操作系统的范畴，主要是同一段时间内，可以同时执行一个以上的程序，而线程则是在同一程序内几乎同时执行一个以上的程序段。&lt;/p&gt;
&lt;h2 id=&#34;5-动态库与静态库&#34;&gt;5. 动态库与静态库
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;静态（函数）库&lt;/strong&gt; 一般扩展名为（.a或.lib）,这类的函数库通常扩展名为libxxx.a或xxx.lib 。 &lt;strong&gt;这类库在编译的时候会直接整合到目标程序中&lt;/strong&gt;，所以利用静态函数库编译成的文件会比较大，这类函数库最大的优点就是编译成功的可执行文件可以独立运行，而不再需要向外部要求读取函数库的内容；但是从升级难易度来看明显没有优势，如果函数库更新，需要重新编译。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;动态函数库&lt;/strong&gt; 动态函数库的扩展名一般为（.so或.dll），这类函数库通常名为libxxx.so或xxx.dll 。 与静态函数库被整个捕捉到程序中不同，&lt;strong&gt;动态函数库在编译的时候，在程序里只有一个“指向”的位置而已，也就是说当可执行文件需要使用到函数库的机制时，程序才会去读取函数库来使用&lt;/strong&gt;；也就是说&lt;strong&gt;可执行文件无法单独运行&lt;/strong&gt;。这样从产品功能升级角度方便升级，只要替换对应动态库即可，不必重新编译整个可执行文件。&lt;/p&gt;
&lt;p&gt;静态库特点总结如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;静态库对函数库的链接是放在编译时期完成的。&lt;/li&gt;
&lt;li&gt;程序在运行时与函数库再无瓜葛，移植方便。&lt;/li&gt;
&lt;li&gt;浪费空间和资源，因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;动态库特点总结：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;动态库把对一些库函数的链接载入推迟到程序运行的时期。&lt;/li&gt;
&lt;li&gt;可以实现进程之间的资源共享。（因此动态库也称为共享库）&lt;/li&gt;
&lt;li&gt;将一些程序升级变得简单。&lt;/li&gt;
&lt;li&gt;甚至可以真正做到链接载入完全由程序员在程序代码中控制（&lt;strong&gt;显式调用&lt;/strong&gt;）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;6-锁-互斥锁旋锁读写锁乐观锁悲观锁&#34;&gt;6. 锁-&lt;strong&gt;互斥锁、⾃旋锁、读写锁、乐观锁、悲观锁&lt;/strong&gt;
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;互斥锁&lt;/strong&gt;加锁失败后，线程会&lt;strong&gt;释放&lt;/strong&gt; &lt;strong&gt;CPU&lt;/strong&gt; ，给其他线程；&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;⾃旋锁&lt;/strong&gt;加锁失败后，线程会&lt;strong&gt;忙等待&lt;/strong&gt;，直到它拿到锁； 自旋锁是通过 CPU 提供的 &lt;code&gt;CAS&lt;/code&gt; 函数（&lt;em&gt;Compare And Swap&lt;/em&gt;）&lt;strong&gt;需要注意，在单核 CPU 上，需要抢占式的调度器（即不断通过时钟中断一个线程，运行其他线程）。否则，自旋锁在单 CPU 上无法使用，因为一个自旋的线程永远不会放弃 CPU。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;对于互斥锁加锁失败⽽阻塞的现象，是由操作系统内核实现的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;互斥锁&lt;strong&gt;加锁失败时，会从⽤户态陷⼊到内核态，让内核帮我们切换线程&lt;/strong&gt;，虽然简化了使⽤锁的难度，但是&lt;strong&gt;存在⼀定的性能开销成本&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;那这个开销成本是什么呢？会有**两次线程上下⽂切换的成本，**线程的上下文切换的是什么？当两个线程是属于同一个进程，&lt;strong&gt;因为虚拟内存是共享的，所以在切换时，虚拟内存这些资源就保持不动，只需要切换线程的私有数据、寄存器等不共享的数据。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果你能确定被锁住的代码执⾏时间很短，就不应该⽤互斥锁，⽽应该选⽤⾃旋&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;当加锁失败时，互斥锁⽤「线程切换」来应&lt;/strong&gt;对，**⾃旋锁则⽤「忙等待」**来应对。&lt;/p&gt;
&lt;p&gt;如果**只读取共享资源⽤「读锁」&lt;strong&gt;加锁，如果要&lt;/strong&gt;修改共享资源则⽤「写锁」**加锁。&lt;/p&gt;
&lt;p&gt;当「写锁」没有被线程持有时，多个线程能够并发地持有读锁，这⼤⼤提⾼了共享资源的访问效率，因为「读锁」是⽤于读取共享资源的场景，所以多个线程同时持有读锁也不会破坏共享资源的数据。&lt;/p&gt;
&lt;p&gt;但是，&lt;strong&gt;⼀旦「写锁」被线程持有后，读线程的获取读锁的操作会被阻塞&lt;/strong&gt;，⽽且其他写线程的获取写锁的操作也会被阻塞。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;公平读写锁⽐较简单的⼀种⽅式是：⽤队列把获取锁的线程排队，不管是写线程还是读线程都按照先进先出的原则加锁即可，这样读线程仍然可以并发，也不会出现「饥饿」的现象。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;前⾯提到的&lt;strong&gt;互斥锁、⾃旋锁、读写锁，都是属于悲观锁&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;悲观锁做事⽐较悲观，它认为&lt;strong&gt;多线程同时修改共享资源的概率⽐较⾼，于是很容易出现冲突，所以访问共享资源前，先要上锁&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;如果多线程同时修改共享资源的概率⽐较低，就&lt;strong&gt;可以采⽤乐观锁。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;乐观锁做事⽐较乐观，它假定冲突的概率很低，它的⼯作⽅式是：&lt;strong&gt;先修改完共享资源，再验证这段时间内有没有发⽣冲突，如果没有其他线程在修改资源，那么操作完成，如果发现有其他线程已经修改过这个资源，就放弃本次操作&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;可⻅，乐观锁的⼼态是，不管三七⼆⼗⼀，先改了资源再说。另外，你会发现&lt;strong&gt;乐观锁全程并没有加锁，所以它也叫⽆锁编程，比如在线⽂档。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;实际上，我们常见的 SVN 和 Git 也是用了&lt;strong&gt;乐观锁的思想，先让用户编辑代码，然后提交的时候，通过版本号来判断是否产生了冲突&lt;/strong&gt;，发生了冲突的地方，需要我们自己修改后，再重新提交。&lt;/p&gt;
&lt;p&gt;乐观锁虽然去除了加锁解锁的操作，&lt;strong&gt;但是⼀旦发⽣冲突，重试的成本⾮常⾼&lt;/strong&gt;，所以&lt;strong&gt;只有在冲突概率⾮常低，且加锁成本⾮常⾼的场景时，才考虑使⽤乐观锁。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;7-fork函数&#34;&gt;7. fork()函数
&lt;/h2&gt;&lt;p&gt;fork（）函数通过系统调用&lt;strong&gt;创建一个与原来进程几乎完全相同的进程&lt;/strong&gt;，copy一个&lt;strong&gt;进程，包括代码、数据和分配给进程的资源&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;也就是两个进程可以做完全相同的事，&lt;strong&gt;但如果初始参数或者传入的变量不同，两个进程也可以做不同的事。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一个进程调用fork（）函数后，系统先给新的进程分配资源，例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中，只有少数值与原来的进程的值不同。&lt;strong&gt;相当于克隆了一个自己&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;fork的实现分为以下两步&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 复制进程资源 2. 执行该进程&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;复制进程的资源包括以下几步&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;进程控制块（PCB）&lt;/strong&gt;：包含了进程的基本信息，如&lt;strong&gt;进程状态、程序计数器、堆栈指针&lt;/strong&gt;等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;程序体&lt;/strong&gt;：即代码段、数据段等，表示进程的可执行程序和数据。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用户栈&lt;/strong&gt;：包含了&lt;strong&gt;函数调用、局部变量&lt;/strong&gt;等信息。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内核栈&lt;/strong&gt;：用于&lt;strong&gt;处理系统调用和中断时保存现场信息&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;虚拟内存池&lt;/strong&gt;：包括&lt;strong&gt;进程的地址空间&lt;/strong&gt;，即进程可以使用的&lt;strong&gt;虚拟内存范围。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;页表&lt;/strong&gt;：&lt;strong&gt;用于将虚拟地址映射到物理地址的数据结构&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;除了上述资源外，还有一些其他的资源也会被复制：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;文件描述符表&lt;/strong&gt;：&lt;strong&gt;指向文件的引用会被复制&lt;/strong&gt;，但文件本身不会被复制。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;信号处理设置&lt;/strong&gt;：父进程设置的&lt;strong&gt;信号处理函数和信号屏蔽字&lt;/strong&gt;会被复制到子进程中。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;环境变量&lt;/strong&gt;：父进程的&lt;strong&gt;环境变量&lt;/strong&gt;会被复制到子进程中。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;资源限制&lt;/strong&gt;：如文件描述符数、CPU 时间等&lt;strong&gt;资源限制会被复制&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;进行进程的话&lt;/strong&gt;就比较简单了，只需要将其加入到&lt;strong&gt;就绪队列&lt;/strong&gt;即可，&lt;strong&gt;接下来就等待cpu的调度了。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;8-计算机启动的时候内存做了什么&#34;&gt;8. 计算机启动的时候内存做了什么？
&lt;/h2&gt;&lt;p&gt;内存映射，&lt;strong&gt;CPU 地址总线的宽度决定了可访问的内存空间的大小&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;比如 16 位的 CPU 地址总线宽度为 20 位，地址范围是 1M。32 位的 CPU 地址总线宽度为 32 位，地址范围是 4G。你可以算算我们现在的 64 位机的地址范围。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;内存中划分出了一片一片区域分配给外设，BIOS&lt;/strong&gt;(基本输入输出系统)&lt;/p&gt;
&lt;p&gt;BIOS 更狠，不但其空间被映射到了内存 0xC0000 - 0xFFFFF 位置，其里面的程序还占用了开头的一些区域. BIOS 程序的入口地址也就是开始地址是 0xFFFF0（人家就那么写的）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;在你开机的一瞬间，CPU 的 PC 寄存器被强制初始化为 0xFFFF0&lt;/strong&gt;。如果再说具体些**，CPU 将段基址寄存器 cs 初始化为 0xF000，将偏移地址寄存器 IP 初始化为 0xFFF0**，根据实模式下的最终地址计算规则，将段基址左移 4 位，加上偏移地址，得到最终的物理地址也就是抽象出来的 PC 寄存器地址为 0xFFFF0。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;BIOS 被映射到了内存的某个位置&lt;/strong&gt;，并且开机一瞬间 CPU 强制将自己的 &lt;strong&gt;pc 寄存器初始化为 BIOS 程序的入口地址&lt;/strong&gt;, 跳转到物理地址 0xfe05b 处开始执行&lt;/p&gt;
&lt;p&gt;这块代码会&lt;strong&gt;检测一些外设信息，并初始化好硬件，建立中断向量表并填写中断例程&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;BIOS 把控制权转交给排在第一位的存储设备。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;所以 BIOS 负责加载了启动区，而启动区又负责加载真正的操作系统内核&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;按下开机键，CPU 将 PC 寄存器的值强制初始化为 0xffff0，这个位置是 BIOS 程序的入口地址（一跳）&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;该入口地址处是一个跳转指令，跳转到 0xfe05b 位置，开始执行（二跳）&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;执行了一些&lt;/strong&gt;硬件检测工作&lt;strong&gt;后，最后一步将启动区内容加载到内存 0x7c00，并跳转到这里（三跳）&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;启动区代码主要是加载操作系统内核，并跳转到加载处（四跳）&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;①加电→打开电源开关，给主板和内部风扇供电。&lt;/p&gt;
&lt;p&gt;②启动引导程序→CPU开始执行存储在ROM BIOS（基本输入输出系统）中的指令。&lt;/p&gt;
&lt;p&gt;③开机自检→计算机对系统的主要部件进行诊断测试。&lt;/p&gt;
&lt;p&gt;④加载操作系统→计算机将&lt;strong&gt;操作系统文件从磁盘读到RAM中&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;⑤&lt;strong&gt;检查配置文件&lt;/strong&gt;，定制操作系统的运行环境→读取配置文件，&lt;strong&gt;根据用户的设置对操作系统进行定制。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;⑥&lt;strong&gt;准备读取命令和数据&lt;/strong&gt;→计算机等待用户输入命令&lt;/p&gt;
&lt;h2 id=&#34;9-数据是如何拷贝到网卡的&#34;&gt;9. 数据是如何拷贝到网卡的
&lt;/h2&gt;&lt;p&gt;image-20210707232134178&lt;/p&gt;
&lt;h2 id=&#34;10冯诺依曼体系结构&#34;&gt;10.冯诺依曼体系结构
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;计算机处理的&lt;strong&gt;数据和指令一律用二进制数&lt;/strong&gt;表示；&lt;/li&gt;
&lt;li&gt;指令和数据不加区别混合存储在&lt;strong&gt;同一个存储器&lt;/strong&gt;中；&lt;/li&gt;
&lt;li&gt;顺序执行程序的每一条指令；&lt;/li&gt;
&lt;li&gt;计算机硬件由&lt;strong&gt;运算器、控制器、存储器、输入设备和输出设备&lt;/strong&gt;五大部分组成。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://img-blog.csdn.net/20130828001944015&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;12-用户堆栈和系统堆栈&#34;&gt;12. 用户堆栈和系统堆栈
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;内核栈&lt;/strong&gt;是属于&lt;strong&gt;操作系统空间的一块固定区域&lt;/strong&gt;，可以用于&lt;strong&gt;保存中断现场、保存操作系统子程序间相互调用的参数、返回值等。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;用户栈&lt;/strong&gt;是属于&lt;strong&gt;用户进程空间的一块区域，&lt;strong&gt;用户保存&lt;/strong&gt;用户进程子程序间的相互调用的参数、返回值等。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;**系统栈（也叫核心栈、内核栈）**是内存中属于操作系统空间的一块区域，其主要用途为：&lt;/p&gt;
&lt;p&gt;(1)&lt;strong&gt;保存中断现场，对于嵌套中断，被中断程序的现场信息依次压入系统栈&lt;/strong&gt;，中断返回时逆序弹出；&lt;/p&gt;
&lt;p&gt;(2)&lt;strong&gt;保存操作系统子程序间相互调用的参数、返回值、返回点以及子程序(函数)的局部变量&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;用户栈是用户进程空间中的一块区域，用于保存用户进程的子程序间相互调用的参数、返回值、返回点以及子程序(函数)的局部变量。&lt;/p&gt;
&lt;p&gt;那么为什么不直接用一个栈，何必浪费那么多的空间呢？？原因有二：&lt;/p&gt;
&lt;p&gt;（1）如果&lt;strong&gt;只用系统栈。系统栈一般大小有限&lt;/strong&gt;，如果&lt;strong&gt;中断有16个优先级&lt;/strong&gt;，那么系统栈一般大小为15（只需保存15个低优先级的中断，另一个高优先级中断处理程序处于运行），但用户程序子程序调用次数可能很多，那样15次子程序调用以后的&lt;strong&gt;子程序调用的参数、返回值、返回点以及子程序(函数)的局部变量就不能被保存，用户程序也就无法正常运行了&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;（2）如果&lt;strong&gt;只用用户栈。我们知道系统程序需要在某种保护下运行&lt;/strong&gt;，而用户栈在用户空间（即&lt;strong&gt;cpu处于用户态&lt;/strong&gt;，而&lt;strong&gt;cpu处于核心态时是受保护的&lt;/strong&gt;），不能提供相应的&lt;strong&gt;保护措施&lt;/strong&gt;（或相当困难）。&lt;/p&gt;
&lt;p&gt;核心程序的工作栈就是当前运行的用户进程的系统栈。每个进程都有自己的用户栈和系统栈。而且系统栈的大小是确定的（&lt;strong&gt;取决于系统允许的中断嵌套数量，即中断优先级个数&lt;/strong&gt;）。&lt;/p&gt;
&lt;p&gt;当系统因为系统调用（软中断）或硬件中断，CPU切换到&lt;strong&gt;特权工作模&lt;/strong&gt;式，进程陷入内核态，&lt;strong&gt;进程使用的栈也要从用户栈转向系统栈。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;从用户态到内核态要两步骤，首先是将用户堆栈地址保存到内核堆栈中&lt;/strong&gt;，然后将&lt;strong&gt;CPU堆栈指针寄存器指向内核堆栈&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;当由&lt;strong&gt;内核态转向用户态，步骤首先是将内核堆栈中得用户堆栈地址恢复到CPU堆栈指针寄存器&lt;/strong&gt;中。&lt;/p&gt;
&lt;h2 id=&#34;13-电脑32位和64位有什么区别&#34;&gt;13. 电脑32位和64位有什么区别
&lt;/h2&gt;&lt;p&gt;1、&lt;strong&gt;计算能力不同&lt;/strong&gt;：&lt;strong&gt;64位的系统理论上比32位系统快一倍&lt;/strong&gt;，并且它们的&lt;strong&gt;内存寻址&lt;/strong&gt;也不一样。&lt;/p&gt;
&lt;p&gt;2、支&lt;strong&gt;持的最大运行内存不同&lt;/strong&gt;：&lt;strong&gt;32位的电脑最大只支持4G（&lt;strong&gt;一般情况只能用&lt;/strong&gt;3.25G&lt;/strong&gt;左右），而64位的电脑则可以支持&lt;strong&gt;128G甚至更大。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;3、运行的软件不同：&lt;strong&gt;32位的电脑只能运行32位的软件，而64位的电脑可以运行32位的软件也可以运行64位的软件。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;4、支持的系统不同：32位电脑支持32位的系统，而64位的电脑支持&lt;strong&gt;支持32位的系统也支持64位的系统&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;64 位相比 32 位 CPU 的优势在哪吗？64 位 CPU 的计算性能一定比 32 位 CPU 高很多吗？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;64 位相比 32 位 CPU 的优势主要体现在两个方面：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;64 位 CPU 可以&lt;strong&gt;一次计算超过 32 位的数字&lt;/strong&gt;，而 32 位 CPU 如果要计算超过 32 位的数字，要分多步骤进行计算，效率就没那么高。&lt;/p&gt;
&lt;p&gt;但是大部分应用程序很少会计算那么大的数字，所以&lt;strong&gt;只有运算大数字的时候，64 位 CPU 的优势才能体现出来，否则和 32 位 CPU 的计算性能相差不大&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;64 位 CPU 可以&lt;strong&gt;寻址更大的内存空间&lt;/strong&gt;，32 位 CPU 最大的寻址地址是 4G，即使你加了 8G 大小的内存，也还是只能寻址到 4G，而 64 位 CPU 最大寻址地址是 &lt;code&gt;2^64&lt;/code&gt;，远超于 32 位 CPU 最大寻址地址的 &lt;code&gt;2^32&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;你知道软件的 32 位和 64 位之间的区别吗？再来 32 位的操作系统可以运行在 64 位的电脑上吗？64 位的操作系统可以运行在 32 位的电脑上吗？如果不行，原因是什么？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;64 位和 32 位软件，实际上代表指令是 64 位还是 32 位的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果 32 位指令在 64 位机器上执行，需要一套兼容机制，就可以做到兼容运行了。但是&lt;strong&gt;如果 64 位指令在 32 位机器上执行，就比较困难了，因为 32 位的寄存器存不下 64 位的指令&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;操作系统其实也是一种程序，我们也会看到操作系统会分成 32 位操作系统、64 位操作系统，其代表意义就是&lt;strong&gt;操作系统中程序的指令是多少位&lt;/strong&gt;，比如 64 位操作系统，指令也就是 64 位，因此不能装在 32 位机器上。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;总之，&lt;strong&gt;硬件的 64 位和 32 位指的是 CPU 的位宽&lt;/strong&gt;，&lt;strong&gt;软件的 64 位和 32 位指的是指令的位宽&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&#34;14-cpu-执行程序的过程&#34;&gt;14. CPU 执行程序的过程
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;第一步，CPU 读取「程序计数器」的值，这个值是&lt;strong&gt;指令的内存地址&lt;/strong&gt;，然后 CPU 的**「控制单元」操作「地址总线」指定需要访问的内存地址**，接着通知内存设备准备数据，数据准备好后&lt;strong&gt;通过「数据总线」将指令数据传给 CPU&lt;/strong&gt;，CPU 收到内存传来的数据后，将这个指&lt;strong&gt;令数据存入到「指令寄存器」&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;第二步，CPU &lt;strong&gt;分析「指令寄存器」中的指令&lt;/strong&gt;，确定指令的类型和参数，如果是计算类型的指令，就把指令交给**「逻辑运算单元」运算**；如果是存储类型的指令，则交&lt;strong&gt;由「控制单元」执行；&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;第三步，CPU 执行完指令后，「程序计数器」的值自增，表示指向下一条指令。这个自增的大小，由 CPU 的位宽决定，比如 32 位的 CPU，指令是 4 个字节，需要 4 个内存地址存放，&lt;strong&gt;因此「程序计数器」的值会自增 4；&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一个程序执行的时候，&lt;strong&gt;CPU 会根据程序计数器里的内存地址，从内存里面把需要执行的指令读取到指令寄存器里面执行，然后根据指令长度自增，开始顺序读取下一条指令。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;现代大多数 CPU 都使用来流水线的方式来执行指令，所谓的流水线就是把&lt;strong&gt;一个任务拆分成多个小任务&lt;/strong&gt;，于是一条指令通常分为 4 个阶段，称为 &lt;strong&gt;4 级流水&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;四个阶段的具体含义：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;CPU 通过程序计数器读取对应内存地址的指令，这个部分称为 &lt;strong&gt;Fetch（取得指令）&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;CPU 对指令进行解码，这个部分称为 &lt;strong&gt;Decode（指令译码）&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;CPU 执行指令，这个部分称为 &lt;strong&gt;Execution（执行指令）&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;CPU 将计算结果存回寄存器或者将寄存器的值存入内存，这个部分称为 &lt;strong&gt;Store（数据回写）&lt;/strong&gt;；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;上面这 4 个阶段，我们称为&lt;strong&gt;指令周期（&lt;em&gt;Instrution Cycle&lt;/em&gt;）&lt;/strong&gt;，CPU 的工作就是一个周期接着一个周期，周而复始。&lt;/p&gt;
&lt;h2 id=&#34;15-linux内核-vs-windows内核&#34;&gt;15. Linux内核 vs Windows内核
&lt;/h2&gt;&lt;p&gt;Linux 内核设计的理念主要有这几个点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;MutiTask&lt;/em&gt;，&lt;strong&gt;多任务&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;SMP&lt;/em&gt;，&lt;strong&gt;对称多处理&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;ELF&lt;/em&gt;，&lt;strong&gt;可执行文件链接格式&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Monolithic Kernel&lt;/em&gt;，&lt;strong&gt;宏内核&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;MutiTask 的意思是多任务&lt;/strong&gt;，代表着 Linux 是一个多任务的操作系统。&lt;/p&gt;
&lt;p&gt;多任务意味着可以有多个任务同时执行，这里的**「同时」可以是并发或并行：**&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于单核 CPU 时，可以让每个任务执行一小段时间，时间到就切换另外一个任务，从宏观角度看，一段时间内执行了多个任务，这被称为并发。&lt;/li&gt;
&lt;li&gt;对于多核 CPU 时，多个任务可以同时被不同核心的 CPU 同时执行，这被称为并行。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;SMP 的意思是对称多处理&lt;/strong&gt;，代表着&lt;strong&gt;每个 CPU 的地位是相等的，对资源的使用权限也是相同的，多个 CPU 共享同一个内存&lt;/strong&gt;，每个 CPU 都可以访问完整的内存和硬件资源。&lt;/p&gt;
&lt;p&gt;这个特点决定了 Linux 操作系统不会有&lt;strong&gt;某个 CPU 单独服务应用程序或内核程序&lt;/strong&gt;，而是每个程序都可以被分配到任意一个 CPU 上被执行。&lt;/p&gt;
&lt;p&gt;ELF 文件&lt;/p&gt;
&lt;p&gt;ELF（Executable and Linkable Format，&lt;strong&gt;可执行和可链接格式&lt;/strong&gt;）是一种用于存储可执行程序、共享库、目标代码和核心转储文件的标准文件格式。ELF 文件是在类 Unix 系统中广泛使用的一种二进制文件格式，用于表示可执行程序和库。&lt;/p&gt;
&lt;p&gt;ELF 文件包含了以下几个主要部分：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;ELF 文件头&lt;/strong&gt;：包含了描述文件类型、目标体系结构、入口点地址等信息的结构。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;程序头表&lt;/strong&gt;：描述了可执行文件的段（segment）和段在内存中的加载位置、大小等信息。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;节区头表&lt;/strong&gt;：描述了文件中各个节（section）的信息，如代码段、数据段、符号表等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;节区&lt;/strong&gt;：包含了程序的实际数据和代码，如可执行代码、全局变量、字符串等。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;符号表&lt;/strong&gt;：包含了程序中定义和引用的符号（如变量、函数名）的信息，用于链接时的符号解析。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;ELF 文件的优点包括了灵活性和可扩展性，它能够支持多种目标体系结构和操作系统，同时也支持调试信息和动态链接等特性。由于这些优点，ELF 成为了现代 Unix 和类 Unix 系统中标准的二进制文件格式。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Monolithic Kernel&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Monolithic Kernel 的意思是&lt;strong&gt;宏内核&lt;/strong&gt;，Linux 内核架构就是宏内核，意味着 Linux 的内核是一个完整的可执行程序，且拥有最高的权限。&lt;/p&gt;
&lt;p&gt;宏内核的特征是系统内核的所有模块，比如进程调度、内存管理、文件系统、设备驱动等，都运行在内核态。&lt;/p&gt;
&lt;p&gt;Windows 和 Linux 一样，同样支持 MutiTask 和 SMP，但不同的是，&lt;strong&gt;Window 的内核设计是混合型内核&lt;/strong&gt;，&lt;/p&gt;
&lt;p&gt;对于内核的架构一般有这三种类型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;宏内核，包含多个模块，整个内核像一个完整的程序；&lt;/li&gt;
&lt;li&gt;微内核，有一个最小版本的内核，&lt;strong&gt;一些模块和服务则由用户态管理；&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;混合内核，是宏内核和微内核的结合体，内核中抽象出了微内核的概念，也就是内核中会有一个小型的内核，其他模块就在这个基础上搭建，整个内核是个完整的程序；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Linux 的内核设计是采用了宏内核，Window 的内核设计则是采用了混合内核。&lt;/p&gt;
&lt;p&gt;这两个操作系统的可执行文件格式也不一样， Linux 可执行文件格式叫作 ELF，&lt;strong&gt;Windows 可执行文件格式叫作 PE。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;16-守护进程-孤儿进程-僵尸进程&#34;&gt;16 守护进程 孤儿进程 僵尸进程
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;守护进程&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;守护进程&lt;/strong&gt;指在后台运行的，&lt;strong&gt;没有控制终端与之相连的进程&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;它独立于控制终端，周期性地执行某种任务&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;Linux的大多数&lt;strong&gt;服务器&lt;/strong&gt;就是用守护进程的方式实现的，&lt;strong&gt;如web服务器进程http&lt;/strong&gt;等，udevd负责维护/dev目录下的&lt;strong&gt;设备文件&lt;/strong&gt; , acpid负责&lt;strong&gt;电源管理，&lt;strong&gt;syslogd负责维护/var/log下的&lt;/strong&gt;日志文件&lt;/strong&gt;,&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;可以看出守护进程通常采用以d结尾的名字,表示Daemo&lt;/strong&gt;n。&lt;/p&gt;
&lt;p&gt;精灵进程作用：&lt;strong&gt;提供服务。eg:内核线程：完成操作系统级别服务&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;创建守护进程要点：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;（1）让程序在后台执行。方法是调用fork（）产生一个子进程，然后使父进程退出。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;（2）&lt;strong&gt;调用setsid（）创建一个新对话期&lt;/strong&gt;。控制终端、登录会话和进程组通常是从父进程继承下来的，守护进程要摆脱它们，不受它们的影响，&lt;/p&gt;
&lt;p&gt;方法是&lt;strong&gt;调用setsid（）&lt;strong&gt;使进程&lt;/strong&gt;成为一个会话组长&lt;/strong&gt;。setsid（）调用成功后，进程成为新的会话组长和进程组长，并与原来的登录会话、进程组和控制终端脱离。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;（3）禁止进程重新打开控制终端。&lt;strong&gt;经过以上步骤，进程已经成为一个无终端的会话组长，但是它可以&lt;/strong&gt;重新申请打开一个终端&lt;/strong&gt;。为了避免这种情况发生，可以通过使进程不再是会话组长来实现。&lt;strong&gt;再一次通过fork（）创建新的子进程，使调用fork的进程退出。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;**（4）关闭不再需要的文件描述符。**子进程从父进程继承打开的文件描述符。如不关闭，将会浪费系统资源，造成进程所在的文件系统无法卸下以及引起无法预料的错误。首先获得最高文件描述符值，然后用一个循环程序，&lt;strong&gt;关闭0到最高文件描述符值的所有文件描述符。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;（5）将&lt;strong&gt;当前目录更改为根目录。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;（6）子进程从父进程继承的文件创建&lt;strong&gt;屏蔽字可能会拒绝某些许可权&lt;/strong&gt;。为防止这一点，使用&lt;strong&gt;unmask（0）将屏蔽字清零。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;（7）处理SIGCHLD信号。对于服务器进程，在请求到来时&lt;strong&gt;往往生成子进程处理请求&lt;/strong&gt;。如果子进程等待父进程捕获状态，则子进程将成为僵尸进程（zombie），从而占用系统资源。&lt;/p&gt;
&lt;p&gt;如果父进程等待子进程结束，将增加父进程的负担，影响服务器进程的并发性能。&lt;strong&gt;在Linux下可以简单地将SIGCHLD信号的操作设为SIG_IGN&lt;/strong&gt;。这样，&lt;strong&gt;子进程结束时不会产生僵尸进程。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;孤儿进程&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果父进程先退出，子进程还没退出&lt;/strong&gt;，那么&lt;strong&gt;子进程的父进程将变为init进程&lt;/strong&gt;。（注：任何一个进程都必须有父进程）。&lt;/p&gt;
&lt;p&gt;一个父进程退出，而它的一个或多个子进程还在运行，那么那些子进程将成为孤儿进程。&lt;strong&gt;孤儿进程将被init进程(进程号为1)所收养，并由init进程对它们完成状态收集工作。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;僵尸进程&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果&lt;strong&gt;子进程先退出，父进程还没退出&lt;/strong&gt;，那么子进程必须&lt;strong&gt;等到父进程捕获到了子进程的退出状态才真正结束&lt;/strong&gt;，否则这个时候子进程就成为&lt;strong&gt;僵尸进程&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;设置&lt;strong&gt;僵尸进程的目&lt;/strong&gt;的是&lt;strong&gt;维护子进程的信息&lt;/strong&gt;，&lt;strong&gt;以便父进程在以后某个时候获取&lt;/strong&gt;。这些信息至少包括&lt;strong&gt;进程ID，进程的终止状态，以及该进程使用的CPU时间&lt;/strong&gt;，所以当终止子进程的&lt;strong&gt;父进程调用wait或waitpid时就可以得到这些信息&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;如果一个进程终止，而该进程有子进程处于僵尸状态，那么它的所有僵尸子进程的父进程ID将被重置为1（init进程）。继承这些子进程的init进程将清理它们（也就是说&lt;strong&gt;init进程将wait它们&lt;/strong&gt;，从而&lt;strong&gt;去除它们的僵尸状态&lt;/strong&gt;）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如何避免僵尸进程？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;通过signal(SIGCHLD, SIG_IGN)通知内核对子进程的结束不关心，由内核回收&lt;/strong&gt;。如果不想让父进程挂&lt;/p&gt;
&lt;p&gt;起，可以在父进程中加入一条语句：signal(SIGCHLD,SIG_IGN);表示父进程忽略SIGCHLD信号，该&lt;/p&gt;
&lt;p&gt;信号是子进程退出的时候向父进程发送的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;父进程调用wait/waitpid等函数等待子进程结束&lt;/strong&gt;，如果尚无子进程退出wait会导致父进程阻塞。waitpid可以通过传递&lt;strong&gt;WNOHANG使父进程不阻塞立即返回&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果父进程很忙可以用signal注册信号处理函数，在信号处理函数调用wait/waitpid等待子进程退出。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;通过两次调用fork。&lt;strong&gt;父进程首先调用fork创建一个子进程然后waitpid等待子进程退出，子进程再fork一个孙进程后退出&lt;/strong&gt;。这样子进程退出后会被父进程等待回收，而对&lt;strong&gt;于孙子进程其父进程已经退出所以孙进程成为一个孤儿进程&lt;/strong&gt;，&lt;strong&gt;孤儿进程由init进程接管，孙进程结束后，init会等待回收&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;第一种方法&lt;strong&gt;忽略SIGCHLD信号，这常用于并发服务器的性能的一个技巧因为并发服务器常常fork很多子进程&lt;/strong&gt;，子进程终结之后需要服务器进程去wait清理资源。如果将此信号的处理方式设为忽略，可让内核把僵尸子进程转交给init进程去处理，省去了大量僵尸进程占用系统资源&lt;/p&gt;
&lt;h1 id=&#34;heading&#34;&gt;————————————————
&lt;/h1&gt;&lt;h2 id=&#34;1-程序进程线程概念分别是什么&#34;&gt;1. 程序、进程、线程概念分别是什么。
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;程序&lt;/strong&gt;是含有&lt;strong&gt;指令和数据的文件&lt;/strong&gt;，被存储在磁盘或其他的数据存储设备中，也就是说程序是静态的代码。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;进程是资源分配的基本单位&lt;/strong&gt;，它&lt;strong&gt;是程序执行时的一个实例&lt;/strong&gt;，在程序运行时创建；&lt;/p&gt;
&lt;p&gt;线程是&lt;strong&gt;程序执行的最小单位&lt;/strong&gt;，是&lt;strong&gt;进程的子任务，是进程的一个执行流，一个线程由多个线程组成的&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;具体来说：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;进程是操作系统分配资源的单位&lt;/strong&gt;，而&lt;strong&gt;线程是进程的一个实体，是CPU调度和分派的基本单位&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;线程没有独立的内存单元，不能够独立执行，必须依存在应用程序中。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;资源开销&lt;/strong&gt;：每个进程都有&lt;strong&gt;独立的代码和数据空间&lt;/strong&gt;（程序上下文），程序之间的切换&lt;strong&gt;会有较大的开销&lt;/strong&gt;；线程可以看做轻量级的进程，同一类线程共享代码和数据空间，每个线程都有自己独立的运行栈和程序计数器（PC），&lt;strong&gt;线程之间切换的开销小&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
</description>
        </item>
        <item>
        <title>计算机系统常见问题2</title>
        <link>https://Salmooo.github.io/p/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%982/</link>
        <pubDate>Fri, 10 Jan 2020 21:56:12 +0800</pubDate>
        
        <guid>https://Salmooo.github.io/p/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%982/</guid>
        <description>&lt;h3 id=&#34;linux的io模型介绍以及同步异步阻塞非阻塞的区别超级重要&#34;&gt;&lt;strong&gt;Linux的I/O模型介绍以及同步异步阻塞非阻塞的区别（超级重要）&lt;/strong&gt;
&lt;/h3&gt;&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://blog.csdn.net/sqsltr/article/details/92762279&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://blog.csdn.net/sqsltr/article/details/92762279&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://www.cnblogs.com/euphie/p/6376508.html&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://www.cnblogs.com/euphie/p/6376508.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;（IO过程包括两个阶段：&lt;/p&gt;
&lt;p&gt;（1）内核从IO设备读写数据和&lt;/p&gt;
&lt;p&gt;（2）进程从内核复制数据）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;阻塞：调用IO操作的时候，如果缓冲区空或者满了，调用的进程或者线程就会处于阻塞状态直到IO可用并完成数据拷贝。&lt;/li&gt;
&lt;li&gt;非阻塞：调用IO操作的时候，内核会马上返回结果，如果IO不可用，会返回错误，这种方式下进程需要不断轮询直到IO可用为止，但是当进程从内核拷贝数据时是阻塞的。&lt;/li&gt;
&lt;li&gt;IO多路复用就是同时监听多个描述符，一旦某个描述符IO就绪（读就绪或者写就绪），就能够通知进程进行相应的IO操作，否则就将进程阻塞在select或者epoll语句上。&lt;/li&gt;
&lt;li&gt;同步IO：同步IO模型包括阻塞IO，非阻塞IO和IO多路复用。特点就是当进程从内核复制数据的时候都是阻塞的。&lt;/li&gt;
&lt;li&gt;异步IO：在检测IO是否可用和进程拷贝数据的两个阶段都是不阻塞的，进程可以做其他事情，当IO完成后内核会给进程发送一个信号。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Epoll是Linux进行IO多路复用的一种方式，用于在一个线程里监听多个IO源，在IO源可用的时候返回并进行操作。它的特点是基于事件驱动，性能很高。&lt;/p&gt;
&lt;p&gt;epoll将文件描述符拷贝到内核空间后使用红黑树进行维护，同时向内核注册每个文件描述符的回调函数，当某个文件描述符可读可写的时候，将这个文件描述符加入到就绪链表里，并唤起进程，返回就绪链表到用户空间，由用户程序进行处理。&lt;/p&gt;
&lt;p&gt;Epoll有三个系统调用：epoll_create(),epoll_ctl()和epoll_wait()。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;eoll_create()函数在内核中初始化一个eventpoll对象，同时初始化红黑树和就绪链表。&lt;/li&gt;
&lt;li&gt;epoll_ctl()用来对监听的文件描述符进行管理。将文件描述符插入红黑树，或者从红黑树中删除，这个过程的时间复杂度是log(N)。同时向内核注册文件描述符的回调函数。&lt;/li&gt;
&lt;li&gt;epoll_wait()会将进程放到eventpoll的等待队列中，将进程阻塞，当某个文件描述符IO可用时，内核通过回调函数将该文件描述符放到就绪链表里，epoll_wait()会将就绪链表里的文件描述符返回到用户空间。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;4-io复用的三种方法selectpollepoll深入理解包括三者区别内部原理实现&#34;&gt;&lt;strong&gt;（4） IO复用的三种方法（select,poll,epoll）深入理解，包括三者区别，内部原理实现？&lt;/strong&gt;
&lt;/h3&gt;&lt;p&gt;（1）select的方法介绍：select把所有监听的文件描述符拷贝到内核中，挂起进程。当某个文件描述符可读或可写的时候，中断程序唤起进程，select将监听的文件描述符再次拷贝到用户空间，然select后遍历这些文件描述符找到IO可用的文件。下次监控的时候需要再次拷贝这些文件描述符到内核空间。select支持监听的描述符最大数量是1024.&lt;/p&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://imgconvert.csdnimg.cn/aHR0cHM6Ly9qaWFudHVrdS1saXdlbmJpbi5vc3MtY24tc2hhbmdoYWkuYWxpeXVuY3MuY29tLyVFOSU5RCVBMiVFNyVCQiU4RiVFNSU5MCU4OCVFOSU5QiU4Ni9zZWxlY3QucG5n?x-oss-process=image/format,png&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;（2）poll使用&lt;strong&gt;链表保存文件描述符&lt;/strong&gt;，其他的跟select没有什么不同。&lt;/p&gt;
&lt;p&gt;（3）epoll将文件描述符拷贝到内核空间后使用红黑树进行维护，同&lt;strong&gt;时向内核注册每个文件描述符的回调函数&lt;/strong&gt;，当某个文件描述符可读可写的时候，将这个文件描述符加入到就绪链表里，并唤起进程，&lt;strong&gt;返回就绪链表到用户空间。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://imgconvert.csdnimg.cn/aHR0cHM6Ly9qaWFudHVrdS1saXdlbmJpbi5vc3MtY24tc2hhbmdoYWkuYWxpeXVuY3MuY29tLyVFOSU5RCVBMiVFNyVCQiU4RiVFNSU5MCU4OCVFOSU5QiU4Ni9lcG9sbC5wbmc?x-oss-process=image/format,png&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;详见 &lt;a class=&#34;link&#34; href=&#34;https://www.cnblogs.com/Anker/p/3265058.html&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://www.cnblogs.com/Anker/p/3265058.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;coredump产生的条件&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;shell资源控制限制&lt;/strong&gt;，使用 ulimit -c 命令查看shell执行程序时的资源 ，如果为0，则不会产生coredump。可以用ulimit -c unlimited设置为不限大小。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;读写越界&lt;/strong&gt;，包括：数组访问越界，指针指向错误的内存，字符串读写越界&lt;/li&gt;
&lt;li&gt;使用了&lt;strong&gt;线程不安全的函数&lt;/strong&gt;，读写未加锁保护&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;错误使用指针转换&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;堆栈溢出&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;linux理论上最多可以创建多少个进程一个进程可以创建多少线程和什么有关&#34;&gt;&lt;strong&gt;Linux理论上最多可以创建多少个进程？一个进程可以创建多少线程，和什么有关&lt;/strong&gt;
&lt;/h3&gt;&lt;h3 id=&#34;3-冯诺依曼结构有哪几个模块分别对应现代计算机的哪几个部分百度安全一面&#34;&gt;&lt;strong&gt;（3） 冯诺依曼结构有哪几个模块？分别对应现代计算机的哪几个部分？（百度安全一面）&lt;/strong&gt;
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;存储器：内存&lt;/li&gt;
&lt;li&gt;控制器：南桥北桥&lt;/li&gt;
&lt;li&gt;运算器：CPU&lt;/li&gt;
&lt;li&gt;输入设备：键盘&lt;/li&gt;
&lt;li&gt;输出设备：显示器、网卡&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;如果要你实现一个mutex互斥锁你要怎么实现&#34;&gt;&lt;strong&gt;如果要你实现一个mutex互斥锁你要怎么实现？&lt;/strong&gt;
&lt;/h3&gt;&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://blog.csdn.net/kid551/article/details/84338619&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://blog.csdn.net/kid551/article/details/84338619&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;实现mutex最重要的就是实现它的lock()方法和unlock()方法。&lt;strong&gt;我们保存一个全局变量flag，flag=1表明该锁已经锁住，flag=0表明锁没有锁住。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;实现lock()时，&lt;strong&gt;使用一个while循环不断检测flag是否等于1，如果等于1就一直循环。然后将flag设置为1；unlock()方法就将flag置为0；&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;static int flag=0;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;void lock(){
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  while(TestAndSet(&amp;amp;flag,1)==1);
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  //flag=1;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;void unlock(){
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  flag=0;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;123456789
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;因为while有可能被重入，所以可以用TestandSet()方法。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;int TestAndSet(int *ptr, int new) {
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    int old = *ptr;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    *ptr = new;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    return old;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;线程之间通信：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用全局变量&lt;/li&gt;
&lt;li&gt;使用信号机制&lt;/li&gt;
&lt;li&gt;使用事件&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;进程之间同步：&lt;/p&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://www.cnblogs.com/sonic4x/archive/2011/07/05/2098036.html&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://www.cnblogs.com/sonic4x/archive/2011/07/05/2098036.html&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;信号量&lt;/li&gt;
&lt;li&gt;管程&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;什么时候用多进程什么时候用多线程&#34;&gt;&lt;strong&gt;什么时候用多进程，什么时候用多线程&lt;/strong&gt;
&lt;/h3&gt;&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://blog.csdn.net/yu876876/article/details/82810178&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://blog.csdn.net/yu876876/article/details/82810178&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;频繁修改：需要频繁创建和销毁的优先使用多线程&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;计算量：需要大量计算的优先使用&lt;strong&gt;多线程&lt;/strong&gt; 因为需要&lt;strong&gt;消耗大量CPU资源且切换频繁，所以多线程好一点&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;相关性：&lt;strong&gt;任务间相关性比较强的用多线程&lt;/strong&gt;，相关性比较弱的用多进程。因为线程之间的数据共享和同步比较简单。&lt;/li&gt;
&lt;li&gt;多分布：可能要扩展到&lt;strong&gt;多机分布的用多进程&lt;/strong&gt;，&lt;strong&gt;多核分布的用多线程。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但是实际中更常见的是进程加线程的结合方式，并不是非此即彼的。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;孤儿进程是父进程退出后它的子进程还在执行&lt;/strong&gt;，这时候这些&lt;strong&gt;子进程就成为孤儿进程&lt;/strong&gt;。孤儿进程会被&lt;strong&gt;init进程收养并完成状态收集。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;僵尸进程是指子进程完成并退出后父进程没有使用wait()或者waitpid()对它们进行状态收集，这些子&lt;strong&gt;进程的进程描述符仍然会留在系统&lt;/strong&gt;中。这些子进程就成为僵尸进程。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;协程就是子程序在&lt;strong&gt;执行时中断并转去执行别的子程序&lt;/strong&gt;，在适当的时候又返回来执行。&lt;/p&gt;
&lt;p&gt;这种子程序间的跳转不是函数调用，也不是多线程执行，所以省去&lt;strong&gt;了线程切换的开销，效率很高&lt;/strong&gt;，并且不需要多线程间的锁机制，不会发生变量写冲突。&lt;/p&gt;
&lt;h3 id=&#34;那协程的底层是怎么实现的怎么使用协程&#34;&gt;&lt;strong&gt;那协程的底层是怎么实现的，怎么使用协程？&lt;/strong&gt;
&lt;/h3&gt;&lt;p&gt;协程进行中断跳转时将函数的&lt;strong&gt;上下文存放在其他位置&lt;/strong&gt;中，而不是&lt;strong&gt;存放在函数堆栈里&lt;/strong&gt;，当处理完其他事情跳转回来的时候，取回上下文继续执行原来的函数。&lt;/p&gt;
&lt;h3 id=&#34;在执行malloc申请内存的时候操作系统是怎么做的内存分配的原理说一下malloc函数底层是怎么实现的进程是怎么分配内存的&#34;&gt;&lt;strong&gt;在执行malloc申请内存的时候，操作系统是怎么做的？/内存分配的原理说一下/malloc函数底层是怎么实现的？/进程是怎么分配内存的？&lt;/strong&gt;
&lt;/h3&gt;&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://blog.csdn.net/yusiguyuan/article/details/39496057&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://blog.csdn.net/yusiguyuan/article/details/39496057&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;从操作系统层面上看，malloc是通过两个系统调用来实现的： &lt;strong&gt;brk和mmap&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;brk是将进程&lt;strong&gt;数据段(.data)的最高地址指针向高处移动&lt;/strong&gt;，这一步可以扩&lt;strong&gt;大进程在运行时的堆大小&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;mmap是在进程的&lt;strong&gt;虚拟地址空间中寻找一块空闲的虚拟内存&lt;/strong&gt;，这一步可以&lt;strong&gt;获得一块可以操作的堆内存。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通常，分配的内&lt;strong&gt;存小于128k时，使用brk调用来获得虚拟内&lt;/strong&gt;存，大&lt;strong&gt;于128k时就使&lt;/strong&gt;用mmap来获得虚拟内存。&lt;/p&gt;
&lt;p&gt;进程先通过这两个系统调用获取或者扩大进程的虚拟内存，获得相应的虚拟地址，在访问这些虚拟地址的时候，通&lt;strong&gt;过缺页中断，让内核分配相应的物理内存，这样内存分配才算完成。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在网络编程中不同字节序的机器发送和接收的顺序不同。&lt;/p&gt;
&lt;p&gt;实现二维码登录通常涉及以下步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;生成二维码&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;服务器端生成一个唯一的登录标识（如&lt;strong&gt;随机生成的Token或临时会话ID）。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;使用生成的标&lt;strong&gt;识创建一个包含标识信息的二维码图像。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;将二维码图像发送给客户端，以便用户扫描。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;扫描二维码&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;用户打开登录应用或扫描工具，并选择扫描二维码选项。&lt;/li&gt;
&lt;li&gt;使用手机或摄像头扫描服务器生成的二维码。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;验证二维码&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;服务器端需要不断&lt;strong&gt;地轮询或等待客户端扫描并验证二维码。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;当客户端扫描到二维码后，将扫描到的信息（通常是&lt;strong&gt;登录标识）发送回服务器。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;创建登录会话&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;服务器接收到扫描信息后，验证该信息&lt;strong&gt;是否有效且未过期。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;如果验证通过，服务器创建一个&lt;strong&gt;登录会话，将用户标识与会话关联，并生成一个会话密钥。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;返回登录结果&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;服务器返回登录成功的响应，其中包括&lt;strong&gt;会话密钥或其他用于标识用户的信息。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;客户端接收到登录成功的响应后，将&lt;strong&gt;会话信息存储在本地，以备后续请求使用。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;保持会话状态&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;服务器和客户端都需要保持会话状态，以便在后续请求中验证用户身份。&lt;/li&gt;
&lt;li&gt;客户端通常会将会话信息存储在本地，而服务器会维护会话状态并提供相应的会话管理机制。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;处理登录超时或失败&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;如果用&lt;strong&gt;户长时间未扫描或扫描失败，服务器可以定期清除未使用的登录标识。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;如果扫描后验证失败，服务器应该返&lt;strong&gt;回登录失败的响应，并可能要求用户重新扫描。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;安全性考虑&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;实现时需要考虑安全性问题，包括&lt;strong&gt;数据的传输加密、二维码生成的随机性、会话标识的有效期限制等&lt;/strong&gt;，以防止恶意攻击。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;请注意，二维码登录是一种方便的登录方式，但需要确保安全性和用户体验。每个应用可能会根据自己的需求和安全标准来实现二维码登录的细节。此外，二维码登录通常与单点登录（SSO）等身份认证机制结合使用，以实现更高级的用户身份管理和认证。&lt;/p&gt;
&lt;h3 id=&#34;8g的int型数据计算机的内存只有2g怎么对它进行排序外部排序百度一面&#34;&gt;&lt;strong&gt;8G的int型数据，计算机的内存只有2G，怎么对它进行排序？（外部排序）（百度一面）&lt;/strong&gt;
&lt;/h3&gt;&lt;p&gt;我们可以使用外部排序来对它进行处理。首先将&lt;strong&gt;整个文件分成许多份&lt;/strong&gt;，比如说m份，划分的依据就是使&lt;strong&gt;得每一份的大小都能放到内存里&lt;/strong&gt;。然后我们用快速排序或者&lt;strong&gt;堆排序等方法对每一份数据进行一个内部排&lt;/strong&gt;序，变成&lt;strong&gt;有序子&lt;/strong&gt;串。接着对这&lt;strong&gt;m份有序子串进行m路归并排序&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;取这&lt;strong&gt;m份数据的最小元素，进行排序，输出排序后最小的元素到结果中&lt;/strong&gt;，同时从该&lt;strong&gt;元素所在子串中读入一个元素&lt;/strong&gt;，直到所&lt;strong&gt;有数据都被输出到结果中为止。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;BitMap算法评价&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;优点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;运算效率高，&lt;strong&gt;不进行比较和移位；&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;占用内存少，比如最大的数MAX=10000000；只需占用内存为MAX/8=1250000Byte=1.25M。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;缺点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;所有的数据不能重复，即不&lt;strong&gt;可对重复的数据进行排序&lt;/strong&gt;。（少量重复数据查找还是可以的，用2-bitmap）。&lt;/li&gt;
&lt;li&gt;所需要的空间随着最大元素的增大而增大，当数据类似（1，1000，10万）只有3个数据的时候，用&lt;strong&gt;bitmap时间复杂度和空间复杂度相当大&lt;/strong&gt;，只&lt;strong&gt;有当数据比较密集时才有优势。&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&#34;布隆过滤器原理与优点&#34;&gt;&lt;strong&gt;布隆过滤器原理与优点&lt;/strong&gt;
&lt;/h1&gt;&lt;p&gt;布隆过滤器是一个比特向量或者比特数组，它本质上是&lt;strong&gt;一种概率型数据结构，用来查找一个元素是否在集合中，支持高效插入和查询某条记录&lt;/strong&gt;。常作为针对超大数据量下高效查找数据的一种方法。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;它的具体工作过程是这样子的：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;假设布隆过滤器的大小为m（比特向量的长度为m），有k个哈希函数，它对每个数据用这k个哈希函数计算哈希，&lt;strong&gt;得到k个哈希值&lt;/strong&gt;，然后将向量中相应的位设为1。在查询某个数据是否存在的时候，对这&lt;strong&gt;个数据用k个哈希函数得到k个哈希值&lt;/strong&gt;，再在比特向量中相应的位查找是否为1，如果某一个相应的位不为1，那这个&lt;strong&gt;数据就肯定不存在。但是如果全找到&lt;/strong&gt;了，则这个数据有可能存在。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么说有可能存在呢？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;因为不同的数据经过哈希后可能有相同的哈希值，在比特向量上某个位置查找到1也可能是由于某个另外的数据映射得到的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;支持删除操作吗&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;目前布隆过滤器只支持插入和查找操作，&lt;strong&gt;不支持删除操作&lt;/strong&gt;，如果要支持删除，就要另外使用一个计数变量，每次&lt;strong&gt;将相应的位置为1则计数加一&lt;/strong&gt;，删除则减一。&lt;/p&gt;
&lt;p&gt;布隆&lt;strong&gt;过滤器中哈希函数的个数需要选择&lt;/strong&gt;。如果太多则很快所有位都置为1，如果太少会容易误报。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;布隆过滤器的大小以及哈希函数的个数怎么选择？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;k 为哈希函数个数，m 为布隆过滤器长度，n 为插入的元素个数，&lt;strong&gt;p 为误报率&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://imgconvert.csdnimg.cn/aHR0cHM6Ly9qaWFudHVrdS1saXdlbmJpbi5vc3MtY24tc2hhbmdoYWkuYWxpeXVuY3MuY29tLyVFOSU5RCVBMiVFNyVCQiU4RiVFNSU5MCU4OCVFOSU5QiU4Ni8lRTUlQjglODMlRTklOUElODYlRTglQkYlODclRTYlQkIlQTQlRTUlOTklQTgucG5n?x-oss-process=image/format,png&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</description>
        </item>
        <item>
        <title>计算机网络常见问题2</title>
        <link>https://Salmooo.github.io/p/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%982/</link>
        <pubDate>Sat, 21 Dec 2019 12:19:27 +0800</pubDate>
        
        <guid>https://Salmooo.github.io/p/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%982/</guid>
        <description>&lt;h3 id=&#34;1-对路由协议的了解与介绍内部网关协议igp包括ripospf和外部网关协议egp和bgp&#34;&gt;&lt;strong&gt;1. 对路由协议的了解与介绍。内部网关协议IGP包括RIP，OSPF，和外部网关协议EGP和BGP.&lt;/strong&gt;
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A&lt;/p&gt;
&lt;p&gt;路由协议是指在计算机网络中用于确定数据包如何从源节点传输到目标节点的规则集合。常见的路由协议包括内部网关协议（IGP）和外部网关协议（EGP）。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;内部网关协议（IGP）&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;RIP（路由信息协议）&lt;/strong&gt;：RIP是一种基于距离向量的路由协议，使用跳数作为度量标准。每隔一段时间，路由器会向相邻路由器发送路由更新信息。RIP“路由信息协议(Route Information Protocol)”的简写，主要传递路由信息，通过每隔30秒广播一次路由表，维护相邻路由器的位置关系，同时根据收到的路由表信息使用动态规划的方式计算自己的路由表信息。RIP是一个距离矢量路由协议,最大跳数为16跳,16跳以及超过16跳的网络则认为目标网络不可达。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OSPF（开放最短路径优先）&lt;/strong&gt;：OSPF是一种基于链路状态的路由协议，使用最短路径优先算法（Dijkstra算法）计算最佳路由。OSPF支持VLSM（可变长度子网掩码）和路由聚合。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;外部网关协议（EGP）&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;EGP（外部网关协议）&lt;/strong&gt;：EGP是一种早期的外部网关协议，已经不再广泛使用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BGP（边界网关协议）&lt;/strong&gt;：BGP是一种路径矢量协议，用于在不同自治系统之间交换路由信息。BGP是互联网核心路由协议，负责在全球范围内的路由选择。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这些协议在计算机网络中起着至关重要的作用，帮助数据包在网络中正确快速地传&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;udp如何实现可靠传输&#34;&gt;&lt;strong&gt;UDP如何实现可靠传输&lt;/strong&gt;
&lt;/h3&gt;&lt;p&gt;因为UDP是无连接的协议，所以在传输层上无法保证可靠传输，要想实现可靠传输，只能从应用层实现。需要实现seq/ack机制，重传机制和窗口确认机制。&lt;/p&gt;
&lt;p&gt;就要接收方收到UDP之后回复个确认包，发送方有个机制，收不到确认包就要重新发送，每个包有递增的序号，接收方发现中间丢了包就要发重传请求，当网络太差时候频繁丢包，防止越丢包越重传的恶性循环，要有个发送窗口的限制，发送窗口的大小根据网络传输情况调整，调整算法要有一定自适应性。&lt;/p&gt;
&lt;h3 id=&#34;2-流量控制的介绍采用滑动窗口会有什么问题死锁可能糊涂窗口综合征&#34;&gt;&lt;strong&gt;2. 流量控制的介绍，采用滑动窗口会有什么问题（死锁可能，糊涂窗口综合征）？&lt;/strong&gt;
&lt;/h3&gt;&lt;p&gt;所谓流量控制就是让发送方发送速率不要过快，让接收方来得及接收。利用TCP报文段中的窗口大小字段来控制发送方的发送窗口不大于接收方发回的窗口大小就可以实施流量控制。&lt;/p&gt;
&lt;p&gt;考虑一种特殊的情况，就是接收方若没有缓存足够使用，就会发送零窗口大小的报文，此时发送放将发送窗口设置为0，停止发送数据。之后接收方有足够的缓存，发送了非零窗口大小的报文，但是这个报文在中途丢失的，那么发送方的发送窗口就一直为零导致死锁。&lt;/p&gt;
&lt;p&gt;解决这个问题，&lt;strong&gt;TCP为每一个连接设置一个持续计时器（persistence timer）&lt;/strong&gt;。只要TCP的一方收到对方的零窗口通知，就启动该计时器，周期性的发送一个零窗口探测报文段。对方就在确认这个报文的时候给出现在的窗口大小（注意：TCP规定，即使设置为零窗口，也必须接收以下几种报文段：零窗口探测报文段、确认报文段和携带紧急数据的报文段）。&lt;/p&gt;
&lt;h3 id=&#34;3-一个机器能够使用的端口号上限是多少为什么可以改变吗那如果想要用的端口超过这个限制怎么办&#34;&gt;&lt;strong&gt;3. 一个机器能够使用的端口号上限是多少，为什么？可以改变吗？那如果想要用的端口超过这个限制怎么办？&lt;/strong&gt;
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;因为TCP的报文头部中&lt;strong&gt;源端口号和目的端口号的长度是16位&lt;/strong&gt;，也就是可以表示2^16=65536个&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;不同端口号，因此TCP可供识别的端口号最多只有65536个。&lt;strong&gt;但是由于0到1023是知名服务端口&lt;/strong&gt;，所以实际上还要少1024个端口号。&lt;/p&gt;
&lt;p&gt;而对于服务器来说，&lt;strong&gt;可以开的端口号与65536无关，其实是受限于Linux可以打开的文件数量&lt;/strong&gt;，并且可以通过&lt;strong&gt;MaxUserPort来进行配置&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;4--对称密码和非对称密码体系&#34;&gt;&lt;strong&gt;4.  对称密码和非对称密码体系&lt;/strong&gt;
&lt;/h3&gt;&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://blog.csdn.net/qq_29689487/article/details/81634057&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://blog.csdn.net/qq_29689487/article/details/81634057&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对称加密：加密和解密使用的密钥是同一个
&lt;ul&gt;
&lt;li&gt;优点：计算量小，算法速度快，加密效率高&lt;/li&gt;
&lt;li&gt;缺点：密钥容易泄漏。不同的会话需要不同的密钥，管理起来很费劲&lt;/li&gt;
&lt;li&gt;常用算法：&lt;strong&gt;DES，3DES，IDEA，CR4，CR5，CR6，AES&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;非对称加密：需要公钥和私钥，公钥用来加密，私钥用来解密
&lt;ul&gt;
&lt;li&gt;优点：&lt;strong&gt;安全，不怕泄漏&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺点：速度慢&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;常用算法：&lt;strong&gt;RSA，ECC，DSA&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;5--数字证书的了解高频&#34;&gt;&lt;strong&gt;5.  数字证书的了解（高频）&lt;/strong&gt;
&lt;/h3&gt;&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://imgconvert.csdnimg.cn/aHR0cHM6Ly9qaWFudHVrdS1saXdlbmJpbi5vc3MtY24tc2hhbmdoYWkuYWxpeXVuY3MuY29tLyVFOSU5RCVBMiVFNyVCQiU4RiVFNSU5MCU4OCVFOSU5QiU4Ni8lRTYlOTUlQjAlRTUlQUQlOTclRTglQUYlODElRTQlQjklQTYuanBn?x-oss-process=image/format,png&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;权威CA使用**私钥将网站A的信息和消息摘要（签名S）**进行加密打包形成数字证书。&lt;/p&gt;
&lt;p&gt;公钥给客户端。&lt;/p&gt;
&lt;p&gt;网站A将自己的&lt;strong&gt;信息和数字证书发给客户端，客户端用CA的公钥对数字证书进行解密&lt;/strong&gt;，得到签名S，与&lt;strong&gt;手动将网站的信息进行消息摘要&lt;/strong&gt;得到的结果S*进行对比，如果签名一致就证明网站A可以信任。&lt;/p&gt;
&lt;h3 id=&#34;6-消息摘要算法列举一下介绍md5算法为什么md5是不可逆的有什么办法可以加强消息摘要算法的安全性让它不那么容易被破解呢百度安全一面&#34;&gt;&lt;strong&gt;6. 消息摘要算法列举一下，介绍MD5算法，为什么MD5是不可逆的，有什么办法可以加强消息摘要算法的安全性让它不那么容易被破解呢？（百度安全一面）&lt;/strong&gt;
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;消息摘要算法有MD家族（MD2，MD4，MD5），SHA家族（SHA-1,SHA-256）和CRC家族（CRC8,CRC16,CRC32）等等&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;MD5算法介绍：&lt;/p&gt;
&lt;p&gt;MD5以512位分组来处理输入的信息，且每一分组又被划分为若干个小分组（16个32位子分组），经过一些列的处理后，&lt;strong&gt;算法输出由四个散列值&lt;/strong&gt;（32位分组组成的128位散列值。）&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;MD5首先将输入的信息分成若干个512字节长度的分组，如果&lt;strong&gt;不够就填充1和若干个0。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;对每个512字节的分组进行循环运算。使用四个幻数对第一个分组的数据进行四轮变换，得到四个变量。&lt;/li&gt;
&lt;li&gt;接下来对其中三个使用线性函数进行计算，与剩下一个相加，并赋值给其中某个变量，得到新的四个变量，重复16次这个过程，得到的四个变量作为幻数，与下一个分组进行相似的计算。&lt;/li&gt;
&lt;li&gt;遍历所有分组后得到的四个变量即为结果。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;详见：https://blog.csdn.net/weixin_39640298/article/details/84555814&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;为什么不可逆：因为MD5在进行消息摘要的过程中，&lt;strong&gt;数据与原始数据相比发生了丢失&lt;/strong&gt;，所以不能由结果进行恢复。&lt;/li&gt;
&lt;li&gt;加强安全性：&lt;strong&gt;加盐（加随机数）&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;7-单条记录高并发访问的优化&#34;&gt;&lt;strong&gt;7. 单条记录高并发访问的优化&lt;/strong&gt;
&lt;/h3&gt;&lt;p&gt;服务器端：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;使用缓存，如redis&lt;/strong&gt;等&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;使用分布式架&lt;/strong&gt;构进行处理&lt;/li&gt;
&lt;li&gt;将静态页面和静态&lt;strong&gt;资源存储在静态资源服务器&lt;/strong&gt;，需要处理的数据&lt;strong&gt;使用服务器进行计算后返回&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;将&lt;strong&gt;静态资源尽可能在客户端进行缓存&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;采用&lt;strong&gt;ngnix进行负载均衡&lt;/strong&gt; （nginx读作恩静埃克斯 = &lt;strong&gt;Engine X&lt;/strong&gt;）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;数据库端：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据库&lt;strong&gt;采用主从赋值&lt;/strong&gt;，&lt;strong&gt;读写分离措施&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;建立&lt;strong&gt;适当的索引&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分库分表&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;8--介绍一下ping的过程分别用到了哪些协议-百度安全等&#34;&gt;&lt;strong&gt;8.  介绍一下ping的过程，分别用到了哪些协议 （百度安全等）&lt;/strong&gt;
&lt;/h3&gt;&lt;p&gt;详见：&lt;a class=&#34;link&#34; href=&#34;https://www.cnblogs.com/Akagi201/archive/2012/03/26/2418475.html&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Ping原理与ICMP协议&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;ping是使用ICMP协议来进行工作的。 ICMP:网络控制报文协议&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;首先，ping命令会构建一个ICMP请求数据包，然后由&lt;strong&gt;ICMP协议将这个数据包连同目的IP地址源IP地址一起交给IP协议。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;然后IP协议就会构建一个IP数据报，并且&lt;strong&gt;在映射表中查找目的IP对应的mac地址&lt;/strong&gt;，将其交给数据&lt;strong&gt;链路层。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;然后数据链路层就会构建一个数据帧，附上&lt;strong&gt;源mac地址和目的mac地址发送出&lt;/strong&gt;去。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;目的主机接收到数据帧后，就会检查包上的mac地址与本机mac是否相符，如果相符，&lt;strong&gt;就接收并把其中的信息提取出来交给IP协议&lt;/strong&gt;，IP协议就会&lt;strong&gt;将其中的信息提取出来交给ICMP协议&lt;/strong&gt;。然后构建一个ICMP应答包，&lt;strong&gt;用相同的过程发送回&lt;/strong&gt;去。&lt;/p&gt;
&lt;h3 id=&#34;9--tcpip的粘包与避免介绍一下&#34;&gt;&lt;strong&gt;9.  TCP/IP的粘包与避免介绍一下&lt;/strong&gt;
&lt;/h3&gt;&lt;p&gt;因为TCP为了减少额外开销，采取的是流式传输，所以接收端在一次接收的时候有可能一次接收多个包。而TCP粘包就是发送方的若干个数据包到达接收方的时候粘成了一个包。多个包首尾相接，无法区分。&lt;/p&gt;
&lt;p&gt;导致TCP粘包的原因有三方面：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;发送端等待缓冲区满才进行发送，造成粘包&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;接收方来不及接收缓冲区内的数据，造成粘包&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;由于TCP协议在&lt;strong&gt;发送较小的数据包的时候&lt;/strong&gt;，会将&lt;strong&gt;几个包合成一个包后发送&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;避免粘包的措施：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;通过编程，&lt;strong&gt;强制使TCP发生数据传送&lt;/strong&gt;，不必&lt;strong&gt;等到缓冲区满&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;优化接收方接收数据的过程，使其&lt;strong&gt;来得及接收数据包，&lt;strong&gt;包括提高接&lt;/strong&gt;收进程优先级&lt;/strong&gt;等&lt;/li&gt;
&lt;li&gt;设置&lt;strong&gt;固定长度的报文&lt;/strong&gt; 或者 &lt;strong&gt;设置报文头部指示报文的长度&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;10-说一下tcp的封包和拆包&#34;&gt;&lt;strong&gt;10. 说一下TCP的封包和拆包&lt;/strong&gt;
&lt;/h3&gt;&lt;p&gt;因为TCP是无边界的流传输，所以需要对TCP进行封包和拆包，确保发送和接收的数据不粘连。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;封包：封包就是在发送数据报的时候为&lt;strong&gt;每个TCP数据包加上一个包头&lt;/strong&gt;，将数据报分为包头和包体两个部分。&lt;strong&gt;包头是一个固定长度的结构体，里面包含该数据包的总长度。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;拆包：接收方在接收到报文后&lt;strong&gt;提取包头中的长度信息进行截取。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        </item>
        <item>
        <title>计算机系统学习总结</title>
        <link>https://Salmooo.github.io/p/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93/</link>
        <pubDate>Thu, 19 Dec 2019 20:20:52 +0800</pubDate>
        
        <guid>https://Salmooo.github.io/p/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%B3%BB%E7%BB%9F%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93/</guid>
        <description>&lt;h1 id=&#34;1-总体概念&#34;&gt;1. 总体概念
&lt;/h1&gt;&lt;h2 id=&#34;11-操作系统的特性&#34;&gt;1.1 操作系统的特性
&lt;/h2&gt;&lt;p&gt;四个特性：&lt;strong&gt;并发、共享、虚拟、异步。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;并发：同一段时间内(时间片轮转算法)多个程序执行。程序并发性体现在两个方面： &lt;strong&gt;用户程序与用户程序之间&lt;/strong&gt;的并发执行。 用户程序与操作系统程序之间的并发。&lt;/li&gt;
&lt;li&gt;共享：系统中的资源可以被内存中多个并发执行的&lt;strong&gt;进线程共同使用&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;虚拟：通过&lt;strong&gt;时分复用&lt;/strong&gt;（虚拟处理机、虚拟设备）以及&lt;strong&gt;空分复用&lt;/strong&gt;（如虚拟内存，虚拟磁盘）技术实现&lt;strong&gt;把一个物理实体虚拟为多个&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;异步：系统中的&lt;strong&gt;进程是以走走停停的方式&lt;/strong&gt;执行的，&lt;strong&gt;且以一种不可预知的速度推进&lt;/strong&gt;。（同步就是&lt;strong&gt;实时处理&lt;/strong&gt;，比如打电话，异步就是&lt;strong&gt;分时处理&lt;/strong&gt;，比如&lt;strong&gt;发短信&lt;/strong&gt;）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;12-操作系统的主要功能&#34;&gt;1.2 操作系统的主要功能
&lt;/h2&gt;&lt;p&gt;操作系统的本质是&lt;strong&gt;对资源的管理&lt;/strong&gt;。包括了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;处理器管理&lt;/strong&gt;：以进程为单位分配资源，&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;存储器管理&lt;/strong&gt;：也叫内存管理&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;设备管理&lt;/strong&gt;：完成所有的IO请求&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;文件管理&lt;/strong&gt;：包括磁盘存储空间管理，文件读写管理等等&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;13-用户态和内核态&#34;&gt;1.3 用户态和内核态
&lt;/h2&gt;&lt;p&gt;从整体上讲，操作系统一般可分为**内核（kernel）&lt;strong&gt;和&lt;/strong&gt;外壳（shell）**两大部分。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://uk-1259555870.cos.eu-frankfurt.myqcloud.com/20200218111032.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;内核态与用户态是操作系统的两种运行级别,&lt;/p&gt;
&lt;p&gt;用户态：&lt;strong&gt;当进程在执行用户自己的代码时，则称其处于用户态&lt;/strong&gt;，这时&lt;strong&gt;cpu访问资源有限&lt;/strong&gt;，&lt;strong&gt;运行在用户态下的程序不能直接访问操作系统内核数据结构和程序&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;内核态：&lt;strong&gt;当一个任务(进程)执行系统调用而陷入内核代码中执行时，我们就称进程处于内核状态，这时cpu可以访问计算机的任何资源&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;当程序运行在0级特权级上时，就可以称之为运行在内核态，CPU将指令分为&lt;strong&gt;特权指令和非特权指令，&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于那些危险的指令，只允许操作系统及其相关模块使用，普通的应用程序只能使用那些不会造成灾难的指令。比如清内存、设置时钟。运行在用户态下的程序不能直接访问操作系统内核数据结构和程序。&lt;/p&gt;
&lt;p&gt;当程序运行在3级特权级上时，就可以称之为运行在用户态，因为这是最低特权级，是普通的用户进程运行的特权级，大部分用户直接面对的程序都是运行在用户态；&lt;/p&gt;
&lt;p&gt;两种状态的主要区别&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;处于用户态执行时，进程所能访问的内存空间和对象&lt;strong&gt;受到限制&lt;/strong&gt;，&lt;strong&gt;其所处于占有的处理机是可被抢占的&lt;/strong&gt; ；&lt;/p&gt;
&lt;p&gt;而处于内核态执行中的进程，则能&lt;strong&gt;访问所有的内存空间和对象&lt;/strong&gt;，且&lt;strong&gt;所占有的处理机是不允许被抢占的&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;用户态切换到内核态有三种情况：&lt;strong&gt;主动，被动，被迫&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;系统调用&lt;/strong&gt;：用户态进程主动要求切换到内核态申请使用操作系统提供的服务程序完成工作的一种方式，&lt;strong&gt;fork()实际上就是执行了一个创建新进程的系统调用。（主动）&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;异常&lt;/strong&gt;：当前运行进程切换到&lt;strong&gt;处理此异常&lt;/strong&gt;的&lt;strong&gt;内核相关程序 （被迫）&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;外围设备中断&lt;/strong&gt;：当外围设备完成用户请求的操作后，&lt;strong&gt;会向CPU发出相应的中断信号&lt;/strong&gt;，这时CPU会暂停执行下一条即将要执行的指令转而去&lt;strong&gt;执行与中断信号对应的处理程序&lt;/strong&gt;。  &lt;strong&gt;如果先前执行的指令是用户态下的程序&lt;/strong&gt;，那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成，系统会切换到硬盘读写的中断处理程序中执行后续操作等。&lt;strong&gt;（被动）&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从触发方式上看，可以认为存在前述3种不同的类型。&lt;/p&gt;
&lt;p&gt;但是从最终实际完成由用户态到内核态的切换操作上来说，&lt;strong&gt;涉及的关键步骤是完全一致的&lt;/strong&gt;，没有任何区别。&lt;/p&gt;
&lt;p&gt;都相当于执行了一个&lt;strong&gt;中断响应&lt;/strong&gt;的过程，因为系统调用实际上最终是&lt;strong&gt;中断机制实现&lt;/strong&gt;的，而异常和中断的处理机制基本上也是一致的，关于它们的具体区别这里不再赘述。关于中断处理机制的细节和步骤这里也不做过多分析。&lt;/p&gt;
&lt;p&gt;涉及到由用户态切换到内核态的步骤：
需要注意的是，内核态堆栈仅用于内核例程，Linux内核另外为中断提供了单独的硬中断栈和软中断栈
[1] 从当前进程的描述符中提取其内核栈的ss0及esp0信息。（ss0段选择子，用于指示内核堆栈所在的段描述符，esp堆栈是一个32位寄存器，存储了内核模式下的堆栈顶部地址指针)
[2] 使用ss0和esp0指向的内核栈将当前进程的cs,eip,eflags,ss,esp信息保存起来，这个过程也完成了由用户栈到内核栈的切换过程，同时保存了被暂停执行的程序的下一条指令。
[3] 将先前由中断向量检索得到的中断处理程序的cs,eip信息装入相应的寄存器，开始执行中断处理程序，这时就转到了内核态的程序执行了。&lt;/p&gt;
&lt;p&gt;（因为内核控制路径使用很少的栈空间，所以只需要几千个字节的内核态堆栈。 需要注意的是，内核态堆栈仅用于内核例程，&lt;strong&gt;Linux内核另外为中断提供了单独的硬中断栈和软中断栈）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;寄存器常见缩写：&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;16
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;17
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;18
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;19
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;20
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;1. PC - Program Counter（程序计数器）
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2. SP - Stack Pointer（栈指针）
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;3. IR - Instruction Register（指令寄存器）
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;4. PSW - Program Status Word（程序状态字）
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;5. ACC - Accumulator（累加器）
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;6. R0, R1, R2, ... - General Purpose Registers（通用寄存器）
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;7. MAR - Memory Address Register（存储器地址寄存器）
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;8. MDR - Memory Data Register（存储器数据寄存器）
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;9. MSR - Machine Status Register（机器状态寄存器）
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;10. SR - Status Register（状态寄存器）
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;11. CR - Control Register（控制寄存器）
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;12. EFLAGS - Extended Flags Register（扩展标志寄存器）
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;13. FLAGS - Flags Register（标志寄存器）
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;14. XMM - Extended Multimedia Register（扩展多媒体寄存器）
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;15. FPU - Floating Point Unit（浮点运算单元）
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;16. MMX - Multimedia Extensions（多媒体扩展寄存器）
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;17. GP - Global Pointer（全局指针）
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;18. BP - Base Pointer（基址指针）
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;19. SP - Stack Pointer（栈指针）
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;20. IP - Instruction Pointer（指令指针）
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h1 id=&#34;2-线程与进程&#34;&gt;2. 线程与进程
&lt;/h1&gt;&lt;h2 id=&#34;21-线程进程协程&#34;&gt;2.1 线程，进程，协程
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;进程是资源分配的基本单位&lt;/strong&gt;，它&lt;strong&gt;是程序执行时的一个实例&lt;/strong&gt;，在程序运行时创建；&lt;/p&gt;
&lt;p&gt;线程是&lt;strong&gt;程序执行的最小单位&lt;/strong&gt;，是&lt;strong&gt;进程的子任务，是进程的一个执行流，一般来说一个进程由多个线程组成的。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;具体来说：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;定义，资源隔离，创建销毁开销，切换开销通信和同步，并发性和并行性，故障影响，使用场景&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;进程是操作系统分配资源的单位&lt;/strong&gt;，而&lt;strong&gt;线程是进程的一个实体，是CPU调度和分派的基本单位&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;线程没有独立的内存单元，只拥有一点在运行中必不可少的资源，如寄存器和运行栈，不能够独立执行，必须依存在进程中。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;执行过程：&lt;strong&gt;每个独立的进程有程序运行的入口&lt;/strong&gt;、顺序执行序列和程序出口。&lt;strong&gt;但是线程不能独立执&lt;/strong&gt;行，必须依存在应用程序中，由应用程序提供多个线程执行控制，两者均可并发执行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;资源开销&lt;/strong&gt;：每个进程都有&lt;strong&gt;独立的代码和数据空间&lt;/strong&gt;（程序上下文），程序之间的&lt;strong&gt;切换会有较大的开销&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;线程可以看做轻量级的进程，&lt;strong&gt;同一类线程共享代码和数据空间&lt;/strong&gt;，每个&lt;strong&gt;线程都有自己独立的运行栈和程序计数器（PC）&lt;/strong&gt;，&lt;strong&gt;线程之间切换的开销小&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;包含关系：如果一个进程内有多个线程，则执行过程不是一条线的，而是多条线（线程）共同完成的；线程是进程的一部分，所以线程也被称为轻权进程或者轻量级进程。&lt;/li&gt;
&lt;li&gt;影响关系：&lt;strong&gt;一个进程崩溃后，在保护模式下不会对其他进程产生影响&lt;/strong&gt;，但是一个线程崩溃整个进程都死掉。所以&lt;strong&gt;多进程要比多线程健壮&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对于操作系统来说，&lt;strong&gt;一个任务就是一个进程(Process)&lt;/strong&gt;，比如使用Word。&lt;/p&gt;
&lt;p&gt;而一个进程可能不只干一件事（&lt;strong&gt;比如word既要打字又要检查拼写&lt;/strong&gt;），这种&lt;strong&gt;进程内的多个子任务就是线程&lt;/strong&gt;（Thread），&lt;strong&gt;进程&lt;/strong&gt;是程序的一次执行过程，&lt;strong&gt;是系统运行程序的基本单位&lt;/strong&gt;，因此进程是动态的。&lt;/p&gt;
&lt;p&gt;系统运行一个程序即是&lt;strong&gt;一个进程从创建，运行到消亡的过程&lt;/strong&gt;。简单来说，&lt;strong&gt;一个进程就是一个执行中的程&lt;/strong&gt;序，它在计算机中一个指令接着一个指令地执行着，同时，&lt;strong&gt;每个进程还占有某些系统资源&lt;/strong&gt;如&lt;strong&gt;CPU时间，内存空间，文件，文件，输入输出设备的使用权&lt;/strong&gt;等等。&lt;/p&gt;
&lt;p&gt;换句话说，&lt;strong&gt;当程序在执行时，将会被操作系统载入内存中&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;Q: &lt;strong&gt;一个进程最多可以创建多少个线程？&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;进程的虚拟内存空间上限&lt;/strong&gt;，&lt;strong&gt;因为创建一个线程，操作系统需要为其分配一个栈空间&lt;/strong&gt;，如果线程数量越多，所需的栈空间就要越大，那么虚拟内存就会占用的越多。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;系统参数限制&lt;/strong&gt;，虽然 Linux 并没有内核参数来控制单个进程创建的最大线程个数，但是有&lt;strong&gt;系统级别的参数来控制整个系统的最大线程个数&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;多线程&#34;&gt;&lt;strong&gt;多线程&lt;/strong&gt;
&lt;/h3&gt;&lt;p&gt;举个例子，假设要编写一个视频播放器软件，那么该软件功能的核心模块有三个：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;从视频文件当中读取数据&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;对读取&lt;strong&gt;的数据进行解压缩&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;把解压缩&lt;strong&gt;后的视频数据播放出来&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;播放出来的画面和声音会不连贯，因为当 CPU 能力不够强的时候，&lt;code&gt;Read&lt;/code&gt; 的时候可能进程就等在这了，这样就会导致等半天才进行数据解压和播放；&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;各个函数之间&lt;strong&gt;不是并发执行&lt;/strong&gt;，影响资源的使用效率；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Q: 多线程的好处:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;并发执行，资源共享，核心利用，多任务场景，代码模块化，提高响应性&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;通俗地讲例子：&lt;/p&gt;
&lt;p&gt;1.使用线程可以把占&lt;strong&gt;据时间长的程序中的任务放到后台去处理&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;2.用户界面更加吸引人,这样&lt;strong&gt;比如用户点击了一个按钮去触发某件事件的处理,可以弹出一个进度条来显示处理的进度&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;3.&lt;strong&gt;程序的运行效率可能会提高&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;4.在一些&lt;strong&gt;等待的任务实现上如用户输入,文件读取和网络收发数据等&lt;/strong&gt;, 线程就比较有用了。&lt;/p&gt;
&lt;p&gt;线程主要优点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个进程中可以同时存在多个线程；&lt;/li&gt;
&lt;li&gt;各个线程之间可以&lt;strong&gt;并发执行&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;各个线程之间可以&lt;strong&gt;共享地址空间和文件等资源&lt;/strong&gt;；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Q: 多线程的缺点:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;竞态条件，死锁和活锁，通信复杂，调试困难，性能下降，不确定性&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如果&lt;strong&gt;有大量的线程,大量的上下文切换会影响性能,因为操作系统需要在它们之间切换&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;更多的线程需要&lt;strong&gt;更多的内存空间&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;线程&lt;strong&gt;中止需要考虑对程序运行的影响.&lt;/strong&gt; 当进程中的一个线程崩溃时，会导致其所属进程的所有线程崩溃（这里是针对 C/C++ 语言，Java语言中的线程奔溃不会造成进程崩溃）&lt;/li&gt;
&lt;li&gt;通常块模型数据是在多个线程间共享的, 需要防止&lt;strong&gt;线程死锁情况&lt;/strong&gt;的发生&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;q1-进程中的一个线程崩溃之后所有线程都会崩溃吗&#34;&gt;Q1. &lt;strong&gt;进程中的一个线程崩溃之后所有线程都会崩溃吗？&lt;/strong&gt;
&lt;/h3&gt;&lt;p&gt;这个不是必定发生的，假设一个进程启动了a,b,c三个线程，只要这三个线程之间的其中一个在运行过程中触发了&lt;strong&gt;unix的信号，比如除0异常，违规访问内存触发段错误等等，都会使得os向进程发送特定的信号&lt;/strong&gt;，&lt;strong&gt;进程默认的行为是在接收到这些信号的时候退出&lt;/strong&gt;，&lt;strong&gt;表象就是你所说的一个线程奔溃导致其他线程奔溃了&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;但是如果&lt;strong&gt;a线程的奔溃没有触发操作系统向进程发送信号或者在进程中已经提前注册了对应信号的回调函数&lt;/strong&gt;(此时收到信号进程不会按默认行为退出而且执行预设的回调函数)，那么&lt;strong&gt;其他两个线程还是能正常地运行。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;大部分情况下，其他线程并不会自己崩溃，而是操作系统检测到异常，会kill掉进程，其他线程就一起被干掉了。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;小部分情况下&lt;/strong&gt;，&lt;strong&gt;一个线程出错，破坏了进程中其他线程的内存，导致其他线程出现严重错误，被操作系统检测到，然后连同进程一起干掉&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;进程是由内核管理和调度的，所以进程的切换只能发生在内核态。&lt;/p&gt;
&lt;p&gt;进程上下文切换开销&lt;/p&gt;
&lt;p&gt;Q: 为什么物理内存只有 2G，进程的虚拟内存却可以使用 25T 呢？&lt;/p&gt;
&lt;p&gt;因为&lt;strong&gt;虚拟内存并不是全部都映射到物理内存的&lt;/strong&gt;，&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;程序是有局部性的特性，也就是某一个时间只会执行部分代码，所以只需要映射这部分程序就好&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;32 位系统，用户态的虚拟空间只有 3G，如果创建线程时分配的栈空间是 10M，那么一个进程最多只能创建 300 个左右的线程。&lt;/li&gt;
&lt;li&gt;64 位系统，用户态的虚拟空间大到有 128T，&lt;strong&gt;理论上不会受虚拟内存大小的限制&lt;/strong&gt;，&lt;strong&gt;而会受系统的参数或性能限制&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;总结：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;一个程序至少有一个进程, 一个进程至少有一个线程&lt;/strong&gt;。hh&lt;/li&gt;
&lt;li&gt;进程在执行过程中&lt;strong&gt;拥有独立的内存单元&lt;/strong&gt;，而&lt;strong&gt;多个线程共享内存。同一进程内的线程共享内存和文件，因此它们之间相互通信无须调用内核&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;引入线程的好处：&lt;/strong&gt; 线程快！&lt;strong&gt;创建、终止、切换&lt;/strong&gt;都很快！&lt;strong&gt;虽然线程拥有单独的程序运行入口，出口，但不能独立执行&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;https://s3-us-west-2.amazonaws.com/secure.notion-static.com/b7d61d94-75ff-40c5-b659-fe1ba3852f98/Untitled.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Untitled&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://s3-us-west-2.amazonaws.com/secure.notion-static.com/48e7525f-9f6b-4737-bb87-380664d2d6c9/Untitled.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Untitled&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;(1)用户级上下文: &lt;strong&gt;正文、数据、用户堆栈以及共享存储区&lt;/strong&gt;；&lt;/p&gt;
&lt;p&gt;(2)寄存器上下文: 通用寄存器、&lt;strong&gt;程序寄存器(IP)、处理器状态寄存器(EFLAGS)&lt;/strong&gt;、&lt;strong&gt;栈指针(ESP)&lt;/strong&gt;；&lt;/p&gt;
&lt;p&gt;(3)系统级上下文: &lt;strong&gt;进程控制块task_struct、内存管理信息(mm_struct、vm_area_struct、pgd、pte)、内核栈&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;协程和线程&#34;&gt;协程和线程
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;一个线程可以多个协程，一个进程也可以单独拥有多个协程。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;线程进程都是同步机制，而&lt;strong&gt;协程则是异步&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;协程能&lt;strong&gt;保留上一次调用时的状态，每次过程重入时&lt;/strong&gt;，就相当于进入上一次调用的状态。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;线程是抢占式，而协程是非抢占式的&lt;/strong&gt;，所以需要用户自己释放使用权来切换到其他协程，因此同一时间其&lt;strong&gt;实只有一个协程拥有运行权&lt;/strong&gt;，相当于单线程的能力。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;协程并不是取代线程, 而且抽象于线程之上, 线程是被分割的CPU资源, 协程是组织好的代码流程, &lt;strong&gt;协程需要线程来承载运行, 线程是协程的资源&lt;/strong&gt;, 但&lt;strong&gt;协程不会直接使用线程, 协程直接利用的是执行器(Interceptor),&lt;/strong&gt; 执行器可以关联任意线程或线程池, 可以使当前线程, UI线程, 或新建新程.。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;线程是协程的资源。&lt;strong&gt;协程通过Interceptor来间接使用线程这个资源。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;22-进程有哪些状态转换条件是什么&#34;&gt;2.2 进程有哪些状态，转换条件是什么？
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;就绪状态&lt;/strong&gt;：进程获得了除CPU之外的一切所需资源&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;运行状态&lt;/strong&gt;：一个CPU的一个核只能有一个进程处于运行状态。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;阻塞状态&lt;/strong&gt;，又称等待状态：进程需要其他资源或正在等待某一事件发生而暂停运行。如&lt;strong&gt;等待某资源为可用&lt;/strong&gt;（不包括处理机）或等待输入/输出完成。即使处理机空闲，该进程也不能运行。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://uk-1259555870.cos.eu-frankfurt.myqcloud.com/20200218102010.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意区别就绪状态和等待状态：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;就绪状态是指进程仅缺少处理机，只要获得处理机资源就立即执行；&lt;strong&gt;而&lt;/strong&gt;等待状态是指进程需要其他资源（除了处理机）或等待某一事件。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sleep()函数和wait()函数的区别&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;（1）属于不同的两个类，&lt;strong&gt;sleep()方法是线程类（Thread）的静态方法，wait()方法是Object类里的方法&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;（2）&lt;strong&gt;sleep()方法不会释放锁&lt;/strong&gt;，&lt;strong&gt;wait()方法释放对象锁&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;（3）&lt;strong&gt;sleep()方法可以在任何地方使用&lt;/strong&gt;，wait()方法则只能在&lt;strong&gt;同步方法或同步块&lt;/strong&gt;中使用。&lt;/p&gt;
&lt;p&gt;（4）&lt;strong&gt;sleep()使线程进入阻塞状态（线程睡眠）&lt;/strong&gt;，wait()方法使线程进入等待队列（线程挂起），也就是阻塞类别不同。&lt;/p&gt;
&lt;p&gt;join()方法： join()方法使调用该方法的线程在此之前执行完毕，&lt;strong&gt;也就是等待该方法的线程执行完毕后再往下继续执行&lt;/strong&gt;。注意该方法也需要捕捉异常。&lt;/p&gt;
&lt;p&gt;yield()方法:该方法与sleep()类似，都是可以让当前正在运行的线程暂停，&lt;strong&gt;区别在于yield()方法不会阻塞该线程，它只是将线程转换成就绪状态&lt;/strong&gt;，让系统的调度器重新调度一次，&lt;strong&gt;并且yield()方法只能让优先级相同或许更高的线程有执行的机会&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&#34;23-进程间通信&#34;&gt;2.3 进程间通信
&lt;/h2&gt;&lt;p&gt;IPC(Inter process communication)问题，主要是指&lt;strong&gt;进程间交换数据的方式&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;进程是相互独立的，&lt;strong&gt;并&lt;/strong&gt;不需要条件变量、互斥锁这些机制，要锁也是文件锁这种大锁&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;而线程需要互斥锁的原因是：&lt;strong&gt;线程之间的资源室共享的，需要程序员来完成变量级别的同步。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;进程间通信分为&lt;strong&gt;低级通信&lt;/strong&gt;和&lt;strong&gt;高级通信&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;低级通信：&lt;strong&gt;信号量&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;高级通信：
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;管道&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;消息队列&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;共享内存&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;信号 套接字&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;管道pipe&#34;&gt;管道(pipe)
&lt;/h3&gt;&lt;p&gt;管道是一种&lt;strong&gt;半双工的通信方式，数据只能单向流动&lt;/strong&gt;，而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;管道&lt;/strong&gt;是指用于&lt;strong&gt;连接一个读进程和一个写进程的一个共享文件&lt;/strong&gt;，&lt;strong&gt;又名pipe文件&lt;/strong&gt;，以&lt;strong&gt;字符流形式&lt;/strong&gt;将数据写入文件。&lt;/p&gt;
&lt;p&gt;管道分为&lt;strong&gt;无名管道&lt;/strong&gt;和&lt;strong&gt;有名管道&lt;/strong&gt;：匿名管道&lt;strong&gt;就是内核⾥⾯的⼀串缓存&lt;/strong&gt;。从管道的⼀段写⼊的数据，实际上是缓存在内核中的，另⼀端读取，也就是从内核中读取这段数据。另外，&lt;strong&gt;管道传输的数据是⽆格式的流且⼤⼩受限&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;对于匿名管道，它的通信范围是存在⽗⼦关系的进程&lt;/strong&gt;。因为管道没有实体，也就是没有管道⽂件，只能通过 fork 来复制⽗进程 fd ⽂件描述符，来达到通信的⽬的。&lt;/p&gt;
&lt;p&gt;另外，&lt;strong&gt;对于命名管道，它可以在不相关的进程间也能相互通信&lt;/strong&gt;。因为命令管道，提前创建了⼀个类型为管道的设备⽂件，在进程⾥只要使⽤这个设备⽂件，就可以相互通信。在 shell ⾥⾯执⾏ A | B 命令的时候，A 进程和 B 进程都是 shell 创建出来的⼦进程，&lt;strong&gt;A 和 B 之间不存在⽗⼦关系，它俩的⽗进程都是 shell。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;匿名管道是&lt;strong&gt;半双工的通信方式&lt;/strong&gt;，数据&lt;strong&gt;只能单向流动&lt;/strong&gt;，只能在父子进程中流通；&lt;/li&gt;
&lt;li&gt;有名管道也是半双工，&lt;strong&gt;但是&lt;/strong&gt;它&lt;strong&gt;允许无亲缘关系进程间通信&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;通信⽅式是效率低的，因此管道不适合进程间频繁地交换数据。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;所谓的管道，就是&lt;strong&gt;内核⾥⾯的⼀串缓存&lt;/strong&gt;。 &lt;strong&gt;读写效率低，因此管道不适合进程间频繁地交换数据。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;创建的⼦进程会复制⽗进程的⽂件描述符。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;对于匿名管道，它的通信范围是存在⽗⼦关系的进程&lt;/strong&gt;，&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;对于命名管道，它可以在不相关的进程间也能相互通信。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;不管是匿名管道还是命名管道，进程写⼊的数据都是缓存在内核中，另⼀个进程读取数据时候⾃然也是从内核中获取，同时通信数据都&lt;strong&gt;遵循先进先出原则&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;消息队列messagequeue&#34;&gt;消息队列(messagequeue)
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;消息队列是由消息的链表，存放在内核中并由消息队列标识符标识&lt;/strong&gt;，消息体是⽤户⾃定义的数据类型。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;消息队列&lt;/strong&gt;指的是&lt;strong&gt;进程间的数据交换是以格式化的消息(Message)为单位的&lt;/strong&gt;，再由消息组成的链表，形成队列。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;消息队列是保存在内核中的消息链表&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;⼀：通信不及时&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;⼆：消息也有⼤⼩限制&lt;/strong&gt;，这同样也是消息队列通信不⾜的点，&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;三：消息队列通信过程中，存在⽤户态与内核态之间的数据拷⻉开销&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;消息队列不适合⽐较⼤数据的传输&lt;/strong&gt;，因为在内核中每个消息体都有⼀个&lt;strong&gt;最⼤⻓度的限制&lt;/strong&gt;，同时所有队列所包含的全部消息体的总⻓度也是有上限。&lt;/p&gt;
&lt;h3 id=&#34;共享内存shared-memory&#34;&gt;共享内存(shared memory)
&lt;/h3&gt;&lt;p&gt;共享内存的机制，就是&lt;strong&gt;拿出⼀块虚拟地址空间来&lt;/strong&gt;，&lt;strong&gt;映射到相同的物理内存中&lt;/strong&gt;，这段共享内存&lt;strong&gt;由一个进程创建，但多个进程都可以访问&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;共享内存是最快的 IPC 方式&lt;/strong&gt;，它是针对其他进程间通信方式运行效率低而专门设计的。&lt;/p&gt;
&lt;p&gt;⽤了共享内存通信⽅式，带来新的问题，那就是&lt;strong&gt;如果多个进程同时修改同⼀个共享内存，很有可能就冲突&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;例如两个进程都同时写⼀个地址，那先写的那个进程会发现内容被别⼈覆盖了。&lt;/p&gt;
&lt;p&gt;它往往与其他通信机制，&lt;strong&gt;如信号量&lt;/strong&gt;，配合使用，来实现&lt;strong&gt;进程间的同步和通信&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;共享内存&lt;/strong&gt;指在通信的进程之间存在&lt;strong&gt;一块可直接访问的共享空间&lt;/strong&gt;，通过对这片&lt;strong&gt;共享空间进行写/读操&lt;/strong&gt;作实现进程之间的信息交换。&lt;/p&gt;
&lt;p&gt;在对共享空间进行写/读操作时，需要使用同步互斥工具**（如 P操作、V操作）**，对共享空间的写/读进行控制。&lt;/p&gt;
&lt;p&gt;P（S）：①将信号量S的值减1，即S=S-1；&lt;/p&gt;
&lt;p&gt;②&lt;strong&gt;如果S&amp;gt;=0，则该进程继续执行&lt;/strong&gt;；&lt;strong&gt;否则该进程置为等待状态，排入等待队列&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;V（S）：①将信号量S的值加1，即S=S+1；&lt;/p&gt;
&lt;p&gt;②如果S&amp;gt;0，则该进程继续执行；&lt;strong&gt;否则&lt;/strong&gt;释放&lt;strong&gt;队列中第一个等待信号量的进程&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;信号量semaphore&#34;&gt;信号量(semaphore)
&lt;/h3&gt;&lt;p&gt;信号量是一个计数器，&lt;strong&gt;可以用来控制多个进程对共享资源的访问&lt;/strong&gt;。它常作为一种锁机制，防止某进程正在访问共享资源时，其他进程也访问该资源。因此，主要作为进程间以及同一进程内不同线程之间的同步手段。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;信号量&lt;/strong&gt;是一个&lt;strong&gt;计数器&lt;/strong&gt;，防止多个进程将资源拿光，防止某进程正在访问共享资源时，其他进程也访问该资源。&lt;/p&gt;
&lt;p&gt;为了防⽌多进程竞争共享资源，⽽造成的数据错乱，所以需要保护机制，使得共享 的资源，在任意时刻只能被⼀个进程访问。正好，&lt;strong&gt;信号量&lt;/strong&gt;就实现了这⼀保护机制。&lt;/p&gt;
&lt;p&gt;⼀个是 &lt;strong&gt;P&lt;/strong&gt; &lt;strong&gt;操作&lt;/strong&gt;，这个操作会把信号量减去 1，相减后如果信号量 &amp;lt; 0，则表明资源已被占⽤，进程需&lt;/p&gt;
&lt;p&gt;阻塞等待；&lt;strong&gt;相减后如果信号量 &amp;gt;= 0，则表明还有资源可使⽤&lt;/strong&gt;，进程可正常继续执⾏。&lt;/p&gt;
&lt;p&gt;另⼀个是 &lt;strong&gt;V&lt;/strong&gt; &lt;strong&gt;操作&lt;/strong&gt;，这个操作会把信号量加上 1，&lt;strong&gt;相加后如果信号量 &amp;lt;= 0，则表明当前有阻塞中的进程&lt;/strong&gt;，于是会将该进程唤醒运⾏；相加后如果信号量 &amp;gt; 0，则表明当前没有阻塞中的进程；&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;信号初始化为 1 ，就代表着是互斥信号量&lt;/strong&gt;，它可以&lt;strong&gt;保证共享内存在任何时刻只有⼀个进程在访问，这就很好的保护了共享内存。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;信号初始化为 0 ，就代表着是同步信号量&lt;/strong&gt;，它可以&lt;strong&gt;保证进程 A 应在进程 B 之前执⾏&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;信号-sinal&#34;&gt;信号 (sinal)
&lt;/h3&gt;&lt;p&gt;**信号是一种比较复杂的通信方式，用于通知接收进程某个事件已经发生。**上⾯说的进程间通信，都是常规状态下的⼯作模式。&lt;/p&gt;
&lt;p&gt;**对于异常情况下的⼯作模式，就需要⽤「信号」的⽅**式来通知进程。&lt;strong&gt;信号是进程间通信机制中唯⼀的异步通信机制，&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1.执⾏默认操作&lt;/strong&gt;。Linux 对每种信号都规定了默认操作，例如，上⾯列表中的 &lt;strong&gt;SIGTERM 信号，就是终⽌进程的意思&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2.捕捉信号&lt;/strong&gt;。我们可以为信号定义⼀个信号处理函数。当信号发⽣时，我们就执⾏相应的信号处理函数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3.忽略信号&lt;/strong&gt;。当我们不希望处理某些信号的时候，&lt;strong&gt;就可以忽略该信号，不做任何处理&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;有两个信号是应⽤进程⽆法捕捉和忽略的，即 &lt;strong&gt;SIGKILL 和 SEGSTOP&lt;/strong&gt; ，它们⽤于在任何时候中断或结束某⼀进程。&lt;/p&gt;
&lt;h3 id=&#34;socket套接字&#34;&gt;Socket(套接字)
&lt;/h3&gt;&lt;p&gt;套接口也是一种进程间通信机制，与其他通信机制不同的是，它可用于不同设备及其间的进程通信。&lt;/p&gt;
&lt;p&gt;那要想&lt;strong&gt;跨⽹络与不同主机上的进程&lt;/strong&gt;之间通信，就需要 &lt;strong&gt;Socket&lt;/strong&gt; &lt;strong&gt;通信了。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;服务端和客户端初始化 &lt;code&gt;socket&lt;/code&gt;，得到文件描述符；&lt;/li&gt;
&lt;li&gt;服务端调用 &lt;code&gt;bind&lt;/code&gt;，绑定 IP 地址和端口，协议;&lt;/li&gt;
&lt;li&gt;服务端调用 &lt;code&gt;listen&lt;/code&gt;，进行监听；&lt;/li&gt;
&lt;li&gt;服务端调用 &lt;code&gt;accept&lt;/code&gt;，等待客户端连接；&lt;/li&gt;
&lt;li&gt;客户端调用 &lt;code&gt;connect&lt;/code&gt;，向服务器端的地址和端口发起连接请求；&lt;/li&gt;
&lt;li&gt;服务端 &lt;code&gt;accept&lt;/code&gt; 返回用于传输的 &lt;code&gt;socket&lt;/code&gt; 的文件描述符；&lt;/li&gt;
&lt;li&gt;客户端调用 &lt;code&gt;write&lt;/code&gt; 写入数据；服务端调用 &lt;code&gt;read&lt;/code&gt; 读取数据；&lt;/li&gt;
&lt;li&gt;客户端断开连接时，会调用 &lt;code&gt;close&lt;/code&gt;，那么服务端 &lt;code&gt;read&lt;/code&gt; 读取数据的时候，就会读取到了 &lt;code&gt;EOF&lt;/code&gt;，待处理完数据后，服务端调用 &lt;code&gt;close&lt;/code&gt;，表示连接关闭。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;C:%5cUsers%5cadmin%5cAppData%5cRoaming%5cTypora%5ctypora-user-images%5cimage-20210706201229857.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;image-20210706201229857&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;C:%5cUsers%5cadmin%5cAppData%5cRoaming%5cTypora%5ctypora-user-images%5cimage-20210706202328078.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;h2 id=&#34;24-进程间同步通信主要为了同步&#34;&gt;2.4 进程间同步（通信主要为了同步）
&lt;/h2&gt;&lt;p&gt;多进程虽然提高了系统资源利用率和吞吐量，但是由于&lt;strong&gt;进程的异步性可能造成系统的混乱&lt;/strong&gt;。&lt;strong&gt;进程同步&lt;/strong&gt;的任务就是&lt;strong&gt;对多个相关进程在执行顺序上进行协调。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;进程是相互独立的，所以进程间通信大多不需要锁，需要的锁也是文件锁之类的“大锁”，并不需要条件变量、互斥锁这些机制来同步&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;25-线程间同步和通信&#34;&gt;2.5 线程间同步和通信
&lt;/h2&gt;&lt;p&gt;&lt;img src=&#34;C:%5cUsers%5cadmin%5cAppData%5cRoaming%5cTypora%5ctypora-user-images%5cimage-20210706202429960.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;由于线程间的资源可以共享，同步的方式就会更加细致：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;互斥量&lt;/strong&gt; 互斥与临界区很相似，但是使用时相对复杂一些（&lt;strong&gt;互斥量为内核对象）&lt;/strong&gt;，&lt;strong&gt;不仅可以在同一应用程序的线程间实现同步，&lt;strong&gt;还可以在不同的进程间实现同步，从而实现资源的安全共享。 由于&lt;/strong&gt;互斥量是内核对象&lt;/strong&gt;，因此其可以进行进程间通信，同时还具有一个很好的特性，就是在进程间通信时完美的解决了“遗弃”问题&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;信号量&lt;/strong&gt;，只能用于一个资源的互斥访问，&lt;strong&gt;不能实现多个资源的多线程&lt;/strong&gt;互斥问题。信号量的用法和互斥的用法很相似，不同的是它&lt;strong&gt;可以同一时刻允许多个线程访问同一个资源&lt;/strong&gt;，PV操作。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;读写锁&lt;/strong&gt;，可以&lt;strong&gt;被多个读者拥有&lt;/strong&gt;，但是&lt;strong&gt;只能被一个写者拥有的锁&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;条件变量&lt;/strong&gt;，线程 A 等待某个条件并挂起，&lt;strong&gt;直到线程 B 设置了这个条件&lt;/strong&gt;，&lt;strong&gt;并通知条件变量，然后线程 A 被唤醒&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;原子操作PV：&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;通道：&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;事件：&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;1-临界区&#34;&gt;1. 临界区
&lt;/h3&gt;&lt;p&gt;每个进程中&lt;strong&gt;访问临界资源的那段程序称为临界区&lt;/strong&gt;，&lt;strong&gt;一次仅允许一个进程使用的资源称为临界资源。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决冲突的办法：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果有若干进程要求进入空闲的临界区，&lt;strong&gt;一次仅允许一个进程进入&lt;/strong&gt;，如已有进程进入自己的临界区，则其它所有试图进入临界区的进程必须等待；&lt;/li&gt;
&lt;li&gt;进入临界区的进程要在&lt;strong&gt;有限时间内退出&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;如果进程&lt;strong&gt;不能进入自己的临界区，则应让出CPU&lt;/strong&gt;，&lt;strong&gt;避免进程出现“忙等”现象。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;临界区&lt;/em&gt;指的是一个访问共用资源（例如：共用设备或是共用存储器）的程序片段，而这些共用资源又无法同时被多个线程访问的特性&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;任何想进⼊临界区的线程，必须先执⾏加锁操作。&lt;/strong&gt; &lt;strong&gt;若加锁操作顺利通过&lt;/strong&gt;，则线程可进⼊临界区；&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;在完成对临界资源的访问后再执⾏解锁操作&lt;/strong&gt;，以释放该临界资源。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;互斥锁&lt;/strong&gt;加锁失败后，线程会&lt;strong&gt;释放 CPU&lt;/strong&gt; ，给其他线程；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自旋锁&lt;/strong&gt;加锁失败后，线程会&lt;strong&gt;忙等待&lt;/strong&gt;，直到它拿到锁；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当获取不到锁时，&lt;strong&gt;线程就会⼀直 wile 循环&lt;/strong&gt;，不做任何事情，所以就被称为**「忙等待锁」，也被称为⾃旋锁（spin lock）**。&lt;/p&gt;
&lt;p&gt;既然不想⾃旋，那当没获取到锁的时候，就把当前线程放⼊到锁的等待队列，然后执⾏调度程序，把 CPU让给其他线程执⾏。&lt;strong&gt;（互斥锁）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;临界区对应着一个&lt;strong&gt;CcriticalSection对象&lt;/strong&gt;。当线程需要访问保护数据时，调用&lt;strong&gt;EnterCriticalSection&lt;/strong&gt;函数；当对保护数据的操作完成之后，&lt;strong&gt;调用LeaveCriticalSection函数释放对临界区对象的拥有权&lt;/strong&gt;，以使另一个线程可以夺取临界区对象并访问受保护的数据。&lt;/p&gt;
&lt;p&gt;关键段对象会记录拥有该对象的线程句柄即其具有“线程所有权”概念，即进入代码段的线程在leave之前，可以重复进入关键代码区域。所以关键段可以用于线程间的互斥，但不可以用于同步（同步需要在一个线程进入，在另一个线程leave）&lt;/p&gt;
&lt;h3 id=&#34;2-互斥量&#34;&gt;2. &lt;strong&gt;互斥量&lt;/strong&gt;
&lt;/h3&gt;&lt;p&gt;互斥锁(又名互斥量）强调的是&lt;strong&gt;资源的访问互斥&lt;/strong&gt;：互斥锁是用在多线程多任务互斥的，一个线程占用了某一个资源，那么别的线程就无法访问，&lt;strong&gt;直到这个线程unlock&lt;/strong&gt;，其他的线程才开始可以利用这个资源。&lt;/p&gt;
&lt;p&gt;比如&lt;strong&gt;对全局变量的访问，有时要加锁，操作完了，在解锁&lt;/strong&gt;。&lt;strong&gt;有的时候锁和信号量会同时使用的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;也就是说，信号量不一定是锁定某一个资源，而是流程上的概念，比如：有A,B两个线程，B线程要等A线程完成某一任务以后再进行自己下面的步骤，&lt;strong&gt;这个任务并不一定是锁定某一资源，还可以是进行一些计算或者数据处理之类&lt;/strong&gt;。而线程互斥量则是“锁住某一资源”的概念，在锁定期间内，其他线程无法对被保护的数据进行操作。在有些情况下两者可以互换。&lt;/p&gt;
&lt;p&gt;在linux下, 线程的互斥量数据类型是pthread_mutex_t. 在使用前, 要对它进行初始化:&lt;/p&gt;
&lt;p&gt;对于静态分配的互斥量, 可以把它设置为PTHREAD_MUTEX_INITIALIZER, 或者调用pthread_mutex_init.&lt;/p&gt;
&lt;p&gt;对于动态分配的互斥量, &lt;strong&gt;在申请内存(malloc)之后, 通过&lt;/strong&gt;pthread_mutex_init&lt;strong&gt;进行初始化, 并且在释放内存(free)前需要调用pthread_mutex_destroy&lt;/strong&gt;.采用互斥对象机制&lt;/p&gt;
&lt;p&gt;只有拥有了互斥对象的线程才有访问资源的权限。&lt;strong&gt;因为互斥对象只有一个&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;所以可以保证&lt;strong&gt;公共资源不会被多个线程同时访问&lt;/strong&gt;，互斥量本质上是一把锁，在访问共享资源前对互斥量进行加锁，在访问完成后释放互斥量上的锁。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;C:%5cUsers%5cadmin%5cAppData%5cRoaming%5cTypora%5ctypora-user-images%5cimage-20210706203300387.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;h3 id=&#34;哲学家进餐问题&#34;&gt;哲学家进餐问题：
&lt;/h3&gt;&lt;p&gt;拿起叉⼦⽤ P 操作，代表有叉⼦就直接⽤，没有叉⼦时就等待其他哲学家放回叉⼦。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.jsdelivr.net/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/24-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%B8%80%E7%A4%BA%E4%BE%8B.jpg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;不过，这种解法存在一个极端的问题：&lt;strong&gt;假设五位哲学家同时拿起左边的叉子，桌面上就没有叉子了， 这样就没有人能够拿到他们右边的叉子，也就说每一位哲学家都会在 &lt;code&gt;P(fork[(i + 1) % N ])&lt;/code&gt; 这条语句阻塞了，很明显这发生了死锁的现象&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.jsdelivr.net/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/26-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%BA%8C%E7%A4%BA%E4%BE%8B.jpg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;上⾯程序中的互斥信号量的作⽤就在于，&lt;strong&gt;只要有⼀个哲学家进⼊了「临界区」，也就是准备要拿叉⼦时，&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;其他哲学家都不能动，只有这位哲学家⽤完叉⼦了，才能轮到下⼀个哲学家进餐&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;会导致只能允许⼀个哲学家就餐，那么我们就不⽤它。&lt;/p&gt;
&lt;p&gt;⽅案⼀的问题在于，&lt;strong&gt;会出现所有哲学家同时拿左边⼑叉的可能性，那我们就避免哲学家可以同时拿&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;左边的⼑叉，采⽤分⽀结构，根据哲学家的编号的不同，⽽采取不同的动作。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;即让偶数编号的哲学家「先拿左边的叉⼦后拿右边的叉⼦」，奇数编号的哲学家「先拿右边的叉⼦后拿左边的叉⼦」。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 P 操作时，根据哲学家的编号不同，拿起左右两边叉⼦的顺序不同。另外，V 操作是不需要分⽀的，因为 &lt;strong&gt;V 操作是不会阻塞的。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;方案三即不会出现死锁，也可以两人同时进餐&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.jsdelivr.net/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/28-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%B8%89%E7%A4%BA%E4%BE%8B.jpg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;方案四&lt;/p&gt;
&lt;p&gt;在这里再提出另外一种可行的解决方案，我们&lt;strong&gt;用一个数组 state 来记录每一位哲学家的三个状态，分别是在进餐状态、思考状态、饥饿状态（正在试图拿叉子）。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;那么，&lt;strong&gt;一个哲学家只有在两个邻居都没有进餐时，才可以进入进餐状态。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第 &lt;code&gt;i&lt;/code&gt; 个哲学家的左邻右舍，则由宏 &lt;code&gt;LEFT&lt;/code&gt; 和 &lt;code&gt;RIGHT&lt;/code&gt; 定义：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;LEFT&lt;/em&gt; : ( i + 5 - 1 ) % 5&lt;/li&gt;
&lt;li&gt;&lt;em&gt;RIGHT&lt;/em&gt; : ( i + 1 ) % 5&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;比如 i 为 2，则 &lt;code&gt;LEFT&lt;/code&gt; 为 1，&lt;code&gt;RIGHT&lt;/code&gt; 为 3。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.jsdelivr.net/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/30-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E5%9B%9B%E7%A4%BA%E4%BE%8B.jpg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.jsdelivr.net/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/21-%E7%94%9F%E4%BA%A7%E8%80%85%E6%B6%88%E8%B4%B9%E8%80%85%E4%BB%A3%E7%A0%81%E7%A4%BA%E4%BE%8B.jpg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;h3 id=&#34;3信号量&#34;&gt;3.信号量
&lt;/h3&gt;&lt;p&gt;信号量允许同一时刻&lt;strong&gt;多个线程访问同一个资源，但是要控制最大线程数量&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;C:%5cUsers%5cadmin%5cAppData%5cRoaming%5cTypora%5ctypora-user-images%5cimage-20210706203212055.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Ctrl+C 产生 &lt;code&gt;SIGINT&lt;/code&gt; 信号&lt;/strong&gt;，表示终止该进程；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ctrl+Z 产生 &lt;code&gt;SIGTSTP&lt;/code&gt; 信号&lt;/strong&gt;，表示停止该进程，但还未结束；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对于两个并发线程，互斥信号量的值仅取 1、0 和 -1 三个值，分别表示：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果互斥信号量为 1，表示没有线程进入临界区；&lt;/li&gt;
&lt;li&gt;如果互斥信号量为 0，表示有一个线程进入临界区；&lt;/li&gt;
&lt;li&gt;如果互斥信号量为 -1，表示一个线程进入临界区，&lt;strong&gt;另一个线程等待进入。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通过互斥信号量的方式，就能保证临界区任何时刻只有一个线程在执行，就达到了互斥的效果。&lt;/p&gt;
&lt;p&gt;如果进程在后台运行，可以通过 &lt;code&gt;kill&lt;/code&gt; 命令的方式给进程发送信号，但前提需要知道运行中的进程 PID 号，例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;kill -9 1050 ，表示给 PID 为 1050 的进程发送 &lt;code&gt;SIGKILL&lt;/code&gt; 信号，用来立即结束该进程；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以，信号事件的来源主要有&lt;strong&gt;硬件来源（如键盘 Cltr+C ）和软件来源（如 kill 命令）&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;4-信号&#34;&gt;4. 信号
&lt;/h3&gt;&lt;p&gt;信号是进程间通信机制中&lt;strong&gt;唯一的异步通信机制&lt;/strong&gt;，因为可以在任何时候发送信号给某一进程，一旦有信号产生，我们就有下面这几种，用户进程对信号的处理方式。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1.执行默认操作&lt;/strong&gt;。Linux 对每种信号都规定了默认操作，例如，上面列表中的 SIGTERM 信号，就是终止进程的意思。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2.捕捉信号&lt;/strong&gt;。我们可以为信号定义一个信号处理函数。当信号发生时，我们就执行相应的信号处理函数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3.忽略信号&lt;/strong&gt;。当我们不希望处理某些信号的时候，就可以忽略该信号，不做任何处理。&lt;/p&gt;
&lt;p&gt;有两个信号是应用进程无法捕捉和忽略的，&lt;strong&gt;即 &lt;code&gt;SIGKILL&lt;/code&gt; 和 &lt;code&gt;SEGSTOP&lt;/code&gt;&lt;/strong&gt;，它们用于在任何时候中断或结束某一进程&lt;/p&gt;
&lt;h3 id=&#34;5-读写锁&#34;&gt;5. 读写锁
&lt;/h3&gt;&lt;p&gt;读写锁与互斥量类似，不过&lt;strong&gt;读写锁允许更高的并行性&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;互斥量要么是锁住状态要么是不加锁状态，而且一次只有一个线程可以对其加锁。&lt;/p&gt;
&lt;p&gt;读写锁可以由三种状态：&lt;strong&gt;读模式下加锁状态、写模式下加锁状态、不加锁状态&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;先进先出，&lt;strong&gt;一次只有一个线程可以占有写模式的读写锁，但是多个线程可以同时占有读模式的读写锁。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;既然&lt;strong&gt;读者优先策略和写者优先策略&lt;/strong&gt;都会造成饥饿的现象，那么我们就来实现。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.jsdelivr.net/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/34-%E8%AF%BB%E8%80%85%E5%86%99%E8%80%85-%E6%96%B9%E6%A1%88%E4%B8%89%E7%A4%BA%E4%BE%8B.jpg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;h3 id=&#34;6-条件变量condition&#34;&gt;6. 条件变量(condition)
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;条件变量是用来等待而不是用来上锁的&lt;/strong&gt;，条件变量是利用线程间&lt;strong&gt;共享的全局变量&lt;/strong&gt;进行同步的一种机制，条件变量与互斥量一起使用时，允许线程等待特定的条件发生。条件变量与互斥量一起使用时，&lt;strong&gt;允许线程以无竞争的方式等待特定的条件发生&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;条件本身是由互斥量保护的。&lt;strong&gt;线程在改变条件状态前必须首先锁住互斥量，其它线程在获得互斥量之前不会察觉到这种改变，因此必须锁定互斥量以后才能计算条件&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;条件的检测是在互斥锁的保护下进行的&lt;/strong&gt;。&lt;strong&gt;如果一个条件为假，一个线程自动阻塞，并释放等待状态改变的互斥锁。如果另一个线程改变了条件，它发信号给关联的条件变量，唤醒一个或多个等待它的线程，重新获得互斥锁，重新评价条件&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;如果两进程共享可读写的内存，条件变量可以被用来实现这两进程间的线程同步。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;如果线程正在等待共享数据内某个条件出现，那会发生什么呢？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;代码可以反复对互斥对象锁定和解锁， 以检查值的任何变化&lt;/strong&gt;。同时，&lt;strong&gt;还要快速将互斥对象解锁&lt;/strong&gt;，以便其它线程能够进行任何必需的更改。需要一种方法以唤醒因等待满足特定条件而睡眠的线程。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;7-事件&#34;&gt;&lt;strong&gt;7. 事件&lt;/strong&gt;
&lt;/h3&gt;&lt;p&gt;通过&lt;strong&gt;通知操作的方式&lt;/strong&gt;来保持线程的同步&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;事件是内核对象&lt;/strong&gt;，可以解决线程间同步问题，&lt;strong&gt;因此也能解决互斥问题&lt;/strong&gt;。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;事件机制，则允许一个线程在处理完一个任务后，主动唤醒另外一个线程执行任务。
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;比如在某些网络应用程序中，一个线程如A负责侦听通信端口，另外一个线程B负责更新用户数据，利用事件机制，
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;则线程A可以通知线程B何时更新用户数据。每个Cevent对象可以有两种状态：有信号状态和无信号状态。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;1、使用全局变量&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;主要由于多个线程可能更改全局变量，因此全局变量&lt;strong&gt;最好声明为volatile&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2、使用消息实现通信&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在Windows程序设计中，每一个线程都可以拥有自己的消息队列（UI线程默认自带消息队列和消息循环，工作线程需要手动实现消息循环），因此可以采用消息进行线程间通信sendMessage，postMessage。&lt;/p&gt;
&lt;h2 id=&#34;27-线程的分类&#34;&gt;2.7 线程的分类
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;内核级线程&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;这类线程依赖于内核，又称为&lt;strong&gt;内核支持的线程&lt;/strong&gt;或轻量级进程。&lt;/p&gt;
&lt;p&gt;无论是在用户程序中的线程还是系统进程中的线程，&lt;strong&gt;它们的创建、撤销和切换都由内核实现&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;比如英特尔i5-8250U是4核8线程，这里的线程就是内核级线程。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;内核线程是由操作系统管理的，线程对应的&lt;/strong&gt; &lt;strong&gt;TCB&lt;/strong&gt; &lt;strong&gt;⾃然是放在操作系统⾥的，这样线程的创建、终⽌和管理&lt;/strong&gt;都是由操作系统负责。&lt;/p&gt;
&lt;p&gt;内核线程的&lt;strong&gt;优点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在⼀个进程当中，如果某个内核线程发起系统调⽤⽽被阻塞，并不会影响其他内核线程的运⾏；&lt;/li&gt;
&lt;li&gt;分配给线程，多线程的进程获得更多的 CPU 运⾏时间；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;内核线程的&lt;strong&gt;缺点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在⽀持内核线程的操作系统中，&lt;strong&gt;由内核来维护进程和线程的上下⽂信息，如 PCB 和 TCB&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;线程的创建、终⽌和切换都是通过系统调⽤的⽅式来进⾏，因此对于系统来说，&lt;strong&gt;系统开销⽐较⼤&lt;/strong&gt;；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;用户级线程&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;它仅存在于用户级中，这种线程是&lt;strong&gt;不依赖于操作系统核心&lt;/strong&gt;的。应用进程利用&lt;strong&gt;线程库来完成其创建和管理&lt;/strong&gt;，速度比较快，&lt;strong&gt;操作系统内核无法感知用户级线程的存在&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;⽤户线程是基于⽤户态的线程管理库来实现的，那么&lt;strong&gt;线程控制块（Thread Control Block, TCB）&lt;/strong&gt; 也是在库⾥⾯来实现的，对于操作系统⽽⾔是看不到这个 TCB 的，它只能看到整个进程的 PCB。&lt;/p&gt;
&lt;p&gt;⽤户线程的&lt;strong&gt;优点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每个进程都需要有它私有的线程控制块（TCB）列表，⽤来跟踪记录它各个线程状态信息（&lt;strong&gt;PC、栈指针、寄存器&lt;/strong&gt;），TCB 由⽤户级线程库函数来维护，&lt;strong&gt;可⽤于不⽀持线程技术的操作系统；&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;⽤户线程的切换也是由线程库函数来完成的&lt;/strong&gt;，&lt;strong&gt;⽆需⽤户态与内核态的切换&lt;/strong&gt;，所以速度特别快；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;⽤户线程的&lt;strong&gt;缺点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;由于操作系统不参与用户级线程的调度&lt;/strong&gt;，&lt;strong&gt;如果⼀个线程发起了系统调⽤⽽阻塞&lt;/strong&gt;，那**进程所包含的⽤户线程都不能执⾏**了。&lt;/li&gt;
&lt;li&gt;当⼀个线程开始运⾏后，除⾮&lt;strong&gt;它主动地交出 CPU 的使⽤权&lt;/strong&gt;，&lt;strong&gt;否则它所在的进程当中的其他线程⽆法运⾏，因为⽤户态的线程没法打断当前运⾏中的线程&lt;/strong&gt;，它没有这个特权，只有操作系统才有，但是⽤户线程不是由操作系统管理的。&lt;/li&gt;
&lt;li&gt;由于时间⽚分配给进程，故与其他进程⽐，&lt;strong&gt;在多线程执⾏时，每个线程得到的时间⽚较少&lt;/strong&gt;，执⾏会⽐较慢；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;轻量级进程（Light-weight process，LWP）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;是内核⽀持的⽤户线程，⼀个进程可有⼀个或多个** &lt;strong&gt;LWP，每个&lt;/strong&gt; &lt;strong&gt;LWP&lt;/strong&gt; &lt;strong&gt;是跟内核线程⼀对⼀映射的，也就是&lt;/strong&gt; &lt;strong&gt;LWP&lt;/strong&gt; &lt;strong&gt;都是由⼀个内核线程⽀持。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;另外，&lt;strong&gt;LWP 只能由内核管理并像普通进程⼀样被调度&lt;/strong&gt;，Linux 内核是⽀持 LWP 的典型例⼦。&lt;/p&gt;
&lt;p&gt;在⼤多数系统中，&lt;strong&gt;LWP与普通进程的区别也在于它只有⼀个最⼩的执⾏上下⽂和调度程序所需的统计信息&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;⼀般来说，⼀个进程代表程序的⼀个实例，⽽ &lt;strong&gt;LWP 代表程序的执⾏线程.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;因为⼀个执⾏线程不像进程那样需要那么多状态信息，所以 LWP 也不带有这样的信息&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在 LWP 之上也是可以使⽤⽤户线程的，那么 LWP 与⽤户线程的对应关系就有三种：&lt;/p&gt;
&lt;p&gt;1 : 1 ，即⼀个 LWP 对应 ⼀个⽤户线程；&lt;/p&gt;
&lt;p&gt;N : 1 ，即⼀个 LWP 对应多个⽤户线程；&lt;/p&gt;
&lt;p&gt;M : N ，即多个 LMP 对应多个⽤户线程；&lt;/p&gt;
&lt;h2 id=&#34;28-线程池&#34;&gt;2.8 线程池
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;线程池就是提前创建若干个线程&lt;/strong&gt;，如果有任务需要处理，&lt;strong&gt;线程池里的线程就会处理任务&lt;/strong&gt;，&lt;strong&gt;处理完之后线程并不会被销毁，而是等待下一个任务&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;为了减少创建和销毁线程的次数，让每个线程可以多次使用，同时可根据系统情况&lt;strong&gt;调整执行&lt;/strong&gt;的线程数量，&lt;strong&gt;防止消耗过多&lt;a class=&#34;link&#34; href=&#34;https://so.csdn.net/so/search?q=%E5%86%85%E5%AD%98&amp;amp;spm=1001.2101.3001.7020&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;内存&lt;/a&gt;&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;由于&lt;strong&gt;创建和销毁线程都是消耗系统资源的，所以池化技术能提升性能&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;线程池的组成主要分为 3 个部分，这三部分配合工作就可以得到一个完整的线程池：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;任务&lt;a class=&#34;link&#34; href=&#34;https://so.csdn.net/so/search?q=%E9%98%9F%E5%88%97&amp;amp;spm=1001.2101.3001.7020&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;队列&lt;/a&gt;&lt;/strong&gt;，存储需要处理的任务，由工作的线程来处理这些任务
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;通过线程池提供的 API 函数，将一个待处理的任务添加到任务队列&lt;/strong&gt;，或者从任务队列中删除&lt;/li&gt;
&lt;li&gt;已处理的任务会被从任务队列中删除&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;线程池的使用者，也就是调用线程池函数往任务队列中添加任务的线程就是生产者线程&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;工作的线程&lt;/strong&gt;（任务队列任务的消费者） ，N个
&lt;ul&gt;
&lt;li&gt;1.线程池中维护了一定数量的工作线程，他们的作用是是不停的读任务队列，从里边取出任务并处理.&lt;/li&gt;
&lt;li&gt;2.&lt;strong&gt;工作的线程相当于是任务队列的消费者角色&lt;/strong&gt;，&lt;/li&gt;
&lt;li&gt;3.如果任务队列为空，&lt;strong&gt;工作的线程将会被阻塞 (使用条件变量 / 信号量阻塞)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;4.&lt;strong&gt;如果阻塞之后有了新的任务，由生产者将阻塞解除，工作线程开始工作&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;管理者线程&lt;/strong&gt;（不处理任务队列中的任务），1个
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;它的任务是周期性的对任务队列中的任务数量以及处于忙状态的工作线程个数进行检测&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;当任务过多的时候，可以适当的创建一些新的工作线程&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;当任务过少的时候，可以适当的销毁一些工作的线程&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;29-进程调度&#34;&gt;2.9 进程调度
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;批处理系统、分时系统和实时系统中，各采用哪几种进程（作业）调度算法？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;批处理系统常用调度算法：&lt;/p&gt;
&lt;p&gt;①、先来先服务：&lt;a class=&#34;link&#34; href=&#34;https://www.baidu.com/s?wd=FCFS&amp;amp;tn=SE_PcZhidaonwhc_ngpagmjz&amp;amp;rsv_dl=gh_pc_zhidao&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;FCFS&lt;/a&gt; ②、最短作业优先 ③、最短剩余时间优先 ④、响应比最高者优先&lt;/p&gt;
&lt;p&gt;分时系统调度算法：&lt;/p&gt;
&lt;p&gt;①、轮转调度 ②、优先级调度 ③、多级队列调度 ④、彩票调度&lt;/p&gt;
&lt;p&gt;实时系统调度算法：&lt;/p&gt;
&lt;p&gt;①、单比率调度 ②、限期调度 ③、最少裕度法&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;C:%5cUsers%5cadmin%5cAppData%5cRoaming%5cTypora%5ctypora-user-images%5cimage-20210706210454043.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;C:%5cUsers%5cadmin%5cAppData%5cRoaming%5cTypora%5ctypora-user-images%5cimage-20210706210530112.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;C:%5cUsers%5cadmin%5cAppData%5cRoaming%5cTypora%5ctypora-user-images%5cimage-20210706210539267.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;C:%5cUsers%5cadmin%5cAppData%5cRoaming%5cTypora%5ctypora-user-images%5cimage-20210706210553611.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;C:%5cUsers%5cadmin%5cAppData%5cRoaming%5cTypora%5ctypora-user-images%5cimage-20210706210600929.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;C:%5cUsers%5cadmin%5cAppData%5cRoaming%5cTypora%5ctypora-user-images%5cimage-20210706210615451.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;C:%5cUsers%5cadmin%5cAppData%5cRoaming%5cTypora%5ctypora-user-images%5cimage-20210706210635305.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;C:%5cUsers%5cadmin%5cAppData%5cRoaming%5cTypora%5ctypora-user-images%5cimage-20210706210654311.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;C:%5cUsers%5cadmin%5cAppData%5cRoaming%5cTypora%5ctypora-user-images%5cimage-20210706210713301.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;h2 id=&#34;210-多线程冲突了怎么办&#34;&gt;2.10 多线程冲突了怎么办
&lt;/h2&gt;&lt;p&gt;由于多线程执行操作共享变量的这段代码可能会导致竞争状态，因此我们将此段代码称为&lt;strong&gt;临界区（&lt;em&gt;critical section&lt;/em&gt;），它是访问共享资源的代码片段，一定不能给多线程同时执行。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们希望这段代码是&lt;strong&gt;互斥（&lt;em&gt;mutualexclusion&lt;/em&gt;）的，也就说保证一个线程在临界区执行时，其他线程应该被阻止进入临界区&lt;/strong&gt;，说白了，就是这段代码执行过程中，最多只能出现一个线程。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;锁&lt;/em&gt;：加锁、解锁操作；&lt;/li&gt;
&lt;li&gt;&lt;em&gt;信号量&lt;/em&gt;：P、V 操作；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;根据锁的实现不同，可以分为「忙等待锁」和「无忙等待锁」。&lt;/p&gt;
&lt;p&gt;那什么是原子操作呢？&lt;strong&gt;原子操作就是要么全部执行，要么都不执行，不能出现执行到一半的中间状态&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.jsdelivr.net/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/17-%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9FPV%E7%AE%97%E6%B3%95%E6%8F%8F%E8%BF%B0.jpg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;PV 操作的算法描述&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;多线程，为什么要用多线程&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;提高系统的并发性：多线程可以使系统同时执行多个任务，提高系统的并发性和响应能力。当一个线程被阻塞或等待某个操作完成时，其他线程可以继续执行，充分利用处理器的资源，提高系统整体的吞吐量。&lt;/li&gt;
&lt;li&gt;改善用户体验：多线程可以使复杂的任务在后台运行，而不阻塞用户界面的响应。例如，在图形界面应用程序中，可以使用一个线程来处理用户界面事件和响应，另一个线程来执行耗时的计算或网络操作，这样用户可以同时进行交互而不会感到应用程序卡顿。&lt;/li&gt;
&lt;li&gt;提高程序的执行效率：通过并行执行多个任务，可以充分利用多核处理器的能力，加快程序的执行速度。对于需要进行大量计算或密集的I/O操作的任务，通过多线程可以将任务分解为多个子任务并行执行，从而减少总体执行时间。&lt;/li&gt;
&lt;li&gt;实现异步编程：多线程可以用于实现异步编程模型，其中一个线程可以执行长时间运行的操作，而其他线程可以继续执行其他任务。这种模型在处理网络请求、文件操作、数据库查询等需要等待外部资源响应的情况下非常有用，可以提高应用程序的性能和响应能力。&lt;/li&gt;
&lt;li&gt;分解复杂任务：多线程可以将一个复杂的任务分解为多个独立的子任务，并使用不同的线程同时执行这些子任务。这样可以简化任务的管理和实现，并且可以更好地利用系统资源。&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&#34;3-内存管理&#34;&gt;3. 内存管理
&lt;/h1&gt;&lt;ol&gt;
&lt;li&gt;内存(memory)资源永远都是&lt;strong&gt;稀缺的&lt;/strong&gt;，当越来越多的进程需要越来越来内存时，某些进程会因为得不到内存而无法运行；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内存容易被破坏&lt;/strong&gt;，一个进程可能误踩其他进程的内存空间；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;为了在&lt;strong&gt;多进程环境下&lt;/strong&gt;，使得进程之间的内存地址不受影响，&lt;strong&gt;相互隔离，内存短缺 + 内存访问需要做保护&lt;/strong&gt;，于是操作系统就为每个进程独立分配一套&lt;strong&gt;虚拟地址空间。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;操作系统引入了虚拟内存，&lt;strong&gt;进程持有的虚拟地址会通过 CPU 芯片中的内存管理单元（MMU）的映射关系&lt;/strong&gt;，来转换变成物理地址，然后再通过物理地址访问内存&lt;/p&gt;
&lt;p&gt;每个进程都有自己的虚拟空间，而物理内存只有一个，所以当启用了大量的进程，物理内存必然会很紧张，于是操作系统会通过&lt;strong&gt;内存交换&lt;/strong&gt;技术，把&lt;strong&gt;不常使用的内存暂时存放到硬盘（换出&lt;/strong&gt;），在需要的时候再装载回物理内存（换入）。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;高效使用内存&lt;/strong&gt;：VM将主存看成是存储在磁盘上的地址空间的高速缓存，&lt;strong&gt;主存中保存热的数据&lt;/strong&gt;，根据需要在磁盘和主存之间传送数据；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;简化内存管理&lt;/strong&gt;：VM为每个进程提供了一致的地址空间，从而&lt;strong&gt;简化了链接、加载、内存共享&lt;/strong&gt;等过程；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内存保护&lt;/strong&gt;：通过在页表条目中加入保护位，保护&lt;strong&gt;每个进程的地址空间不被其他进程破坏&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;31-逻辑地址线性地址和物理地址的区别&#34;&gt;3.1 逻辑地址、线性地址和物理地址的区别？
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;逻辑地址（Logic Address）是指由程序产生的与段相关的偏移地址部分&lt;/strong&gt;，因此一个逻辑地址&lt;strong&gt;由段标识符和段内偏移量&lt;/strong&gt;组成，有时也称&lt;strong&gt;虚拟地址&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;比如，在C程序中，&lt;strong&gt;可以使用&amp;amp;操作读取指针变量本身的值，实际上这个值就是逻辑地址&lt;/strong&gt;。逻辑地址和绝对的物理地址不相干。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;程序经过编译后，每个目标模块都是从0号单元开始编址&lt;/strong&gt;，&lt;strong&gt;称为该目标模块的相对地址（或逻辑地址）&lt;/strong&gt;。要通过&lt;strong&gt;分段地址的变化处理+分页后才会对应到相应的物理内存地址&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;线性地址（Linear Address）&lt;strong&gt;是逻辑地址到物理地址变换之间的&lt;/strong&gt;中间层&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;程序代码会产生逻辑地址，或说是&lt;strong&gt;段中的偏移地址&lt;/strong&gt;，&lt;strong&gt;加上相应段的基地址就生成了一个线性地址。 如果启用了分页机制，那么线性地址可以再经过变换产生物理地址。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;若是没有采用分页机制，那么线性地址就是物理地址。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;物理地址（Physical Address）&lt;strong&gt;是指内&lt;/strong&gt;存中物理单元的集合，它是地址转换的最终地址&lt;/strong&gt;，是CPU外部地址总线上的地址。进程在运行时执行指令和访问数据都要通过物理地址从主存中存取&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;逻辑（虚拟）地址经过分段（查询段表）转化为线性地址。线性地址经过分页（查询页表）转为物理地址。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;分页是把整个虚拟和物理内存空间切成一段段固定尺寸的大小&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这样一个连续并且尺寸固定的内存空间，我们叫&lt;strong&gt;页&lt;/strong&gt;（&lt;em&gt;Page&lt;/em&gt;）。&lt;strong&gt;在 Linux 下，每一页的大小为 &lt;code&gt;4KB&lt;/code&gt;。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;虚拟地址与物理地址之间通过&lt;strong&gt;页表来映射&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&#34;32-寻址方式有哪些&#34;&gt;3.2 寻址方式有哪些？
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;寻址寻的都是物理地址&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;分三组：立即寻址+寄存器寻址；&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;直接间接寻址； 相对寻址+2个基变址寻址。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;33-什么是虚拟内存&#34;&gt;3.3 什么是虚拟内存？
&lt;/h2&gt;&lt;p&gt;虚拟内存是一种计算机系统内存管理技术。&lt;strong&gt;它使得应用程序认为它拥有连续可用的内存，&lt;strong&gt;即&lt;/strong&gt;一个连续完整的地址空间&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;而实际上，它通常是被分隔成&lt;strong&gt;多个物理内存碎片&lt;/strong&gt;，还有部分暂时存储在外部磁盘存储器上，&lt;strong&gt;在需要时进行数据交换&lt;/strong&gt;。&lt;strong&gt;多任务会带来进程对内存的操作冲突，需要虚拟内存来解决&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;假设现在有一块物理内存，&lt;strong&gt;操作系统让两个进程共用这一块内存&lt;/strong&gt;，彼此并不打扰。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;虚拟&lt;strong&gt;内存地址空间是连续的，没有碎片&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;虚拟内存的最大空间就是cpu的最大寻址空间&lt;/strong&gt;，不受内存大小的限制，&lt;strong&gt;能提供比内存更大的地址空间&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;电脑中所运行的程序均需经过&lt;a class=&#34;link&#34; href=&#34;https://so.csdn.net/so/search?q=%E5%86%85%E5%AD%98&amp;amp;spm=1001.2101.3001.7020&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;内存&lt;/a&gt;执行，若执行的程序占用的内存很大很多，则会导致内存消耗殆尽，为解决该问题，WINDOWS运用了虚拟内存技术，即拿出一部分硬盘空间来充当内存使用，这部分空间即称为虚拟内存。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;优点：&lt;strong&gt;可以弥补物理内存大小的不足&lt;/strong&gt;；加载或交换每个用户程序到内存所需的 I/O 会更少一定程度的提高反映速度；&lt;/p&gt;
&lt;p&gt;用户可以为&lt;strong&gt;一个巨大的虚拟地址空间编写程序&lt;/strong&gt;，&lt;strong&gt;同时运行更多的程序&lt;/strong&gt;，进而增加 CPU 利用率和吞吐量，但没有增加响应时间或周转时间**，减少对物理内存的读取从而保护内存延长内存使用寿命**；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;缺点：&lt;strong&gt;占用一定的物理硬盘空间&lt;/strong&gt;；&lt;strong&gt;加大了对硬盘的读写&lt;/strong&gt;；&lt;strong&gt;设置不得当会影响整机稳定性与速度。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;虚拟内存技术允许执行进程不必完全处于内存&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这种方案的一个主要优点就是，&lt;strong&gt;程序可以大于物理内存&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;此外，虚拟内存将内存抽象成一个巨大的、统一的存储数组，进而实现了用户看到的逻辑内存与物理内存的分离。这种技术使得程序员不再担忧内存容量的限制。&lt;/p&gt;
&lt;p&gt;虚拟内存还&lt;strong&gt;允许进程轻松共享文件和实现共享内存&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;331-虚拟内存作用&#34;&gt;3.3.1 虚拟内存作用
&lt;/h3&gt;&lt;p&gt;1、&lt;strong&gt;安全隔离&lt;/strong&gt;，进程访问自身的私有内存片&lt;/p&gt;
&lt;p&gt;2、&lt;strong&gt;共享内存&lt;/strong&gt;，在进程之间有效共享代码库&lt;/p&gt;
&lt;p&gt;3、善用碎片空间，更有效率地使用主存能够创建给主存更多的空间，&lt;strong&gt;每个进程都独有一个虚拟内存，并且解决主存非连续空间分配内存给某进程善用碎片空间&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;4、可作为缓存用，但需要进程通过页表进行翻译，这个时候需要在&lt;strong&gt;通过硬件进行缓存如TLB&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Q: 假设没有虚拟内存会怎么样？&lt;/p&gt;
&lt;p&gt;1、当一个进程需要的空间少于主存的时候，运行正常&lt;/p&gt;
&lt;p&gt;2、当一个进程需要读取非常大的文件的时候，&lt;strong&gt;主存不够大，这个时候就出现缺页，切换进行效率好差&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;3、当有3个进程，划分了n分空间，第四个进程没有连续空间进行划分这个时候就会出现&lt;strong&gt;创建不了进程&lt;/strong&gt;，&lt;strong&gt;甚至出现频繁切换进程&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;【总结】&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;运行更安全进程独立内存地址空间，善用碎片的内存空间&lt;/strong&gt;，从而运行更多进程提高效率&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://uk-1259555870.cos.eu-frankfurt.myqcloud.com/20200217135006.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;h2 id=&#34;34-什么是交换空间&#34;&gt;3.4 什么是交换空间？
&lt;/h2&gt;&lt;p&gt;操作系统把&lt;strong&gt;物理内存(physical RAM)分成一块一块的小内存&lt;/strong&gt;，每一块内存被称为&lt;strong&gt;页(page)&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;当内存资源不足时，&lt;strong&gt;Linux把某些页的内容转移至硬盘上的一块空间上，以释放内存空间&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;硬盘上的那块空间叫做&lt;strong&gt;交换空间&lt;/strong&gt;(swap space), 而这一过程被称为交换(swapping)。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;物理内存和交换空间的总容量就是虚拟内存的可用容量。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;用途：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;物理内存不足时一些不常用的页可以被交换出去，腾给系统。&lt;/li&gt;
&lt;li&gt;程序启动时很多内存页被用来初始化，之后便不再需要，可以交换出去。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;35-什么是分页&#34;&gt;3.5 什么是分页？
&lt;/h2&gt;&lt;p&gt;把内存空间划分为&lt;strong&gt;大小相等且固定的块&lt;/strong&gt;，作为主存的基本单位。&lt;/p&gt;
&lt;p&gt;因为程序数据存储在不同的页面中，而页面又离散的分布在内存中，&lt;strong&gt;因此需要一个页表来记录映射关系，以实现从页号到物理块号的映射。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;访问分页系统中内存数据需要两次的内存访问.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;(第&lt;strong&gt;一次是从内存中访问页表，从中找到指定的物理块号，加上页内偏移得到实际物理地址&lt;/strong&gt;；&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二次就是根据第一次得到的物理地址访问内存取出数据&lt;/strong&gt;。)&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://uk-1259555870.cos.eu-frankfurt.myqcloud.com/20200218105728.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;分段的好处就是能产生连续的内存空间，但是会&lt;strong&gt;出现内存碎片和内存交换的空间太大&lt;/strong&gt;的问题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;分页是把整个虚拟和物理内存空间切成一段段固定尺寸的大小&lt;/strong&gt;。这样一个连续并且尺寸固定的内存空间，我们叫&lt;strong&gt;页&lt;/strong&gt;（&lt;em&gt;Page&lt;/em&gt;）。在 Linux 下，每一页的大小为 &lt;code&gt;4KB&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;页表是存储在内存里的，&lt;strong&gt;内存管理单元&lt;/strong&gt; （&lt;em&gt;MMU&lt;/em&gt;）就做将虚拟内存地址转换成物理地址的工作。&lt;/p&gt;
&lt;p&gt;而当进程访问的虚拟地址在页表中查不到时，&lt;strong&gt;系统会产生一个缺页异常&lt;/strong&gt;，&lt;strong&gt;进入系统内核空间分配物理内存、更新进程页表，最后再返回用户空间&lt;/strong&gt;，恢复进程的运行。&lt;/p&gt;
&lt;p&gt;由于内存空间都是预先划分好的，也就不会像分段会产生间隙非常小的内存，这正是分段会产生内存碎片的原因。&lt;/p&gt;
&lt;p&gt;而&lt;strong&gt;采用了分页，那么释放的内存都是以页为单位释放的，也就不会产生无法给进程使用的小内存。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果内存空间不够，操作系统会把其他正在运行的进程中的「&lt;strong&gt;最近没被使用&lt;/strong&gt;」的内存页面给释放掉，也就是暂时写在硬盘上，称为&lt;strong&gt;换出&lt;/strong&gt;（&lt;em&gt;Swap Out&lt;/em&gt;）。&lt;/p&gt;
&lt;p&gt;一旦需要的时候，再加载进来，称为&lt;strong&gt;换入&lt;/strong&gt;（&lt;em&gt;Swap In&lt;/em&gt;）。所以，一次性写入磁盘的也只有少数的一个页或者几个页，不会花太多时间，&lt;strong&gt;内存交换的效率就相对比较高。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;总结一下，对于一个内存地址转换，其实就是这样三个步骤：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;把虚拟内存地址，切分成&lt;strong&gt;页号和偏移量&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;根据页号，从&lt;strong&gt;页表里面，查询对应的物理页号&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;直接拿&lt;strong&gt;物理页号，加上前面的偏移量&lt;/strong&gt;，就得到了物理内存地址。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因为&lt;strong&gt;操作系统是可以同时运行非常多的进程的，那这不就意味着页表会非常的庞大。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 32 位的环境下，虚拟地址空间共有 4GB（2^32），假设一个页的大小是 4KB（2^12），那么就需要大约 100 万 （2^20） 个页&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;每个「页表项」需要 4 个字节大小来存储&lt;/strong&gt;，&lt;strong&gt;那么整个 4GB 空间的映射就需要有 &lt;code&gt;4MB&lt;/code&gt; 的内存来存储页表&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这 4MB 大小的页表，看起来也不是很大。但是要知道每个进程都是有自己的虚拟地址空间的，也就说都有自己的页表。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;那么，&lt;code&gt;100&lt;/code&gt; 个进程的话，就需要 &lt;code&gt;400MB&lt;/code&gt; 的内存来存储页表，这是非常大的内存了，更别说 64 位的环境了。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;36-什么是分段&#34;&gt;3.6 什么是分段？
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;分页是为了提高内存利用率，而分段是为了满足程序员在编写代码的时候的一些逻辑需求&lt;/strong&gt;(&lt;strong&gt;比如数据共享，数据保护，动态链接等)。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;分段内存管理当中，&lt;strong&gt;地址是二维的，一维是段号，二维是段内地址；其中每个段的长度是不一样的，而且每个段内部都是从0开始编址的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;由于分段管理中，&lt;strong&gt;每个段内部是连续内存分配&lt;/strong&gt;，但是&lt;strong&gt;段和段之间是离散分配&lt;/strong&gt;的，因此也存在一个逻辑地址到物理地址的映射关系，相应的就是&lt;strong&gt;段表机制&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://uk-1259555870.cos.eu-frankfurt.myqcloud.com/20200218110201.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;段选择⼦就保存在段寄存器&lt;/strong&gt;⾥⾯。&lt;strong&gt;段选择⼦⾥⾯最重要的是段号&lt;/strong&gt;，⽤作段表的索引。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;段表&lt;/strong&gt;⾥⾯保存的是这个&lt;strong&gt;段的基地址、段的界限和特权等级&lt;/strong&gt;等。&lt;/p&gt;
&lt;p&gt;虚拟地址中的&lt;strong&gt;段内偏移量&lt;/strong&gt;应该位于 0 和段界限之间，如果段内偏移量是合法的，就将段&lt;strong&gt;基地址加上段内偏移量得到物理内存地址&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第⼀个就是**外部内存碎⽚**的问题。&lt;/p&gt;
&lt;p&gt;第⼆个就是&lt;strong&gt;内存交换的效率低&lt;/strong&gt;的问题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;分段机制下&lt;/strong&gt;的虚拟地址由两部分组成，&lt;strong&gt;段选择子&lt;/strong&gt;和&lt;strong&gt;段内偏移量&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;段选择子&lt;/strong&gt;就保存在段寄存器里面。段选择子里面最重要的是&lt;strong&gt;段号&lt;/strong&gt;，用作段表的索引。&lt;strong&gt;段表&lt;/strong&gt;里面保存的是这个&lt;strong&gt;段的基地址、段的界限和特权等级&lt;/strong&gt;等。&lt;/li&gt;
&lt;li&gt;虚拟地址中的&lt;strong&gt;段内偏移量&lt;/strong&gt;应该位于 0 和段界限之间，如果段内偏移量是合法的，就将段基地址加上段内偏移量得到物理内存地址。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里的内存碎片的问题共有两处地方：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;外部内存碎片&lt;/strong&gt;，也就是产生了多个不连续的小物理内存，&lt;strong&gt;导致新的程序无法被装载&lt;/strong&gt;；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;内部内存碎片&lt;/strong&gt;，&lt;strong&gt;程序所有的内存都被装载到了物理内存&lt;/strong&gt;，但是这个程序有部分的内存&lt;strong&gt;可能并不是很常使用&lt;/strong&gt;，这也会导致内存的浪费；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对于多进程的系统来说，用分段的方式，内存碎片是很容易产生的，产生了内存碎片，那不得不重新 &lt;code&gt;Swap&lt;/code&gt; 内存区域，这个过程会产生性能瓶颈。&lt;/p&gt;
&lt;p&gt;因为硬盘的访问速度要比内存慢太多了，每一次内存交换，我们都需要把一大段连续的内存数据写到硬盘上。&lt;/p&gt;
&lt;p&gt;所以，&lt;strong&gt;如果内存交换的时候，交换的是一个占内存空间很大的程序，这样整个机器都会显得卡顿。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;为了&lt;strong&gt;解决内存分段的内存碎片和内存交换效率低的问题，就出现了内存分页。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;37-分页分段的区别是什么&#34;&gt;3.7 分页分段的区别是什么？
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;属性：&lt;strong&gt;页是信息的物理单位&lt;/strong&gt;，对用户不可见，&lt;strong&gt;段是逻辑单位&lt;/strong&gt;，用户可见。&lt;/li&gt;
&lt;li&gt;大小：&lt;strong&gt;分页固定，分段不固定&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;决定权：&lt;strong&gt;分页在于系统，分段在于用户&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;目的：&lt;strong&gt;分页有利于资源的利用，分段方便用户管理内存&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;目的&lt;/p&gt;
&lt;p&gt;页是信息的物理单位，分页是为实现离散分配方式，&lt;strong&gt;以消减内存的外零头，提高内存的利用率&lt;/strong&gt;。或者说，分页是出于&lt;strong&gt;系统管理的需要而不是用户需要&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;段是信息的逻辑单位，它含有一组其意义相对完整的信息。分段的目的是为了&lt;strong&gt;更好地满足用户的需要&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;长度&lt;/p&gt;
&lt;p&gt;页的&lt;strong&gt;大小固定而且由系统决定&lt;/strong&gt;，由系统把逻辑地址划分为页号和页内地址两部分，是由机器硬件实现的，因而在系统中只能有一种大小的页面。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;段的长度不固定，决定于用户所编写的程序&lt;/strong&gt;，通常由编译程序在对程序进行编译时，根据信息的性质来划分。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;地址空间&lt;/p&gt;
&lt;p&gt;页的地址空间是&lt;strong&gt;一维&lt;/strong&gt;的，即单一的线形地址空间，程序员只要&lt;strong&gt;利用一个记忆符就可以表示一个地址&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;作业段地址空间是&lt;strong&gt;二维&lt;/strong&gt;的，程序员在标识一个地址时，&lt;strong&gt;既需要给出段名，又需给出段内地址&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;碎片&lt;/p&gt;
&lt;p&gt;分页有内部碎片&lt;strong&gt;无外部碎片&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;分段有外部碎片&lt;strong&gt;无内部碎片&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;绝对地址&lt;/p&gt;
&lt;p&gt;处理器使用&lt;strong&gt;页号和偏移量计算绝对地址&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;处理器使用&lt;strong&gt;段号和偏移量计算绝对地址&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;管理方式&lt;/p&gt;
&lt;p&gt;对于分页，操作系统必须为&lt;strong&gt;每个进程维护一个页表&lt;/strong&gt;，以说明每个页对应的的页框。&lt;/p&gt;
&lt;p&gt;当进程运行时，&lt;strong&gt;它的所有页都必须在内存中&lt;/strong&gt;，除非&lt;strong&gt;使用覆盖技术或虚拟技术&lt;/strong&gt;，另外&lt;strong&gt;操作系统需要维护一个空闲页框列表&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;对于分段，操作系统必须为&lt;strong&gt;每个进程维护一个段表&lt;/strong&gt;，以说明每个段的加载地址和长度。当进程运行时，它的所有短都必须在内存中，除非使用覆盖技术或虚拟技术，另外操作系统需要维护一个内存中的空闲的空洞列表。&lt;/p&gt;
&lt;p&gt;特别的，当使用虚拟技术是，把一页或一段写入内存时可能需要把一页或几个段写入磁盘。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;共享和动态链接&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;分页不容易实现，分段容易实现。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;38-有哪些页面置换算法&#34;&gt;3.8 有哪些页面置换算法？
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;缺页中断&lt;/strong&gt;：在请求分页系统中，可以通过&lt;strong&gt;查询页表中的状态位&lt;/strong&gt;来确定所要访问的页面是否存在于内存中。每当所要访问的页面不在内存是，会产生一次&lt;strong&gt;缺页中断&lt;/strong&gt;，此时操作系统会根据&lt;strong&gt;页表中的外存地址在外存中找到所缺的一页，将其调入内存&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;有时候操作系统必须在内存选择一个页面将其移出内存，以便为即将调入的页面让出空间。而用来选择淘汰哪一页的规则叫做&lt;strong&gt;页面置换算法。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;最佳置换算法（OPT）&lt;/strong&gt;（理想置换算法）：从主存中&lt;strong&gt;移出永远不再需要的页面&lt;/strong&gt;；如无这样的页面存在，&lt;strong&gt;则选择最长时间不需要访问的页面&lt;/strong&gt;。于所选择的被淘汰页面将是以后永不使用的，或者是在最长时间内不再被访问的页面，这样可以保证获得最低的缺页率。 最佳页面置换算法作用是为了衡量你的算法的效率，你的算法效率越接近该算法的效率，那么说明你的算法是高效的。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;先进先出FIFO&lt;/strong&gt;：总是选择在&lt;strong&gt;主存中停留时间最长&lt;/strong&gt;（即&lt;strong&gt;最老&lt;/strong&gt;）的一页置换&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;LRU&lt;/strong&gt;：&lt;strong&gt;选择在最近一段时间里最久没有使用&lt;/strong&gt;过的页面予以置换最佳页面置换算法作用是为了衡量你的算法的效率，你的算法效率越接近该算法的效率，那么说明你的算法是高效的。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;LFU(&lt;strong&gt;least )：统计页的使用频率，选择在&lt;/strong&gt;最近时期使用最少的页面作为淘汰页&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;NRU&lt;/strong&gt;(Not Recently Used)：&lt;strong&gt;最近未用算法&lt;/strong&gt;，通过给每一个访问的页面关联一个&lt;strong&gt;附加位(reference bit)&lt;/strong&gt;，有些地方也叫做&lt;em&gt;&lt;strong&gt;使用位(use bit)&lt;/strong&gt;&lt;/em&gt;。&lt;/p&gt;
&lt;p&gt;主要思想是：当某一页装入主存时，将use bit置成1；如果该页之后又被访问到，使用位也还是标记成1。对于页面置换算法，候选的帧集合可以看成是一个&lt;strong&gt;循环缓冲区&lt;/strong&gt;，并且有一个指针和缓冲区相关联。遇到页面替换时，指针指向缓冲区的下一帧。&lt;strong&gt;如果这页进入主存后发现没有空余的帧&lt;/strong&gt;(frame)，即所有页面的使用位均为1，那么这时候从指针开始循环一个缓冲区，&lt;strong&gt;将之前的使用位都清0&lt;/strong&gt;，并且留在最初的位置上，换出该桢对应的页。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;改进NRU：在之前的CLOCK算法上面除了&lt;em&gt;&lt;strong&gt;使用位(used bit)&lt;/strong&gt;&lt;/em&gt;&lt;em&gt;，还增加了一个&lt;/em&gt;&lt;strong&gt;修改位(modified bit)&lt;/strong&gt;&lt;em&gt;&lt;em&gt;，有些地方也叫做dirty bit。现在每一页有两个状态，分别**是&lt;/em&gt;(使用位，修改位)&lt;/em&gt;***，可分为以下四种情况考虑：&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;https://img-blog.csdnimg.cn/20200617192749203.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0MyOTI1ODExMDgx,size_16,color_FFFFFF,t_70&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;刚刚换出的页面马上又要换入内存，刚刚换入的页面马上又要换出外存，这种&lt;strong&gt;频繁的页面调度行为称为抖动，或颠簸&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;产生抖动的主要原因是&lt;strong&gt;进程频繁访问的页面数目高于可用的物理块数&lt;/strong&gt;（分配给进程&lt;strong&gt;的物理块不够&lt;/strong&gt;）&lt;/p&gt;
&lt;h2 id=&#34;39-段式内存管理&#34;&gt;3.9 段⻚式内存管理
&lt;/h2&gt;&lt;p&gt;先将程序划分为多个有逻辑意义的段，也就是前⾯提到的分段机制；&lt;/p&gt;
&lt;p&gt;接着再把每个段划分为多个⻚，也就是对分段划分出来的连续空间，再划分固定⼤⼩的⻚；&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第⼀次访问段表，得到⻚表起始地址；&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第⼆次访问⻚表，得到物理⻚号；&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第三次将物理⻚号与⻚内位移组合，得到物理地址。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;可用&lt;strong&gt;软、硬件相结合的方法实现段页式地址变换&lt;/strong&gt;，这样虽然增加了硬件成本和系统开销，但提高了内存的利用率。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Linux&lt;/strong&gt; &lt;strong&gt;内存主要采⽤的是⻚式内存管理，但同时也不可避免地涉及了段机制&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这主要是上⾯ &lt;strong&gt;Intel 处理器发展历史&lt;/strong&gt;导致的，因为 &lt;strong&gt;Intel X86 CPU&lt;/strong&gt; ⼀律对程序中使⽤的&lt;strong&gt;地址先进⾏段式映射&lt;/strong&gt;，然后才能进⾏&lt;strong&gt;⻚式映射&lt;/strong&gt;。既然 CPU 的硬件结构是这样，Linux 内核也只好服从 Intel 的选择。&lt;/p&gt;
&lt;p&gt;但是事实上，&lt;strong&gt;Linux 内核所采取的办法是使段式映射的过程实际上不起什么作⽤&lt;/strong&gt;。也就是说，“上有政策，下有对策”，若惹不起就躲着⾛。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Linux&lt;/strong&gt; &lt;strong&gt;系统中的每个段都是从&lt;/strong&gt; &lt;strong&gt;0&lt;/strong&gt; &lt;strong&gt;地址开始的整个&lt;/strong&gt; &lt;strong&gt;4GB&lt;/strong&gt; &lt;strong&gt;虚拟空间（32&lt;/strong&gt; &lt;strong&gt;位环境下），也就是所有的段的起始地址都是⼀样的。这意味着，Linux&lt;/strong&gt; &lt;strong&gt;系统中的代码，包括操作系统本身的代码和应⽤程序代码，所⾯对的&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;地址空间都是线性地址空间（虚拟地址），这种做法相当于屏蔽了&lt;strong&gt;处理器中的逻辑地址概念&lt;/strong&gt;，段只被⽤于&lt;strong&gt;访问控制和内存保护。&lt;strong&gt;这样虽然&lt;/strong&gt;增加了硬件成本和系统开销&lt;/strong&gt;，&lt;strong&gt;但提⾼了内存的利⽤率&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;C:%5cUsers%5cadmin%5cAppData%5cRoaming%5cTypora%5ctypora-user-images%5cimage-20210707231451261.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;C:%5cUsers%5cadmin%5cAppData%5cRoaming%5cTypora%5ctypora-user-images%5cimage-20210707231435592.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;h2 id=&#34;310-cache&#34;&gt;3.10 Cache
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;多级⻚表虽然解决了空间上的问题&lt;/strong&gt;，但是虚拟地址到物理地址的转换&lt;strong&gt;就多了⼏道转换的⼯序&lt;/strong&gt;，这显然就降低了这俩地址转换的速度，也就是带来了&lt;strong&gt;时间上的开销&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;CPU 芯⽚中，加⼊了⼀个专⻔存放程序&lt;strong&gt;最常访问的⻚表项的 Cache&lt;/strong&gt;，这个 Cache 就是 TLB&lt;/p&gt;
&lt;p&gt;（&lt;em&gt;Translation Lookaside Buffer&lt;/em&gt;） ，通常称为⻚表缓存、转址旁路缓存、快表等。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.jsdelivr.net/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/16-TLB.jpg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;在 CPU 芯片里面，封装了内存管理单元（&lt;em&gt;Memory Management Unit&lt;/em&gt;）芯片，它用来完成地址转换和 TLB 的访问与交互。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;有了 TLB 后，那么 CPU 在寻址时，会先查 TLB，如果没找到，才会继续查常规的页表&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TLB 的命中率其实是很高的，因为程序最常访问的页就那么几个。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;311-linux内存分布&#34;&gt;3.11 Linux内存分布
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;程序文件段，包括二进制可执行代码；&lt;/li&gt;
&lt;li&gt;已初始化数据段，包括&lt;strong&gt;静态常量&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;未初始化数据段，包括&lt;strong&gt;未初始化的静态变量&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;堆段，包括&lt;strong&gt;动态分配的内存&lt;/strong&gt;，从低地址开始向上增长；&lt;/li&gt;
&lt;li&gt;文件映射段，包括动态库、共享内存等，从低地址开始向上增长（跟硬件和内核版本有关 ）；&lt;/li&gt;
&lt;li&gt;栈段，包括局部变量和函数调用的上下文等。&lt;strong&gt;栈的大小是固定的&lt;/strong&gt;，一般是 &lt;code&gt;8 MB&lt;/code&gt;。当然系统也提供了参数，以便我们自定义大小；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在这 6 个内存段中，&lt;strong&gt;堆和文件映射段的内存是动态分配的&lt;/strong&gt;。比如说，使用 C 标准库的 &lt;code&gt;malloc()&lt;/code&gt;（br()） 或者 &lt;code&gt;mmap()&lt;/code&gt; ，就可以分别在堆和文件映射段动态分配内存。&lt;/p&gt;
&lt;h2 id=&#34;312-malloc-是如何分配内存的&#34;&gt;3.12 malloc 是如何分配内存的？
&lt;/h2&gt;&lt;p&gt;实际上，&lt;strong&gt;malloc() 并不是系统调用&lt;/strong&gt;，&lt;strong&gt;而是 C 库里的函数&lt;/strong&gt;，用于动态分配内存。&lt;/p&gt;
&lt;p&gt;malloc 申请内存的时候，会有两种方式向操作系统申请堆内存。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;方式一：&lt;strong&gt;通过 brk() 系统调用从堆分配内存&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;方式二：&lt;strong&gt;通过 mmap() 系统调用在文件映射区域分配内存&lt;/strong&gt;；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;方式一 实现的方式很简单，就是通过 brk() 函数将「堆顶」指针向&lt;strong&gt;高地址移动&lt;/strong&gt;，获得新的内存空间。如下图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://img-blog.csdnimg.cn/img_convert/0dd0e2c1eb32b8b7cabfb95392a36f82.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;方式二 通过 mmap() 系统调用中「&lt;strong&gt;私有匿名映射&lt;/strong&gt;」的方式，在文件映射区分配一块内存，也就是从&lt;strong&gt;文件映射区“偷”了一块内存&lt;/strong&gt;。如下图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://img-blog.csdnimg.cn/img_convert/f8425aa73ca7e5ac8e3a46c2e3eb9188.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;malloc() 源码里默认定义了一个阈值：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;如果用户分配的内存小于 128 KB&lt;/strong&gt;，&lt;strong&gt;则通过 brk() 申请内存&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;如果用户分配的内存&lt;strong&gt;大于 128 KB&lt;/strong&gt;，则通过 mmap() 申请内存；&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;313-malloc-分配的是物理内存吗&#34;&gt;3.13 malloc() 分配的是物理内存吗？
&lt;/h2&gt;&lt;p&gt;不是的，&lt;strong&gt;malloc() 分配的是虚拟内存&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果分配后的虚拟内存没有被访问的话，是不会将虚拟内存不会映射到物理内存&lt;/strong&gt;，这样就不会占用物理内存了。&lt;/p&gt;
&lt;p&gt;只有在访问已分配的虚拟地址空间的时候，操作系统通过查找页表，&lt;strong&gt;发现虚拟内存对应的页没有在物理内存中，就会触发缺页中断&lt;/strong&gt;，然后操作系统会建立虚拟内存和物理内存之间的映射关系&lt;/p&gt;
&lt;p&gt;我们在上面的进程往下执行，看看通过 free() 函数释放内存后，堆内存还在吗？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这是因为与其把这 1 字节释放给操作系统，不如先缓存着放进 malloc 的内存池里，当进程再次申请 1 字节的内存时就可以直接复用&lt;/strong&gt;，这样速度快了很多。&lt;/p&gt;
&lt;p&gt;当然，当&lt;strong&gt;进程退出后，操作系统就会回收进程的所有资源&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;上面说的 &lt;strong&gt;free 内存后堆内存还存在，是针对 malloc 通过 brk() 方式申请的内存的情况。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果 &lt;strong&gt;malloc 通过 mmap 方式申请的内存，free 释放内存后就会归还给操作系统&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;malloc() 在分配内存的时候，并不是老老实实按用户预期申请的字节数来分配内存空间大小，而是&lt;strong&gt;会预分配更大的空间作为内存池&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&#34;314-为什么不全部使用-mmap-来分配内存&#34;&gt;3.14 为什么不全部使用 mmap 来分配内存？
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;因为向操作系统申请内存，是要通过系统调用的，执行系统调用是要进入内核态的&lt;/strong&gt;，&lt;strong&gt;然后在回到用户态，运行态的切换会耗费不少时间。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;所以，申请内存的操作应该避免频繁的系统调用，&lt;strong&gt;如果都用 mmap 来分配内存&lt;/strong&gt;，&lt;strong&gt;等于每次都要执行系统调用。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;另外，&lt;strong&gt;因为 mmap 分配的内存每次释放的时候，都会归还给操作系统&lt;/strong&gt;，&lt;strong&gt;于是每次 mmap 分配的虚拟地址都是缺页状态的&lt;/strong&gt;，然后在第一次访问该虚拟地址的时候，就会触发缺页中断。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;频繁通过 mmap 分配的内存话，不仅每次都会发生运行态的切换，还会发生缺页中断（在第一次访问虚拟地址后），这样会导致 CPU 消耗较大&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;为了改进这两个问题，malloc 通过 brk() 系统调用在堆空间申请内存的时候，由于堆空间是连续的，所以&lt;strong&gt;直接预分配更大的内存来作为内存池&lt;/strong&gt;，&lt;strong&gt;当内存释放的时候，就缓存在内存池中&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;等下次在申请内存的时候，就直接从内存池取出对应的内存块就行了，而且可能这个内存块的虚拟地址与物理地址的映射关系还存在，这样不仅减少了系统调用的次数，也减少了缺页中断的次数，这将大大降低 CPU 的消耗&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;但是如果下次申请的内存大于 30k，没有可用的空闲内存空间，必须向 OS 申请，实际使用内存继续增大。&lt;/p&gt;
&lt;p&gt;因此，&lt;strong&gt;随着系统频繁地 malloc 和 free ，尤其对于小块内存，堆内将产生越来越多不可用的碎&lt;/strong&gt;片，导致“内存泄露”。而这种“泄露”现象使用 valgrind 是无法检测出来的。&lt;/p&gt;
&lt;p&gt;所以，malloc 实现中，充分考虑了 sbrk 和 mmap 行为上的差异及优缺点，&lt;strong&gt;默认分配大块内存 (128KB) 才使用 mmap 分配内存空间&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;315-free-函数只传入一个内存地址为什么能知道要释放多大的内存&#34;&gt;3.15 free() 函数只传入一个内存地址，为什么能知道要释放多大的内存？
&lt;/h2&gt;&lt;p&gt;还记得，我前面提到， &lt;strong&gt;malloc 返回给用户态的内存起始地址比进程的堆空间起始地址多了 16 字节吗？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这个多出来的 16 字节就是保存了该内存块的描述信息，比如有该内存块的大小。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://img-blog.csdnimg.cn/img_convert/cb6e3ce4532ff0a6bfd60fe3e52a806e.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;这样当执行 free() 函数时，&lt;strong&gt;free 会对传入进来的内存地址向左偏移 16 字节，然后从这个 16 字节的分析出当前的内存块的大小&lt;/strong&gt;，自然就知道要释放多大的内存了。&lt;/p&gt;
&lt;h1 id=&#34;4-系统中断&#34;&gt;4. 系统中断
&lt;/h1&gt;&lt;p&gt;在CPU执行程序的过程中，&lt;strong&gt;出现了某种紧急情况或异常的事件时，暂停正在执行的程序，转去处理该事件，并在处理完该事件之后返回断点处（指返回主程序时执行的第一条指令的地址）继续执行刚刚被暂停的程序&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;软中断和硬中断&#34;&gt;软中断和硬中断
&lt;/h3&gt;&lt;p&gt;我们通常所说的&lt;strong&gt;中断指的是硬中断(hardirq)&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;软中断是执行中断指令产生的，而硬中断是由外设引发的。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;硬中断的中断号是由中断控制器提供的，软中断的中断号由指令直接指出，无需使用中断控制器。&lt;/p&gt;
&lt;p&gt;硬中断是可屏蔽的，软中断不可屏蔽。&lt;/p&gt;
&lt;p&gt;硬中断处理程序要确保它能快速地完成任务，这样程序执行时才不会等待较长时间，称为上半部。&lt;/p&gt;
&lt;p&gt;软中断处理硬中断未完成的工作，是一种推后执行的机制，属于下半部。&lt;/p&gt;
&lt;h2 id=&#34;41-中断的处理过程&#34;&gt;4.1 中断的处理过程
&lt;/h2&gt;&lt;h3 id=&#34;中断请求&#34;&gt;中断请求
&lt;/h3&gt;&lt;p&gt;中断源向CPU发出中断请求，&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;发生&lt;strong&gt;在CPU内部的中断（内部中断），不需要中断请求&lt;/strong&gt;，&lt;strong&gt;CPU内部的中断控制逻辑直接接收处理&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;软中断是执行中断指令产生的，&lt;/strong&gt;  &lt;strong&gt;而硬中断是由外设引发的&lt;/strong&gt;，&lt;strong&gt;比如当网卡收到数据包的时候，就会发出一个中断。&lt;/strong&gt;&lt;/p&gt;
&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;&lt;strong&gt;外部中断请求由中断源提出&lt;/strong&gt;。&lt;strong&gt;外部中断源利用CPU的中断输入引脚 输入中断请求信号&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;一般CPU设有两个中断请求输入引脚：可屏蔽中断请求输入引脚和不可屏蔽中断请求输入引脚。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;中断请求触发器&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;每个中断源发中断请求信号的时间是不确定的，而CPU在何时响应中断也是不确定的。&lt;/p&gt;
&lt;p&gt;所以，&lt;strong&gt;每个中断源都有一个中断请求触发器&lt;/strong&gt;，&lt;strong&gt;锁存自己的中断请求信号，并保持到CPU响应这个中断请求之后才将其清除。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在CPU内部有一个&lt;strong&gt;中断允许触发器&lt;/strong&gt;，当其&lt;strong&gt;为“1”时，允许CPU响应中断， 称为开中断&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;若其为“0”，不允许CPU响应中断，中断被屏蔽，称为关中断&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;中断响应&#34;&gt;中断响应
&lt;/h3&gt;&lt;p&gt;① &lt;strong&gt;保护硬件现场（PC）和（PSW）&lt;/strong&gt;； 把CPU的状态保存在寄存器中。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;程序计数器（Program Counter，PC）&lt;strong&gt;用来指出&lt;/strong&gt;下一条指令在主存储器中的地址&lt;/strong&gt;，&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;程序状态字（Program Status Word，PSW）&lt;strong&gt;用来表&lt;/strong&gt;征当前运算的状态及程序的工作方式&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;② 关中断；&lt;/p&gt;
&lt;h3 id=&#34;中断服务处理阶段&#34;&gt;中断服务处理阶段
&lt;/h3&gt;&lt;p&gt;1）&lt;strong&gt;保护现场。&lt;/strong&gt; 在中断服务程序的起始部分安排若干条&lt;strong&gt;入栈指令&lt;/strong&gt;，&lt;strong&gt;再将各寄存器的内容压入堆栈保存&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;2）&lt;strong&gt;开中断。&lt;/strong&gt;    &lt;strong&gt;在中断服务程序执行期间允许级别更高的中断请求中断现行的中断服务程序&lt;/strong&gt;，实现&lt;strong&gt;中断嵌套&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;3）&lt;strong&gt;中断服务。&lt;/strong&gt; 获得&lt;strong&gt;中断服务程序的入口地址。&lt;strong&gt;完成中断源的具体要求，根据&lt;/strong&gt;中断类型码&lt;/strong&gt;在&lt;strong&gt;中断向量表&lt;/strong&gt;中&lt;strong&gt;找到相应中断服务程序的入口地址&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;4）&lt;strong&gt;恢复现场。&lt;/strong&gt; 中断服务程序结束前，必须&lt;strong&gt;恢复主程序的中断现场&lt;/strong&gt;。通常是将保存在堆栈中的现场信息&lt;strong&gt;弹出到原来的寄存器中&lt;/strong&gt;。 &lt;strong&gt;返回到原程序的断点处，恢复硬件现场&lt;/strong&gt;，继续执行原程序。&lt;/p&gt;
&lt;p&gt;5）&lt;strong&gt;中断返回。&lt;/strong&gt; 返回到原程序的断点处，恢复硬件现场,继续执行原程序。&lt;/p&gt;
&lt;h2 id=&#34;42-中断和轮询有什么区别&#34;&gt;4.2 中断和轮询有什么区别？
&lt;/h2&gt;&lt;p&gt;轮询：CPU对&lt;strong&gt;特定设备轮流询问&lt;/strong&gt;。                   中断：通过&lt;strong&gt;特定事件提醒CPU&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;轮询：效率低等待时间长，CPU利用率不高。 中断：容易遗漏问题，&lt;strong&gt;CPU利用率不高&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;CPU要和外设进行通信，可以采用轮询和中断两种方式。&lt;/p&gt;
&lt;p&gt;因为轮询方式需要&lt;strong&gt;CPU轮询外设&lt;/strong&gt;，&lt;strong&gt;查询外设是否发生中断&lt;/strong&gt;，效率不高显而易见。于是增加了如下图的中断系统来减轻CPU负担，但是这样做效率就高了吗？&lt;/p&gt;
&lt;p&gt;本质上，采用中断系统后，CPU仍然需要&lt;strong&gt;每隔一小段时间去查询中断控制寄存器TCON的各位状态&lt;/strong&gt;，&lt;strong&gt;以判断是否有外设中断发生&lt;/strong&gt;，否则CPU仍旧无法知道外设的当前状态。&lt;/p&gt;
&lt;p&gt;如上所述，中断和轮询，好像又没啥区别，CPU仍旧&lt;strong&gt;摆脱不了查询的命运&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;但是&lt;strong&gt;让CPU直接和各个外设逐一沟通&lt;/strong&gt;，&lt;strong&gt;和让CPU只与中断控制系统机构沟通&lt;/strong&gt;，&lt;strong&gt;效率是完全不一样的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;为了证明我的推断，我们假设, CPU外接20个不同的设备，这20个外设中在某一刻有两个外设同时中断，正好这个时候CPU来查看外设的状态，如果是轮询方式，CPU需要一一遍历20种不同的外设控制器，才能判断哪些外设刚才申请过中断，哪些外设没有申请中断。如果采用中断方式处理呢？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CPU只需查询一下中断标志位&lt;/strong&gt;，处&lt;strong&gt;理最高优先级的那个中断，其他的事情全交给中断系统去处&lt;/strong&gt;理，效率提高了20倍！&lt;/p&gt;
&lt;p&gt;从中，我们也可以发现一个现象，不论硬件设计如何巧妙，软件产品如何复杂，&lt;strong&gt;在设计原则上仍然是在不断的做加法。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://images0.cnblogs.com/blog/310759/201308/04154441-5d9d1933fe544a9fbb1164acb33fcb04.jpg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;异常与中断不同，它在产生时必须考虑与处理器时钟同步&lt;/strong&gt;。实际上，&lt;strong&gt;异常也称为同步中断&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;比如，在处理器执行到由于&lt;strong&gt;编程失误而导致的错误指令&lt;/strong&gt;的时候，或者在执行期间出现特殊情况(缺页)，&lt;strong&gt;必须靠内核来处理的，处理器就产生一个异常。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;和中断的的工作方式类似，其差异只在于&lt;strong&gt;中断是由硬件而不是软件引起的&lt;/strong&gt;。&lt;/p&gt;
&lt;h1 id=&#34;5-磁盘空间&#34;&gt;5. 磁盘空间
&lt;/h1&gt;&lt;h2 id=&#34;51-磁盘调度&#34;&gt;5.1 磁盘调度
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;先来先服务算法，先到来的请求，先被服务。&lt;/li&gt;
&lt;li&gt;最短寻道时间优先算法，优先选择从当前磁头位置所需寻道时间最短的请求，还是以这个序列为例子：&lt;/li&gt;
&lt;li&gt;扫描算法算法，最短寻道时间优先算法会产生饥饿的原因在于：磁头有可能再一个小区域内来回得移动。为了防止这个问题，可以规定：&lt;strong&gt;磁头在一个方向上移动，访问所有未完成的请求，直到磁头到达该方向上的最后的磁道，才调换方向，这就是扫描（&lt;em&gt;Scan&lt;/em&gt;）算法&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;循环扫描算法，只有磁头朝某个特定方向移动时，才处理磁道访问请求，而返回时直接快速移动至最靠边缘的磁道，也就是复位磁头，这个过程是很快的，并且&lt;strong&gt;返回中途不处理任何请求&lt;/strong&gt;，该算法的特点，就是&lt;strong&gt;磁道只响应一个方向上的请求&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;LOOK 与 C-LOOK 算法，那这其实是可以优化的，&lt;strong&gt;优化的思路就是磁头在移动到「最远的请求」位置，然后立即反向移动。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id=&#34;6-文件系统&#34;&gt;6. 文件系统
&lt;/h1&gt;&lt;h2 id=&#34;61-软硬链接&#34;&gt;6.1 软硬链接
&lt;/h2&gt;&lt;p&gt;和普通文件不同的是，&lt;strong&gt;普通文件的块里面保存的是文件数据，而目录文件的块里面保存的是目录里面一项一项的文件信息。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;有时候我们希望给某个文件取个别名，那么在 Linux 中可以通过&lt;strong&gt;硬链接（Hard Link）&lt;/strong&gt; 和&lt;strong&gt;软链接（Symbolic Link）&lt;/strong&gt; 的方式来实现，它们都是比较特殊的文件，但是实现方式也是不相同的。&lt;/p&gt;
&lt;p&gt;硬链接是&lt;strong&gt;多个目录项中的「索引节点」指向一个文件&lt;/strong&gt;，也就是指向同一个 inode，但是 &lt;strong&gt;inode 是不可能跨越文件系统&lt;/strong&gt;的，每个文件系统都有各自的 inode 数据结构和列表，所以&lt;strong&gt;硬链接是不可用于跨文件系统的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;由于多个目录项都是指向一个 inode，那么&lt;strong&gt;只有删除文件的所有硬链接以及源文件时，系统才会彻底删除该文件。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;软链接相当于重新创建一个文件，这个文件有&lt;strong&gt;独立的 inode&lt;/strong&gt;，但是这个&lt;strong&gt;文件的内容是另外一个文件的路径&lt;/strong&gt;，所以访问软链接的时候，实际上相当于访问到了另外一个文件，所以&lt;strong&gt;软链接是可以跨文件系统的&lt;/strong&gt;，甚至&lt;strong&gt;目标文件被删除了，链接文件还是在的，只不过指向的文件找不到了而已。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;62-直接io与非直接io&#34;&gt;6.2 直接io与非直接io
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;直接 I/O，&lt;strong&gt;不会发生内核缓存和用户程序之间数据复制，而是直接经过文件系统访问磁盘&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;非直接 I/O，&lt;strong&gt;读操作时&lt;/strong&gt;，&lt;strong&gt;数据从内核缓存中拷贝给用户程序&lt;/strong&gt;，&lt;strong&gt;写操作时，数据从用户程序拷贝给内核缓存，再由内核决定什么时候写入数据到磁盘&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;以下几种场景会触发内核缓存的数据写入磁盘：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在调用 &lt;code&gt;write&lt;/code&gt; 的最后，当发现&lt;strong&gt;内核缓存的数据太多的时候&lt;/strong&gt;，内核会把数据写到磁盘上；&lt;/li&gt;
&lt;li&gt;用户主动调用 &lt;code&gt;sync&lt;/code&gt;，&lt;strong&gt;内核缓存会刷到磁盘上&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;当内存十分紧张，无法再分配页面时，&lt;strong&gt;也会把内核缓存的数据刷到磁盘上&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内核缓存的数据的缓存时间超过某个时间时&lt;/strong&gt;，也会把数据刷到磁盘上；&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;63-同步io异步io&#34;&gt;6.3. 同步IO，异步IO
&lt;/h2&gt;&lt;p&gt;&lt;img src=&#34;C:%5cUsers%5cadmin%5cAppData%5cRoaming%5cTypora%5ctypora-user-images%5cimage-20210709181006221.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;在前⾯我们知道了，I/O 是分为两个过程的：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  1. 数据准备的过程
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  2. 数据从内核空间拷⻉到⽤户进程缓冲区的过程
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;阻塞 I/O 会阻塞在「过程 1 」和「过程 2」&lt;/strong&gt;，&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;⽽⾮阻塞 I/O 和基于⾮阻塞 I/O 的多路复⽤只会阻塞在「过程2」，所以这三个都可以认为是同步 I/O。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;异步 I/O 则不同，&lt;strong&gt;「过程 1 」和「过程 2 」都不会阻塞。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;食堂打饭例子&lt;/p&gt;
&lt;p&gt;举个你去饭堂吃饭的例⼦，你好⽐⽤户程序，饭堂好⽐操作系统。&lt;/p&gt;
&lt;p&gt;阻塞 I/O 好⽐，你去饭堂吃饭，但是饭堂的菜还没做好，然后你就⼀直在那⾥等啊等，等了好⻓⼀段时间 &lt;strong&gt;终于等到饭堂阿姨把菜端了出来&lt;/strong&gt;（数据准备的过程），但是&lt;strong&gt;你还得继续等阿姨把菜（内核空间）打到&lt;/strong&gt;你的饭盒⾥（⽤户空间），经历完这两个过程，你才可以离开。&lt;/p&gt;
&lt;p&gt;⾮阻塞 I/O 好⽐，你去了饭堂，问阿姨菜做好了没有，阿姨告诉你没，你就离开了，过⼏⼗分钟，你**⼜来饭堂问阿姨，阿姨说做好了**，于是阿姨帮&lt;strong&gt;你把菜打到你的饭盒⾥&lt;/strong&gt;，&lt;strong&gt;这个过程你是得等待的&lt;/strong&gt;。基于⾮阻塞的 I/O 多路复⽤好⽐，你去饭堂吃饭，发现有⼀排窗⼝，饭堂阿姨告诉你这些窗⼝都还没做好菜，等做好了再通知你，于是等啊等（ select 调⽤中），过了⼀会阿姨通知你菜做好了，但&lt;strong&gt;是不知道哪个窗⼝的菜做好了，你⾃⼰看吧&lt;/strong&gt;。&lt;strong&gt;于是你只能⼀个⼀个窗⼝去确认&lt;/strong&gt;，后⾯发现 5 号窗⼝菜做好了，于是你让 5 号窗⼝的阿姨帮你打菜到饭盒⾥，&lt;strong&gt;这个打菜的过程你是要等待的&lt;/strong&gt;，虽然时间不⻓。打完菜后，你⾃然就可以离开了。&lt;/p&gt;
&lt;p&gt;异步 I/O 好⽐，&lt;strong&gt;你让饭堂阿姨将菜做好并把菜打到饭盒⾥后&lt;/strong&gt;，&lt;strong&gt;把饭盒送到你⾯前&lt;/strong&gt;，整个过程你都不需要任何等待.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;同步和异步IO的概念：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;同步 是&lt;strong&gt;用户线程发起I/O请求后&lt;/strong&gt;需要&lt;strong&gt;等待&lt;/strong&gt;或者&lt;strong&gt;轮询内核I/O操作&lt;/strong&gt;完成后&lt;strong&gt;才能继续执行&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;异步 是用户&lt;strong&gt;线程&lt;/strong&gt;发起I/O请求后仍&lt;strong&gt;需要继续执行&lt;/strong&gt;，&lt;strong&gt;当内核I/O操作完成后会通知用户线程&lt;/strong&gt;，或者&lt;strong&gt;调用用户线程注册的回调函数。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;阻塞和非阻塞IO的概念：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;阻塞是指I/O操作需要彻底完成后才能返回用户空间。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;非阻塞是指I/O操作被调用后&lt;strong&gt;立即返回一个状态值&lt;/strong&gt;，&lt;strong&gt;无需等I/O操作彻底完成。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;IO模型&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这里统一使用Linux下的系统调用&lt;strong&gt;recv作为例子&lt;/strong&gt;，它用于从套接字上接收一个消息，因为是一个系统调用，所以调用时会&lt;strong&gt;从用户进程空间切换到内核空间运行一段时间再切换回来&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;默认情况下recv会等到网络数据到达并且复制到用户进程空间&lt;/strong&gt;或者&lt;strong&gt;发生错误时返回&lt;/strong&gt;，而&lt;strong&gt;第4个参数flags可以让它马上返回。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;套接字&lt;/em&gt;(Socket)，就是对网络中不同主机上的应用进程之间进行&lt;strong&gt;双向通信的端点的抽象&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;一个&lt;em&gt;套接字&lt;/em&gt;就是网络上进程通信的一端，提供了应用层进程利用网络协议交换数据的机制&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;阻塞IO模型&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;使用recv的&lt;strong&gt;默认参数&lt;/strong&gt;，会一直&lt;strong&gt;等数据直到拷贝到用户空间&lt;/strong&gt;，这段时间内&lt;strong&gt;进程始终阻塞&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;A同学用杯子装水，打开水龙头装满水然后离开。这一过程就可以看成是使用了阻塞IO模型。&lt;/p&gt;
&lt;p&gt;因为如果水龙头没有水，他也要&lt;strong&gt;等到有水 并 装满杯子&lt;/strong&gt;才能离开去做别的事情。很显然，这种IO模型是同步的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;http://image.euphie.net/2017-09-24-23-18-01.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;非阻塞IO模型&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;改变flags，让recv不管有没有获取到数据都返回，如果没有数据那么一段时间后再调用recv看看，如此循环。&lt;/p&gt;
&lt;p&gt;B同学也用杯子装水，打开水龙头后发现没有水，它&lt;strong&gt;离开&lt;/strong&gt;了，过一会他又拿着杯子来看看……在中间离开的这些时间里，B同学离开了装水现场(&lt;strong&gt;回到用户进程空间&lt;/strong&gt;)，可以做他自己的事情。这就是&lt;strong&gt;非阻塞IO模型。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;但是&lt;strong&gt;它只有是检查有无数据的时候是非阻塞的&lt;/strong&gt;，在&lt;strong&gt;数据到达的时候依然要等待复制数据到用户空间(等着水将水杯装满)&lt;/strong&gt;，因此它还是&lt;strong&gt;同步IO&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;http://image.euphie.net/2017-09-24-23-19-53.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;IO复用模型&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;这里在调用recv前先调用select或者poll，这2个系统调用都可以在内核准备好数据(网络数据到达内核)时告知用户进程&lt;/strong&gt;，这个时候再调用&lt;strong&gt;recv一定是有数据的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;因此这一过程中它是&lt;strong&gt;阻塞于select或poll&lt;/strong&gt;，而没有阻塞于recv，有人将非阻塞IO定义成在读写操作时没有&lt;strong&gt;阻塞于系统调用的IO操作&lt;/strong&gt; (不包括数据从内核复制到用户空间时的阻塞，&lt;strong&gt;因为这相对于网络IO来说确实很短暂&lt;/strong&gt;)，&lt;strong&gt;如果按这样理解，这种IO模型也能称之为非阻塞IO模型&lt;/strong&gt;，&lt;strong&gt;但是按POSIX来看，它也是同步IO&lt;/strong&gt;，那么也和楼上一样称之为&lt;strong&gt;同步非阻塞IO&lt;/strong&gt;吧。&lt;/p&gt;
&lt;p&gt;这种IO模型比较特别，因为它能&lt;strong&gt;同时监听多个文件描述符(fd)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一旦某个文件描述符就绪（一般是读就绪或者写就绪），能够通知程序进行相应的读写操作（这样就&lt;strong&gt;不需要每个用户进程不断的询问内核数据准备好了没&lt;/strong&gt;）&lt;/p&gt;
&lt;p&gt;这个时候C同学来装水，发现&lt;strong&gt;有一排水龙头&lt;/strong&gt;，舍管阿姨告诉他这些水龙头都还没有水，等有水了告诉他。于是等啊等(select调用中)，&lt;strong&gt;过了一会阿姨告诉他有水了&lt;/strong&gt;，&lt;strong&gt;但不知道是哪个水龙头有水&lt;/strong&gt;，自己看吧。于是&lt;strong&gt;C同学一个个打开，往杯子里装水&lt;/strong&gt;(recv)。&lt;/p&gt;
&lt;p&gt;这里再顺便说说鼎鼎大名的epoll(高性能的代名词啊)，epoll也属于IO复用模型，&lt;/p&gt;
&lt;p&gt;主要区别在于舍管阿姨会&lt;strong&gt;告诉C同学哪几个水龙头有水了&lt;/strong&gt;，不需要一个个打开看(当然还有其它区别)。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;http://image.euphie.net/2017-09-24-23-21-54.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;信号驱动IO模型&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通过调用&lt;strong&gt;sigaction注册信号函数&lt;/strong&gt;，等内核数据准备好的时候系统中断当前程序，&lt;strong&gt;执行信号函数&lt;/strong&gt;(在这里面调用recv)。&lt;/p&gt;
&lt;p&gt;D同学让&lt;strong&gt;舍管阿姨等有水&lt;/strong&gt;的时候通知他(注册信号函数)，没多久D同学得知有水了，跑去装水。是不是很像异步IO？很遗憾，它还是同步IO(&lt;strong&gt;省不了装水的时间啊&lt;/strong&gt;)。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;http://image.euphie.net/2017-09-24-23-22-38.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;异步IO模型&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;调用aio_read&lt;/strong&gt;，&lt;strong&gt;让内核等数据准备好，并且复制到用户进程空间后执行事先指定好的函数&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;E同学让舍管阿姨将&lt;strong&gt;杯子装满水后通知他&lt;/strong&gt;。整个过程E同学&lt;strong&gt;都可以做别的事情(没有recv)&lt;/strong&gt;，&lt;strong&gt;这才是真正的异步IO。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;http://image.euphie.net/2017-09-24-23-23-36.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;最后，总结比较下五种IO模型：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://images2015.cnblogs.com/blog/1066890/201611/1066890-20161129014959615-1351089676.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;总结&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;IO分两阶段：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;1.数据准备阶段
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2.内核空间复制回用户进程缓冲区阶段
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;一般来讲：阻塞IO模型、非阻塞IO模型、IO复用模型(select/poll/epoll)、信号驱动IO模型都属于同步IO，因为阶段2是阻塞的(尽管时间很短)。&lt;/p&gt;
&lt;p&gt;只有异步IO模型是符合POSIX异步IO操作含义的，不管在阶段1还是阶段2都可以干别的事。&lt;/p&gt;
&lt;p&gt;IO分两阶段（一旦拿到数据后就变成了数据操作，不再是IO）：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1.数据准备阶段&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2.内核空间复制数据到用户进程缓冲区（用户空间）阶段&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;同步是用户&lt;strong&gt;线程&lt;/strong&gt;发起I/O请求后需要&lt;strong&gt;等待&lt;/strong&gt;或者&lt;strong&gt;轮询内核I/O操作&lt;/strong&gt;完成后才能继续执行&lt;/p&gt;
&lt;p&gt;异步是用户&lt;strong&gt;线程&lt;/strong&gt;发起I/O请求后仍&lt;strong&gt;需要继续执行&lt;/strong&gt;，&lt;strong&gt;当内核I/O操作完成后会通知用户线程&lt;/strong&gt;，或者调用用户线程注册的回调函数&lt;/p&gt;
&lt;p&gt;在操作系统中，程序运行的空间分为内核空间和用户空间。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;应用程序都是运行在用户空间的，所以它们能操作的数据也都在用户空间。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;阻塞IO和非阻塞IO的区别&lt;/strong&gt;在于&lt;strong&gt;第一步发起IO请求是否会被阻塞&lt;/strong&gt;： 如果阻塞直到完成那么就是传统的阻塞IO，如果不阻塞，那么就是非阻塞IO。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;同步IO和异步IO的区别&lt;/strong&gt;就在于&lt;strong&gt;第二个步骤是否阻塞&lt;/strong&gt;： 如果不阻塞，而是操作系统帮你做完IO操作再将结果返回给你，否则就是异步IO&lt;/p&gt;
&lt;p&gt;异步io的实现&lt;/p&gt;
&lt;p&gt;来看一下基本的异步读的操作流程，我们假定发起任务的时候运行是由主线程启动的，那么：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;注册者申请一个异步读任务，同时将自身的&lt;strong&gt;一个回调&lt;/strong&gt;注册给&lt;strong&gt;异步读管理器&lt;/strong&gt;。&lt;strong&gt;调用者在Dispose时，必须也将自身从异步读管理器中注销&lt;/strong&gt;。（主线程）&lt;/li&gt;
&lt;li&gt;管理器在收到任务后，&lt;strong&gt;将相关数据封包&lt;/strong&gt;，并&lt;strong&gt;启动一个新的线程（或者从线程池提取一个线程）来执行异步读任务&lt;/strong&gt;。（主线程）&lt;/li&gt;
&lt;li&gt;在子线程异步读完毕后，&lt;strong&gt;通知管理器提取数据&lt;/strong&gt;。（子线程）&lt;/li&gt;
&lt;li&gt;管理器处理封包，并&lt;strong&gt;通过调用注册者的回调来将数据重新推送下去&lt;/strong&gt;（子线程）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;可以看到，在这个过程中，&lt;strong&gt;发起任务和处理任务分别是在主线程和子线程进行的&lt;/strong&gt;，所以&lt;strong&gt;管理器自身必须有相应的同步机制来保证在不同线程上可以正确的运行。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们还需要考虑一个注册者同时发起多个读取任务的可能性。所以&lt;strong&gt;需要一定的机制来保证正确的区分这些任务&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在这里，我们可以选用一个较为简单的方法，即为每个注册者开辟一个单独的std::vector&amp;lt;&amp;gt;，&lt;strong&gt;并将这些任务按顺序放在vector中&lt;/strong&gt;，同时将&lt;strong&gt;每个任务对应的下标返回给注册者&lt;/strong&gt;。这样当读任务完成时，我们可以通过&lt;strong&gt;下标来告知注册者是哪个任务完成&lt;/strong&gt;了。&lt;/p&gt;
&lt;p&gt;读取缓冲区的&lt;strong&gt;分配与释放应该统一由管理器负责&lt;/strong&gt;，而不是注册者。&lt;/p&gt;
&lt;p&gt;因此&lt;strong&gt;注册者只能拿到一个const状态的缓冲区&lt;/strong&gt;，如果&lt;strong&gt;需要使用内容则需要将其复制到自有的缓冲区。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;64-如何服务更多的用户&#34;&gt;6.4 如何服务更多的用户
&lt;/h2&gt;&lt;p&gt;相信你知道 TCP 连接是由四元组唯一确认的，这个四元组就是：&lt;strong&gt;本机IP, 本机端口, 对端IP, 对端端口&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;服务器作为服务方，通常会在本地固定监听一个端口&lt;/strong&gt;，等待客户端的连接。因此服务器的本地 IP 和端口是固定的，于是对于服务端 TCP 连接的四元组只有对端 IP 和端口是会变化的，所以&lt;strong&gt;最大 TCP 连接数 = 客户端 IP 数×客户端端口数&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;对于 IPv4，客户端的 IP 数最多为 &lt;strong&gt;2 的 32 次方&lt;/strong&gt;，客户端的端口数最多为 &lt;strong&gt;2 的 16 次方&lt;/strong&gt;，也就是&lt;strong&gt;服务端单机最大 TCP 连接数约为 2 的 48 次方&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这个理论值相当“丰满”，但是服务器肯定承载不了那么大的连接数，主要会受两个方面的限制：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;文件描述符&lt;/strong&gt;，Socket 实际上是一个文件，也就会对应一个文件描述符。
&lt;ul&gt;
&lt;li&gt;在 Linux 下，单个进程打开的文件描述符数是有限制的，&lt;strong&gt;没有经过修改的值一般都是 1024，不过我们可以通过 ulimit 增大文件描述符的数目；&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;系统内存&lt;/strong&gt;，每个 TCP 连接&lt;strong&gt;在内核中都有对应的数据结构&lt;/strong&gt;，&lt;strong&gt;意味着每个连接都是会占用一定内存的&lt;/strong&gt;；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;那如果服务器的内存只有 2 GB，网卡是千兆的，能支持并发 1 万请求吗？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;并发 1 万请求，也就是经典的 C10K 问题 ，C 是 Client 单词首字母缩写，C10K 就是单机同时处理 1 万个请求的问题。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;从硬件资源角度看，&lt;strong&gt;对于 2GB 内存千兆网卡的服务器，如果每个请求处理占用不到 200KB 的内存和 100Kbit 的网络带宽就可以满足并发 1 万个请求。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;不过，要想真正实现 C10K 的服务器，要考虑的地方在于服务器的&lt;strong&gt;网络 I/O 模型&lt;/strong&gt;，效率低的模型，会加重系统开销，从而会离 C10K 的目标越来越远。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;基于最原始的阻塞网络 I/O&lt;/strong&gt;， 如果服务器要支持多个客户端，其中比较传统的方式，就是使用&lt;strong&gt;多进程模型&lt;/strong&gt;，也就是为每个客户端分配一个进程来处理请求。&lt;/p&gt;
&lt;p&gt;服务器的主进程负责监听客户的连接，一旦与客户端连接完成，accept() 函数就会返回一个「已连接 Socket」，这时就通过 &lt;code&gt;fork()&lt;/code&gt; 函数创建一个子进程，实际上就把父进程&lt;strong&gt;所有相关的东西都复制一份&lt;/strong&gt;，包括文件描述符、内存地址空间、程序计数器、执行的代码等。&lt;/p&gt;
&lt;p&gt;这两个进程刚复制完的时候，几乎一摸一样。&lt;/p&gt;
&lt;p&gt;不过，会根据&lt;strong&gt;返回值&lt;/strong&gt;来区分是父进程还是子进程，&lt;strong&gt;如果返回值是 0，则是子进程&lt;/strong&gt;；如果返回值是其他的整数，就是父进程。&lt;/p&gt;
&lt;p&gt;正因为子进程会&lt;strong&gt;复制父进程的文件描述符&lt;/strong&gt;，&lt;strong&gt;于是就可以直接使用「已连接 Socket 」和客户端通信了&lt;/strong&gt;，可以发现，&lt;strong&gt;子进程不需要关心「监听 Socket」，只需要关心「已连接 Socket」&lt;/strong&gt;；父进程则相反，将客户服务交给子进程来处理，因此父进程&lt;strong&gt;不需要关心「已连接 Socket」，只需要关心「监听 Socket」&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;下面这张图描述了从连接请求到连接建立，父进程创建生子进程为客户服务。&lt;/p&gt;
&lt;p&gt;另外，当「子进程」退出时，&lt;strong&gt;实际上内核里还会保留该进程的一些信息，也是会占用内存的，如果不做好“回收”工作&lt;/strong&gt;，就会变成&lt;strong&gt;僵尸进程&lt;/strong&gt;，随着僵尸进程越多，会慢慢&lt;strong&gt;耗尽我们的系统资源&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;因此，父进程要“善后”好自己的孩子，怎么善后呢？那么有两种方式可以在子进程退出后回收资源，&lt;strong&gt;分别是调用 &lt;code&gt;wait()&lt;/code&gt; 和 &lt;code&gt;waitpid()&lt;/code&gt; 函数&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这种用多个进程来应付多个客户端的方式，在应对 100 个客户端还是可行的，但是当客户端数量高达一万时，肯定扛不住的，因为每&lt;strong&gt;产生一个进程，必会占据一定的系统资源&lt;/strong&gt;，而且进程间&lt;strong&gt;上下文切换的“包袱”是很重&lt;/strong&gt;的，&lt;strong&gt;性能会大打折扣&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;进程的上下文切换不仅包含了&lt;strong&gt;虚拟内存、栈、全局变量等用户空间的资源&lt;/strong&gt;，&lt;strong&gt;还包括了内核堆栈、寄存器等内核空间的资源&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;那么，我们可以使用&lt;strong&gt;线程池的方式来避免线程的频繁创建和销毁&lt;/strong&gt;，所谓的线程池，&lt;strong&gt;就是提前创建若干个线程，这样当由新连接建立时，将这个已连接的 Socket 放入到一个队列里，然后线程池里的线程负责从队列中取出已连接 Socket 进程处理&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;一个进程虽然任一时刻只能处理一个请求，&lt;strong&gt;但是处理每个请求的事件时，耗时控制在 1 毫秒以内，这样 1 秒内就可以处理上千个请求&lt;/strong&gt;，&lt;/p&gt;
&lt;p&gt;把时间拉长来看，&lt;strong&gt;多个请求复用了一个进程，这就是多路复用&lt;/strong&gt;，这种思想很类似一个 &lt;strong&gt;CPU 并发多个进程&lt;/strong&gt;，所以也叫做&lt;strong&gt;时分多路复用&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我们熟悉的 select/poll/epoll 内核提供给用户态的&lt;strong&gt;多路复用系统调用&lt;/strong&gt;，&lt;strong&gt;进程可以通过一个系统调用函数从内核中获取多个事件&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;select/poll/epoll 是如何获取网络事件的呢？&lt;/p&gt;
&lt;p&gt;在获取事件时，&lt;strong&gt;先把所有连接（文件描述符）传给内核，再由内核返回产生了事件的连接，然后在用户态中再处理这些连接对应的请求即可。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;select/poll/epoll 这是三个多路复用接口，都能实现 C10K 吗？接下来，分别说说它们。&lt;/p&gt;
&lt;h2 id=&#34;65-selectpollepoll的原理区别&#34;&gt;6.5 select，poll，epoll的原理、区别
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;I/O多路复用就通过一种机制，可以监视多个描述符，一旦某个描述符就绪（一般是读就绪或者写就绪），能够通知程序进行相应的读写操作&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;select，poll，epoll都是IO多路复用的机制&lt;/strong&gt;。但select，poll，epoll本质上都是&lt;strong&gt;同步I/O，&lt;strong&gt;因为他们&lt;/strong&gt;都需要在读写事件就绪后自己负责进行读写&lt;/strong&gt;，&lt;strong&gt;也就是说这个读写过程是阻塞的。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;而异步I/O则无需自己负责进行读写，&lt;strong&gt;异步I/O的实现会负责把数据从内核拷贝到用户空间&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;select 的核心功能是调用tcp文件系统的poll函数&lt;/strong&gt;，&lt;strong&gt;不停的查询&lt;/strong&gt;，如果没有想要的数据，&lt;strong&gt;主动执行一次调度（防止一直占用cpu&lt;/strong&gt;），直到有一个连接有想要的消息为止。从这里可以看出select的执行方式基本就是不同的调用poll,直到有需要的消息为止。&lt;/p&gt;
&lt;h3 id=&#34;select&#34;&gt;select:
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;将已连接的 Socket 都放到一个文件描述符集合&lt;/strong&gt;，然后&lt;strong&gt;调用 select 函数&lt;/strong&gt;将文件描述符集合&lt;strong&gt;拷贝&lt;/strong&gt;到内核里，让内核来检查是否有网络事件产生，&lt;strong&gt;检查的方式很粗暴&lt;/strong&gt;，就是通过&lt;strong&gt;遍历文件描述符集合&lt;/strong&gt;的方式，&lt;/p&gt;
&lt;p&gt;当检查到有事件产生后，将此 Socket 标记为&lt;strong&gt;可读或可写&lt;/strong&gt;， &lt;strong&gt;接着再把整个文件描述符集合拷贝回用户态里&lt;/strong&gt;，&lt;strong&gt;然后用户态还需要再通过遍历&lt;/strong&gt;的方法&lt;strong&gt;找到可读或可写的 Socket&lt;/strong&gt;，然后再对其处理。&lt;/p&gt;
&lt;p&gt;所以，对于 select 这种方式，&lt;strong&gt;需要进行&lt;/strong&gt; &lt;strong&gt;2 次「遍历」文件描述符集合&lt;/strong&gt;，一次是在内核态里，一个次是在用户态里 ，&lt;strong&gt;而且还会&lt;/strong&gt;发生 &lt;strong&gt;2 次「拷贝」文件描述符集合&lt;/strong&gt;，先从用户空间传入内核空间，由内核修改后，再传出到用户空间中。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;select 使用固定长度的 BitsMap&lt;/strong&gt;，&lt;strong&gt;表示文件描述符集合&lt;/strong&gt;，而且所支持的文件描述符的个数是有限制的，在 Linux 系统中，由内核中的 &lt;strong&gt;FD_SETSIZE 限制， 默认最大值为 &lt;code&gt;1024&lt;/code&gt;，只能监听 0~1023 的文件描述符&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;缺点：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;1、&lt;strong&gt;每次调用select，都需要把fd集合从用户态拷贝到内核态，这个开销在fd很多时会很大；&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;2、同时每次调用select都需要在内核&lt;strong&gt;遍历传递进来的所有fd&lt;/strong&gt;，这个开销在fd很多时也很大；&lt;/p&gt;
&lt;p&gt;3、select支持的&lt;strong&gt;文件描述符数量太小了&lt;/strong&gt;，&lt;strong&gt;默认是1024&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优点：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;1、&lt;strong&gt;select的可移植性更好&lt;/strong&gt;，在某些Unix系统上不支持poll()。&lt;/p&gt;
&lt;p&gt;2、select对于&lt;strong&gt;超时值提供了更好的精度：微秒，而poll是毫秒。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;poll&#34;&gt;Poll
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;poll本质上和select没有区别&lt;/strong&gt;，&lt;strong&gt;poll 不再用 BitsMap 来存储所关注的文件描述符，取而代之用动态数组，以链表形式来组织&lt;/strong&gt;，突破了 &lt;strong&gt;select 的文件描述符个数限制&lt;/strong&gt;，当然&lt;strong&gt;还会受到系统文件描述符限制。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;它将用户&lt;strong&gt;传入的数组拷贝到内核空间&lt;/strong&gt;，然后查询&lt;strong&gt;每个fd对应的设备状态&lt;/strong&gt;，&lt;strong&gt;如果设备就绪则在设备等待队列中加入一项并继续遍历&lt;/strong&gt;，&lt;strong&gt;如果遍历完所有fd后没有发现就绪设备&lt;/strong&gt;，则&lt;strong&gt;挂起当前进程&lt;/strong&gt;，直到&lt;strong&gt;设备就绪或者主动超时&lt;/strong&gt;，&lt;strong&gt;被唤醒后它又要再次遍历fd&lt;/strong&gt;。这个过程经历了多次无谓的遍历。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;poll还有一个特点是“水平触发”&lt;/strong&gt;，如果报告了fd后，没有被处理，那么下次poll时会再次报告该fd。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;缺点：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;1、&lt;strong&gt;大量的fd的数组被整体复制于用户态和内核地址空间之间&lt;/strong&gt;，而不管这样的复制是不是有意义；&lt;/p&gt;
&lt;p&gt;2、与select一样，poll返回后，需要轮询&lt;strong&gt;pollfd来获取就绪的描述符&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优点：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;1、poll() &lt;strong&gt;不要求开发者计算最大文件描述符加一的大小&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;2、poll() 在应付&lt;strong&gt;大数目的文件描述符的时候速度更快&lt;/strong&gt;，相比于select。&lt;/p&gt;
&lt;p&gt;3、&lt;strong&gt;它没有最大连接数的限制&lt;/strong&gt;，原因是它是基于&lt;strong&gt;链表来存储的。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;但是 poll 和 select &lt;strong&gt;并没有太大的本质区别&lt;/strong&gt;，&lt;strong&gt;都是使用「线性结构」存储进程关注的 Socket 集合，因此都需要遍历文件描述符集合来找到可读或可写的 Socket，时间复杂度为 O(n)，而且也需要在用户态与内核态之间拷贝文件描述符集合&lt;/strong&gt;，这种方式随着并发数上来，性能的损耗会呈指数级增长。&lt;/p&gt;
&lt;h3 id=&#34;epoll&#34;&gt;&lt;strong&gt;epoll&lt;/strong&gt;
&lt;/h3&gt;&lt;p&gt;epoll同样&lt;strong&gt;只告知那些就绪的文件描述符&lt;/strong&gt;，而且当我们调用epoll_wait()获得就绪文件描述符时， &lt;strong&gt;返回的不是实际的描述符&lt;/strong&gt;，&lt;strong&gt;而是一个代表就绪描述符数量的值&lt;/strong&gt;，你只需要&lt;strong&gt;去epoll指定的一个数组中依次取得相应数量的文件描述符即可.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这里也使用了&lt;strong&gt;内存映射技术&lt;/strong&gt;，&lt;strong&gt;这样便彻底省掉了这些文件描述符在系统调用时复制的开销&lt;/strong&gt;。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;epoll 在内核里使用&lt;strong&gt;红黑树来跟踪进程所有待检测的文件描述字&lt;/strong&gt;，把需要监控的 socket 通过 &lt;strong&gt;&lt;code&gt;epoll_ctl()&lt;/code&gt;&lt;/strong&gt; 函数加入&lt;strong&gt;内核中的红黑树&lt;/strong&gt;里（&lt;strong&gt;红黑树是个高效的数据结构&lt;/strong&gt;，增删查一般时间复杂度是 &lt;code&gt;O(logn)）&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;通过对&lt;strong&gt;这棵黑红树进行操作&lt;/strong&gt;，这样就不需要像 &lt;strong&gt;select/poll 每次操作时都传入整个 socket 集合&lt;/strong&gt;，&lt;strong&gt;只需要传入一个待检测的 socket&lt;/strong&gt;，&lt;strong&gt;减少了内核和用户空间大量的数据拷贝和内存分配。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;epoll 使用&lt;strong&gt;事件驱动的机制&lt;/strong&gt;，内核里&lt;strong&gt;维护了一个链表来记录就绪事件&lt;/strong&gt;，当某个 socket 有事件发生时，&lt;strong&gt;通过回调函数内核会将其加入到这个就绪事件列表中&lt;/strong&gt;，当用户调用 &lt;strong&gt;&lt;code&gt;epoll_wait()&lt;/code&gt;&lt;/strong&gt; 函数时，&lt;strong&gt;只会返回有事件发生的文件描述符的个数&lt;/strong&gt;，&lt;strong&gt;不需要像 select/poll 那样轮询扫描整个 socket 集合&lt;/strong&gt;，大大提高了检测的效率。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;epoll 被称为解决 C10K 问题的利器&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;epoll的优点就是改进了前面所说缺点：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;支持一个进程打开大数目的socket描述符&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;相比select**，epoll则没有对FD的限制，它所支持的FD上限是最大可以打开文件的数目**，&lt;strong&gt;这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右&lt;/strong&gt;，具体数目可以&lt;strong&gt;cat /proc/sys/fs/file-max&lt;/strong&gt;察看,一般来说这个数目和系统内存关系很大。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;IO效率不随FD数目增加而线性下降&lt;/strong&gt;：epoll不存在这个问题，它只会对“活跃”的socket进行操作— 这是因为在内核实现中epoll是根据&lt;strong&gt;每个fd上面的callback函数实现的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;那么，&lt;strong&gt;只有“活跃”的socket才会主动的去调用 callback函&lt;/strong&gt;数，其他idle状态socket则不会，在这点上，epoll实现了一个“伪”AIO，因为这时候&lt;strong&gt;推动力在os内核。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在一些 benchmark中，如果所有的socket基本上都是活跃的—比如一个高速LAN环境，epoll并不比select/poll有什么效率，相反，如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;使用mmap加速内核与用户空间的消息传递&lt;/strong&gt;：这点实际上涉及到epoll的具体实现了。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;无论是select,poll还是epoll都需要内核把FD消息通知给用户空间，&lt;strong&gt;如何避免不必要的内存拷贝就 很重要，在这点上，epoll是通过内核于用户空间mmap同一块内存实现的。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;执行epoll_ create时，创建了红黑树和就绪链表；&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;执行epoll_ ctl时，如果增加socket句柄，则检查在红黑树中是否存在，存在立即返回，不存在则添加到树干上。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;然后向内核注册回调函数，用于当中断事件来临时向准备就绪链表中插入数据。执行epoll_wait时立刻返回准备就绪链表里的数据即可。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;(1) select==&amp;gt;时间复杂度O(n)&lt;/p&gt;
&lt;p&gt;它仅仅知道了，有I/O事件发生了，却并不知道是哪那几个流（可能有一个，多个，甚至全部），我们只能无差别轮询所有流，找出能读出数据，或者写入数据的流，对他们进行操作。所以&lt;strong&gt;select具有O(n)的无差别轮询复杂度&lt;/strong&gt;，同时&lt;strong&gt;处理的流越多，无差别轮询时间就越长&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;(2) poll==&amp;gt;时间复杂度O(n)&lt;/p&gt;
&lt;p&gt;poll本质上和select没有区别，它将用户传入的链式数组拷贝到内核空间，然后查询每个&lt;strong&gt;fd对应的设备状态&lt;/strong&gt;， &lt;strong&gt;但是它没有最大连接数的限制&lt;/strong&gt;，原因是它是&lt;strong&gt;基于链表来存储的.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;(3) epoll==&lt;strong&gt;&amp;gt;时间复杂度O(1)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;epoll可以理解为event poll&lt;/strong&gt;，不同于忙轮询和无差别轮询，epoll会把哪个流发生了怎样的&lt;strong&gt;I/O事件通知我们&lt;/strong&gt;。所以我们说epoll实际上是&lt;strong&gt;事件驱动（每个事件关联上fd）&lt;strong&gt;的，此时我们对这些&lt;/strong&gt;流的操作都是有意义的&lt;/strong&gt;。&lt;strong&gt;（复杂度降低到了O(1)）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;水平触发&lt;/strong&gt;(level-triggered，也被称为条件触发)LT: &lt;strong&gt;只要满足条件，就触发一个事件&lt;/strong&gt; (&lt;strong&gt;只要有数据没有被获取，内核就不断通知你&lt;/strong&gt;)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;边缘触发&lt;/strong&gt;(edge-triggered)ET: &lt;strong&gt;每当状态变化时，触发一个事件。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;epoll 支持边缘触发和水平触发的方式，而 select/poll &lt;strong&gt;只支持水平触发&lt;/strong&gt;，&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一般而言，边缘触发的方式会比水平触发的效率高。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;66-dma和零拷贝&#34;&gt;6.6 DMA和零拷贝
&lt;/h2&gt;&lt;p&gt;可以看到，&lt;strong&gt;整个数据的传输过程，都要需要 CPU 亲自参与搬运数据的过程&lt;/strong&gt;，而且这个过程，CPU 是不能做其他事情的。&lt;/p&gt;
&lt;p&gt;简单的搬运几个字符数据那没问题，但是如果我们用千兆网卡或者硬盘传输大量数据的时候，都用 CPU 来搬运的话，肯定忙不过来。&lt;/p&gt;
&lt;p&gt;计算机科学家们发现了事情的严重性后，于是就发明了 DMA 技术，也就是&lt;strong&gt;直接内存访问（&lt;em&gt;Direct Memory Access&lt;/em&gt;）&lt;/strong&gt; 技术。&lt;/p&gt;
&lt;p&gt;什么是 DMA 技术？简单理解就是，&lt;strong&gt;在进行 I/O 设备和内存的数据传输的时候，数据搬运的工作全部交给 DMA 控制器，而 CPU 不再参与任何与数据搬运相关的事情，这样 CPU 就可以去处理别的事务&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户进程&lt;strong&gt;调用 read 方法，向操作系统发出 I/O 请求&lt;/strong&gt;，请求读取数据到自己的内存缓冲区中，&lt;strong&gt;进程进入阻塞状态&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;操作系统收到请求后&lt;/strong&gt;，&lt;strong&gt;进一步将 I/O 请求发送 DMA，然后让 CPU 执行其他任务&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;DMA 进一步将 &lt;strong&gt;I/O 请求发送给磁盘&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;磁盘收到 DMA 的 I/O 请求，把数据从磁盘读取到磁盘控制器的缓冲区中&lt;/strong&gt;，当磁盘控制器的&lt;strong&gt;缓冲区被读满后，向 DMA 发起中断信号&lt;/strong&gt;，告知自己缓冲区已满；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DMA 收到磁盘的信号，将磁盘控制器缓冲区中的数据拷贝到内核缓冲区中，此时不占用 CPU，CPU 可以执行其他任务&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;当 &lt;strong&gt;DMA 读取了足够多的数据&lt;/strong&gt;，就会发送&lt;strong&gt;中断信号给 CPU&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CPU 收到 DMA 的信号，知道数据已经准备好&lt;/strong&gt;，于是&lt;strong&gt;将数据从内核拷贝到用户空间，系统调用返回；&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可以看到， 整个数据传输的过程，CPU 不再参与数据搬运的工作，而是全程由 DMA 完成，&lt;/p&gt;
&lt;p&gt;但是 CPU 在这个过程中&lt;strong&gt;也是必不可少&lt;/strong&gt;的，&lt;strong&gt;因为传输什么数据，从哪里传输到哪里，都需要 CPU 来告诉 DMA 控制器。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;早期 DMA 只存在在主板上，&lt;strong&gt;如今由于 I/O 设备越来越多&lt;/strong&gt;，数据传输的需求也不尽相同，&lt;strong&gt;所以每个 I/O 设备里面都有自己的 DMA 控制器。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;传统 I/O 的工作方式是，&lt;strong&gt;数据读取和写入是从用户空间到内核空间来回复制，而内核空间的数据是通过操作系统层面的 I/O 接口从磁盘读取或写入。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;第一次拷贝&lt;/em&gt;，把&lt;strong&gt;磁盘上的数据拷贝到操作系统内核的缓冲区里&lt;/strong&gt;，这个拷贝的过程是&lt;strong&gt;通过 DMA 搬运的&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;em&gt;第二次拷贝&lt;/em&gt;，把&lt;strong&gt;内核缓冲区的数据拷贝到用户的缓冲区里&lt;/strong&gt;，于是我们&lt;strong&gt;应用程序就可以使用这部分数据&lt;/strong&gt;了，&lt;strong&gt;这个拷贝到过程是由 CPU 完成的。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;第三次拷贝&lt;/em&gt;，把&lt;strong&gt;刚才拷贝到用户的缓冲区里的数据，再拷贝到内核的 socket 的缓冲区里&lt;/strong&gt;，&lt;strong&gt;这个过程依然还是由 CPU 搬运的。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;第四次拷贝&lt;/em&gt;，&lt;strong&gt;把内核的 socket 缓冲区里的数据，拷贝到网卡的缓冲区里，这个过程又是由 DMA 搬运的。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.jsdelivr.net/gh/xiaolincoder/ImageHost2/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E9%9B%B6%E6%8B%B7%E8%B4%9D/%E4%BC%A0%E7%BB%9F%E6%96%87%E4%BB%B6%E4%BC%A0%E8%BE%93.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;h3 id=&#34;如何实现零拷贝&#34;&gt;如何实现零拷贝？
&lt;/h3&gt;&lt;p&gt;零拷贝技术实现的方式通常有 2 种：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;mmap + write&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;sendfile&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下面就谈一谈，它们是如何减少「上下文切换」和「数据拷贝」的次数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;mmap()&lt;/code&gt;&lt;/strong&gt; 系统调用函数会直接把&lt;strong&gt;内核缓冲区里的数据「映射」到用户空间&lt;/strong&gt;，&lt;strong&gt;这样，操作系统内核与用户空间就不需要再进行任何的数据拷贝操作。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;应用进程调用了 &lt;code&gt;mmap()&lt;/code&gt; 后，DMA 会把磁盘的数据拷贝到内核的缓冲区里&lt;/strong&gt;。接着，&lt;strong&gt;应用进程跟操作系统内核「共享」这个缓冲区；&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;应用进程再调用 &lt;strong&gt;&lt;code&gt;write()&lt;/code&gt;&lt;/strong&gt;，操作系统直接将&lt;strong&gt;内核缓冲区的数据拷贝到 socket 缓冲区中&lt;/strong&gt;，这一切&lt;strong&gt;都发生在内核态，由 CPU 来搬运数据&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;最后，把内核的 socket 缓冲区里的数据，&lt;strong&gt;拷贝到网卡的缓冲区里&lt;/strong&gt;，这个过程是由 DMA 搬运的&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我们可以得知，通过使用 &lt;strong&gt;&lt;code&gt;mmap()&lt;/code&gt; 来代替 &lt;code&gt;read()&lt;/code&gt;&lt;/strong&gt;， 可以减少一次数据拷贝的过程。&lt;/p&gt;
&lt;p&gt;但这还不是最理想的零拷贝，因为仍然需要通过 CPU 把内核缓冲区的数据拷贝到 socket 缓冲区里，而且仍&lt;strong&gt;然需要 4 次上下文切换，因为系统调用还是 2 次&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;你可以在你的 Linux 系统通过下面这个命令，查看网卡是否支持 &lt;strong&gt;scatter-gather&lt;/strong&gt; 特性：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ ethtool -k eth0 &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; grep scatter-gatherscatter-gather: on
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;于是，从 Linux 内核 &lt;code&gt;2.4&lt;/code&gt; 版本开始起，对于支持&lt;strong&gt;网卡支持 SG-DMA 技术&lt;/strong&gt;的情况下， &lt;code&gt;sendfile()&lt;/code&gt; 系统调用的过程发生了点变化，具体过程如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一步，通过 &lt;strong&gt;DMA 将磁盘上的数据拷贝到内核缓冲区里&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;第二步，&lt;strong&gt;缓冲区描述符和数据长度传到 socket 缓冲区&lt;/strong&gt;，这样&lt;strong&gt;网卡的 SG-DMA 控制器就可以直接将内核缓存中的数据拷贝到网卡的缓冲区里，&lt;strong&gt;此过程不需要将数据&lt;/strong&gt;从操作系统内核缓冲区拷贝到 socket 缓冲区中&lt;/strong&gt;，这样就减少了一次数据拷贝；&lt;/li&gt;
&lt;li&gt;所以，这个过程之中，只进行了 2 次数据拷贝&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这就是所谓的&lt;strong&gt;零拷贝（&lt;em&gt;Zero-copy&lt;/em&gt;）技术，因为我们没有在内存层面去拷贝数据，也就是说全程没有通过 CPU 来搬运数据，所有的数据都是通过 DMA 来进行传输的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;零拷贝技术的文件传输方式相比传统文件传输的方式，减少了 2 次上下文切换和数据拷贝次数，&lt;strong&gt;只需要 2 次上下文切换和数据拷贝次数，就可以完成文件的传输，而且 2 次的数据拷贝过程，都不需要通过 CPU，2 次都是由 DMA 来搬运。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;所以，总体来看，&lt;strong&gt;零拷贝技术可以把文件传输的性能提高至少一倍以上&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;零拷贝技术&lt;/strong&gt;是基于 &lt;strong&gt;PageCache 的&lt;/strong&gt;，&lt;strong&gt;PageCache 会缓存最近访问的数据，提升了访问缓存数据的性能。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;同时，为了解决机械硬盘寻址慢的问题，&lt;strong&gt;它还协助 I/O 调度算法实现了 IO 合并与预读&lt;/strong&gt;，这也是&lt;strong&gt;顺序读比随机读性能好&lt;/strong&gt;的原因。这些优势，进一步提升了零拷贝的性能。&lt;strong&gt;PageCache 使用了「预读功能」&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;比如，假设 read 方法每次只会读 &lt;code&gt;32 KB&lt;/code&gt; 的字节，虽然 read 刚开始只会读 0 ～ 32 KB 的字节，但&lt;strong&gt;内核会把其后面的 32～64 KB 也读取到 PageCache&lt;/strong&gt;，这样后面读取 32～64 KB 的成本就很低，&lt;strong&gt;如果在 32～64 KB 淘汰出 PageCache 前，进程读取到它了，收益就非常高。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;事实上，Kafka 这个开源项目，就利用了「零拷贝」技术&lt;/strong&gt;，&lt;strong&gt;从而大幅提升了 I/O 的吞吐率，这也是 Kafka 在处理海量数据为什么这么快的原因之一。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;另外，&lt;strong&gt;Nginx 也支持零拷贝技术&lt;/strong&gt;，&lt;strong&gt;一般默认是开启零拷贝技术&lt;/strong&gt;，&lt;strong&gt;这样有利于提高文件传输的效率&lt;/strong&gt;，是否开启零拷贝技术的配置如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.jsdelivr.net/gh/xiaolincoder/ImageHost2/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E9%9B%B6%E6%8B%B7%E8%B4%9D/senfile-%E9%9B%B6%E6%8B%B7%E8%B4%9D.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;h2 id=&#34;67-大文件传输&#34;&gt;6.7 大文件传输
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;应用程序已经实现了磁盘数据的缓存，那么可以不需要 PageCache 再次缓存，减少额外的性能损耗&lt;/strong&gt;。在 MySQL 数据库中，可以&lt;strong&gt;通过参数设置开启直接 I/O，默认是不开启；&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;传输大文件的时候，&lt;strong&gt;由于大文件难以命中 PageCache 缓存&lt;/strong&gt;，&lt;strong&gt;而且会占满 PageCache 导致「热点」文件无法充分利用缓存，从而增大了性能开销&lt;/strong&gt;，因此，&lt;strong&gt;这时应该使用直接 I/O&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;另外，由于直接 I/O 绕过了 PageCache，就无法享受内核的这两点的优化：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;内核的 I/O 调度算法会缓存尽可能多的 I/O 请求在 PageCache 中&lt;/strong&gt;，最后「&lt;strong&gt;合并」成一个更大的 I/O 请求再发给磁盘&lt;/strong&gt;，这样做是为了&lt;strong&gt;减少磁盘的寻址操&lt;/strong&gt;作；&lt;/li&gt;
&lt;li&gt;内核也会「&lt;strong&gt;预读&lt;/strong&gt;」&lt;strong&gt;后续的 I/O 请求&lt;/strong&gt;放在 PageCache 中，一样是为了减少对磁盘的操作；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;于是，**传输大文件的时候，使用「异步 I/O + 直接 I/O」**了，&lt;strong&gt;就可以无阻塞地读取文件了。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;所以，传输文件的时候，我们要根据文件的大小来使用不同的方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;传输大文件的时候，使用「异步 I/O + 直接 I/O」；&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;传输小文件的时候，则使用「零拷贝技术」；&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;另外，当传输大文件时，不能使用零拷贝，因为可能由于 PageCache 被大文件占据，&lt;strong&gt;而导致「热点」小文件无法利用到 PageCache&lt;/strong&gt;，并且大文件的缓存命中率不高，这时就需要使用「异步 IO + 直接 IO 」的方式。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;在 Nginx 里，可以通过配置，设定一个文件大小阈值&lt;/strong&gt;，&lt;strong&gt;针对大文件使用异步 IO 和直接 IO，而对小文件使用零拷贝。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;68-socket模型&#34;&gt;6.8 Socket模型
&lt;/h2&gt;&lt;p&gt;要想客户端和服务器能在网络中通信，那必须得使用 Socket 编程，它是进程间通信里比较特别的方式，特别之处在于它是可以跨主机间通信。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;服务端和客户端初始化 &lt;code&gt;socket&lt;/code&gt;，得到文件描述符；&lt;/li&gt;
&lt;li&gt;服务端调用 &lt;code&gt;bind&lt;/code&gt;，将绑定在 IP 地址和端口;&lt;/li&gt;
&lt;li&gt;服务端调用 &lt;code&gt;listen&lt;/code&gt;，进行监听；&lt;/li&gt;
&lt;li&gt;服务端调用 &lt;code&gt;accept&lt;/code&gt;，等待客户端连接；&lt;/li&gt;
&lt;li&gt;客户端调用 &lt;code&gt;connect&lt;/code&gt;，向服务器端的地址和端口发起连接请求；&lt;/li&gt;
&lt;li&gt;服务端 &lt;code&gt;accept&lt;/code&gt; 返回用于&lt;strong&gt;传输的 &lt;code&gt;socket&lt;/code&gt; 的文件描述符；&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;客户端调用 &lt;code&gt;write&lt;/code&gt; 写入数据；&lt;strong&gt;服务端调用 &lt;code&gt;read&lt;/code&gt; 读取数据；&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;客户端断开连接时，会调用 &lt;code&gt;close&lt;/code&gt;，&lt;strong&gt;那么服务端 &lt;code&gt;read&lt;/code&gt; 读取数据的时候，就会读取到了 &lt;code&gt;EOF&lt;/code&gt;&lt;/strong&gt;，待处理完数据后，服务端调用 &lt;code&gt;close&lt;/code&gt;，表示连接关闭。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;创建 Socket 的时候，可以指定&lt;strong&gt;网络层使用的是 IPv4 还是 IPv6，传输层使用的是 TCP 还是 UDP。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;主线程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;创建完成端口对象&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;创建工作者线程&lt;/strong&gt;（这里工作者线程的数量是按照CPU核的个数来决定，这样可以达到最佳性能）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;创建监听套接字，绑定，监听，然后程序进入循环&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在循环中，做了以下几件事情：&lt;/p&gt;
&lt;p&gt;(1) &lt;strong&gt;接受一个客户端连接&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;(2) 将&lt;strong&gt;该客户端套接字与完成端口绑定到一起&lt;/strong&gt;(还是调用CreateIoCompletionPort，但这次的作用不同)。&lt;/p&gt;
&lt;p&gt;注意，按道理来讲，此时传递给CreateIoCompletionPort的第三个参数应该是一个完成键，一般来讲，**程序都是传递一个单句柄数据结构的地址，**该单句柄数据包含了和该客户端连接有关的信息，由于我们只关心套接字句柄，&lt;strong&gt;所以直接将套接字句柄作为完成键传递；&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;(3) 触发一个WSARecv异步调用，这次又用到了“&lt;strong&gt;尾随数据”，使接收数据所用的缓冲区紧跟在WSAOVERLAPPED对象之后&lt;/strong&gt;，此外，还有操作 类型等重要信息。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h1 id=&#34;7-设备管理&#34;&gt;7. 设备管理
&lt;/h1&gt;&lt;p&gt;我们的电脑设备可以接非常多的输入输出设备，比如键盘、鼠标、显示器、网卡、硬盘、打印机、音响等等，每个设备的用法和功能都不同，那操作系统是如何把这些输入输出设备统一管理的呢?&lt;/p&gt;
&lt;p&gt;为了屏蔽设备之间的差异，&lt;strong&gt;每个设备都有一个叫设备控制器（&lt;em&gt;Device Control&lt;/em&gt;）&lt;/strong&gt; 的组件，比如硬盘有硬盘控制器、显示器有视频控制器等。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&lt;strong&gt;数据寄存器&lt;/strong&gt;&lt;/em&gt;，&lt;strong&gt;CPU 向 I/O 设备写入需要传输的数据&lt;/strong&gt;，比如要打印的内容是「Hello」，CPU 就要先发送一个 H 字符给到对应的 I/O 设备。&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;strong&gt;命令寄存器&lt;/strong&gt;&lt;/em&gt;，&lt;strong&gt;CPU 发送一个命令，告诉 I/O 设备，要进行输入/输出操作，于是就会交给 I/O 设备去工作&lt;/strong&gt;，任务完成后，会把状态寄存器里面的状态标记为完成。&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;strong&gt;状态寄存器&lt;/strong&gt;&lt;/em&gt;，目的是告诉 CPU ，&lt;strong&gt;现在已经在工作或工作已经完成，如果已经在工作状态，CPU 再发送数据或者命令过来，都是没有用的，直到前面的工作已经完成&lt;/strong&gt;，状态寄存标记成已完成，CPU 才能发送下一个字符和命令。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;71-键盘敲入字母时期间发生了什么&#34;&gt;7.1 键盘敲入字母时，期间发生了什么？
&lt;/h2&gt;&lt;p&gt;那当用户输入了键盘字符，&lt;strong&gt;键盘控制器&lt;/strong&gt;就会产生&lt;strong&gt;扫描码数据&lt;/strong&gt;，并将其缓冲在&lt;strong&gt;键盘控制器的寄存器&lt;/strong&gt;中，紧接着键盘控制器通过&lt;strong&gt;总线给 CPU&lt;/strong&gt; 发送&lt;strong&gt;中断请求&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;CPU 收到中断请求后，操作系统会&lt;strong&gt;保存被中断进程的 CPU 上下文&lt;/strong&gt;，然后调用键盘的&lt;strong&gt;中断处理程序&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;键盘的中断处理程序是在键盘驱动程序初始化时注册的&lt;/strong&gt;，那键盘&lt;strong&gt;中断处理函数的功能就是从键盘控制器的寄存器的缓冲区读取扫描码，再根据扫描码找到用户在键盘输入的字符&lt;/strong&gt;，**如果输入的字符是显示字符，那就会把扫描码翻译成对应显示字符的 ASCII 码（**比如用户在键盘输入的是字母 A，是显示字符，于是就会把扫描码翻译成 A 字符的 ASCII 码）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;得到了显示字符的 ASCII 码后，就会把 ASCII 码放到「读缓冲区队列」，接下来就是要把显示字符在屏幕上了，显示设备的驱动程序会定时从「读缓冲区队列」读取数据放到「写缓冲区队列」&lt;/strong&gt;，&lt;strong&gt;最后把「写缓冲区队列」的数据一个一个写入到显示设备的控制器的寄存器中的数据缓冲区，最后将这些数据显示在屏幕里。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;显示出结果后，恢复被中断进程的上下文&lt;/strong&gt;。&lt;/p&gt;
</description>
        </item>
        <item>
        <title>计算机网络常见问题1</title>
        <link>https://Salmooo.github.io/p/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%981/</link>
        <pubDate>Mon, 18 Nov 2019 21:45:27 +0800</pubDate>
        
        <guid>https://Salmooo.github.io/p/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%981/</guid>
        <description>&lt;h1 id=&#34;问题&#34;&gt;问题
&lt;/h1&gt;&lt;h2 id=&#34;71-浏览器中输入url地址到显示主页的过程是什么&#34;&gt;7.1 浏览器中输入URL地址到显示主页的过程是什么？
&lt;/h2&gt;&lt;p&gt;&lt;img src=&#34;https://uk-1259555870.cos.eu-frankfurt.myqcloud.com/20200214133606.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;URL解析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;地址解析：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;首先判断你输入的是一个&lt;strong&gt;合法的 URL 还是一个待搜索的关键词&lt;/strong&gt;，并且根据你输入的内容进行&lt;strong&gt;自动完成、字符编码&lt;/strong&gt;等操作。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;HSTS&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;由于安全隐患，会使用 HSTS 强制客户端使用 HTTPS 访问页面。详见：&lt;a class=&#34;link&#34; href=&#34;https://www.barretlee.com/blog/2015/10/22/hsts-intro/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;你所不知道的 HSTS&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;其他操作&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;浏览器还会进行一些额外的操作，比如安全检查、访问限制（&lt;strong&gt;之前国产浏览器限制 996.icu）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;检查缓存304&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://pic1.zhimg.com/v2-0489444034d569b37867e2e527a7d5d4_r.jpg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DNS解析&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 浏览器缓存&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;浏览器会先检查是否在浏览器缓存中&lt;/strong&gt;，没有则调用系统库函数进行查询。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 操作系统缓存&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;操作系统也有自己的 DNS缓存，但在这之前，&lt;strong&gt;会向检查域名是否存在本地的 Hosts 文件里&lt;/strong&gt;，没有则向 DNS 服务器发送查询请求。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 路由器缓存&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;路由器也有自己的缓存。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. ISP DNS 缓存&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ISP DNS 就是在客户端电脑上设置的首选 DNS 服务器，它们在大多数情况下都会有缓存。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;根域名服务器查询&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在前面所有步骤没有缓存的情况下，本地 DNS 服务器会将请求转发到互联网上的根域，下面这个图很好的诠释了整个流程：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://pic2.zhimg.com/v2-57eb007db72ad239123d56448a2a1d01_r.jpg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;根域名服务器：维基百科&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;需要注意的点&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;递归方式：&lt;strong&gt;一路查下去中间不返回，得到最终结果才返回信息&lt;/strong&gt;（&lt;strong&gt;浏览器到本地DNS服务器的过程&lt;/strong&gt;）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;迭代方式，就是&lt;strong&gt;本地DNS服务器到根域名服务器查询的方式。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;什么是 DNS 劫持&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DNS劫持又叫域名劫持&lt;/strong&gt;,指攻击者利用其他攻击手段,&lt;strong&gt;篡改了某个域名的解析结果&lt;/strong&gt;,使得指向该域名的IP变成了另一个IP,导致对相应网址的访问被劫持到&lt;strong&gt;另一个不可达的或者假冒的网址&lt;/strong&gt;,从&lt;strong&gt;而实现非法窃取用户信息或者破坏正常网络服务的目的&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;前端 dns-prefetch 优化&lt;/p&gt;
&lt;p&gt;DNS预获取，是前端优化的一部分。 一个&lt;strong&gt;是减少DNS的请求次数&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;TCP连接&lt;/p&gt;
&lt;p&gt;根据IP建立TCP连接（三次握手）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;发送HTTP请求&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;服务器处理请求并&lt;strong&gt;返回HTTP报文&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;浏览器解析渲染页面&lt;/p&gt;
&lt;p&gt;渲染页面，构建DOM树&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;连接结束&lt;/p&gt;
&lt;p&gt;关闭TCP连接（四次挥手）。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;72-ping命令的具体过程是什么&#34;&gt;7.2 ping命令的具体过程是什么？
&lt;/h2&gt;&lt;p&gt;简单来说，「ping」是用来探测&lt;strong&gt;本机与网络中另一主机之间是否可达的命令&lt;/strong&gt;，如果两台主机之间ping不通，则表明这两台主机不能建立起连接。ping是定位网络通不通的一个重要手段。&lt;/p&gt;
&lt;p&gt;ping 命令是基于 &lt;strong&gt;ICMP 协议&lt;/strong&gt;来工作的，「 ICMP 」全称为 &lt;strong&gt;Internet 控制报文协议&lt;/strong&gt;（ Internet Control Message Protocol）。&lt;/p&gt;
&lt;p&gt;ping 命令会发送一份ICMP回显请求报文给目标主机，并等待目标主机返回ICMP回显应答。因为&lt;strong&gt;ICMP协议会要求目标主机在收到消息之后&lt;/strong&gt;，必须&lt;strong&gt;返回ICMP应答消息&lt;/strong&gt;给源主机，如果源主机&lt;strong&gt;在一定时间内收到了目标主机的应答，则表明两台主机之间网络是可达的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;假设现在有ABCD四台主机，一台路由，子网掩码为255.255.255.0，默认路由为192.168.0.1&lt;/p&gt;
&lt;p&gt;在主机 A 上运行&lt;code&gt;Ping 192.168.0.5&lt;/code&gt;后,&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ping命令&lt;strong&gt;会构建一个ICMP协议的数据包，交到网络层的IP协议中&lt;/strong&gt;。IP层协议将目的地址和源地址和一些其它的控制信息打包后，形成IP数据包&lt;/li&gt;
&lt;li&gt;通过&lt;strong&gt;ARP映射表获取192.168.0.5的MAC地址&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;交到数据链路层，添加一些控制信息，构建数据帧&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;交到物理层，通过以太网访问&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;主机B收到后，&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;检查目的地址，不相符就丢弃&lt;/li&gt;
&lt;li&gt;将IP数据包提取后送入网络层的IP层协议，&lt;strong&gt;IP层检查后将有用的信息提取后送入ICMP协议&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;ICMP协议马上构建一个&lt;strong&gt;ICMP应答包以之前的相同方式发送给主机&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;根据条件：&lt;strong&gt;是否在同一网段内&lt;/strong&gt;，流程可能有所不同，区别在于MAC的获取方式，具体参见ARP协议。&lt;/p&gt;
&lt;h2 id=&#34;73-什么是负载均衡负载均衡算法有哪些&#34;&gt;7.3 什么是负载均衡，负载均衡算法有哪些？
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;负载均衡是&lt;/strong&gt;高可用网络基础架构的关键组件，通常用于将&lt;strong&gt;工作负载分布到多个服务器&lt;/strong&gt;来提高网站、应用、数据库或其他服务的&lt;strong&gt;性能和可靠性&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;多台服务器以对称的方式组成一个服务器集合&lt;/strong&gt;，每台服务器都具有&lt;strong&gt;等价的地位&lt;/strong&gt;，&lt;strong&gt;能互相分担负载&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;轮询法&lt;/strong&gt;：&lt;strong&gt;将请求按照顺序轮流的分配到服务器上&lt;/strong&gt;。大锅饭，不能发挥某些高性能服务器的优势&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;随机法&lt;/strong&gt;：&lt;strong&gt;随机获取一台&lt;/strong&gt;，和轮询类似&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;哈希法&lt;/strong&gt;：&lt;strong&gt;通过ip地址哈希化来确定要选择的服务器编号&lt;/strong&gt;。好处是，&lt;strong&gt;每次客户端访问的服务器都是同一个服务器，能很好地利用session或者cookie&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;加权轮询&lt;/strong&gt;：根据服务器&lt;strong&gt;性能不同加权&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;一致性哈希&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;不同的负载均衡算法适用的业务场景也不同的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;轮询这类的策略只能适用与每个节点的数据都是相同的场景，访问任意节点都能请求到数据。但是不适用分布式系统&lt;/strong&gt;，因为分布式系统意味着&lt;strong&gt;数据水平切分到了不同的节点上&lt;/strong&gt;，访问数据的时候，一定要寻址存储该数据的节点。&lt;/p&gt;
&lt;p&gt;哈希算法虽然能建立数据和节点的映射关系，&lt;strong&gt;但是每次在节点数量发生变化的时候，最坏情况下所有数据都需要迁移&lt;/strong&gt;，这样太麻烦了，所以不适用节点数量变化的场景。&lt;/p&gt;
&lt;p&gt;为了减少迁移的数据量，就出现了一致性哈希算法。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一致性哈希是指将「存储节点」和「数据」都映射到一个首尾相连的哈希环上，如果增加或者移除一个节点，仅影响该节点在哈希环上顺时针相邻的后继节点，其它数据也不会受到影响&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;但是一致性哈希算法不能够均匀的分布节点，会出现大量请求都集中在一个节点的情况，在这种情况下进行容灾与扩容时&lt;/strong&gt;，容易出现雪崩的连锁反应。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为了解决一致性哈希算法不能够均匀的分布节点的问题，就需要引入虚拟节点&lt;/strong&gt;，对一个真实节点做多个副本。不再将真实节点映射到哈希环上，而是将虚拟节点映射到哈希环上，并将虚拟节点映射到实际节点，&lt;strong&gt;所以这里有「两层」映射关系。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;引入虚拟节点后，可以会提高节点的均衡度，还会提高系统的稳定性。所以，带虚拟节点的一致性哈希方法不仅适合硬件配置不同的节点的场景，而且适合节点规模会发生变化的场景。&lt;/p&gt;
&lt;h2 id=&#34;74-ssl的工作原理&#34;&gt;7.4 SSL的工作原理
&lt;/h2&gt;&lt;p&gt;SSL与TLS SSL：（&lt;strong&gt;Secure Socket Layer&lt;/strong&gt;，安全套接字层），位于可靠的面向连接的网络层协议和应用层协议之间的一种协议层。&lt;/p&gt;
&lt;p&gt;SSL通过&lt;strong&gt;互相认证、使用数字签名&lt;/strong&gt;确保完整性、使用加密确保私密性，以实现客户端和服务器之间的安全通讯。该协议由两层组成：SSL记录协议和SSL握手协议。&lt;/p&gt;
&lt;p&gt;TLS：(Transport Layer Security，传输层安全协议)，TLS（传输层安全）是更&lt;strong&gt;为安全的升级版 SSL&lt;/strong&gt;，用于两个应用程序之间提供保密性和数据完整性。该协议由两层组成：&lt;strong&gt;TLS记录协议和TLS握手协议。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;传统的 TLS 握手基本都是使用 RSA 算法来实现密钥交换的&lt;/strong&gt;，在将 TLS 证书部署服务端时，证书文件中包含一对公私钥，&lt;strong&gt;其中公钥会在 TLS 握手阶段传递给客户端&lt;/strong&gt;，私钥则一直留在服务端，一定要确保私钥不能被窃取。在 RSA 密钥协商算法中，&lt;strong&gt;客户端会生成随机密钥，并使用服务端的公钥加密后再传给服务端.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;SSL/TLS历史 1994年，&lt;strong&gt;NetScape公司设计了SSL协议&lt;/strong&gt;（Secure Sockets Layer）的1.0版，但是未发布。&lt;/p&gt;
&lt;p&gt;1995年，NetScape公司发布SSL 2.0版，很快发现有严重漏洞。&lt;/p&gt;
&lt;p&gt;1996年，SSL 3.0版问世，得到大规模应用。 1999年，**互联网标准化组织ISOC接替NetScape公司，**发布了SSL的升级版TLS 1.0版。&lt;/p&gt;
&lt;p&gt;2006年和2008年，TLS进行了两次升级，分别为TLS 1.1版和TLS 1.2版。最新的变动是2011年TLS 1.2的修订版，在2018年也发布了TLS1.3版本。 TLS 1.0通常被标示为SSL 3.1，TLS 1.1为SSL 3.2，TLS 1.2为SSL 3.3。&lt;/p&gt;
&lt;p&gt;目前&lt;strong&gt;应用的最广泛的 TLS 是 1.2&lt;/strong&gt;，而之前的协议（TLS1.1/1.0、SSLv3/v2）都&lt;strong&gt;已经被认为是不安&lt;/strong&gt;全的了。&lt;/p&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://img-blog.csdn.net/20180920154005922?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1NjQyMDM2/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;SSL/TLS协议的基本过程&lt;/p&gt;
&lt;p&gt;（1） &lt;strong&gt;客户端向服务器端索要并验证公钥&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;（2） &lt;strong&gt;双方协商生成“对话密钥”&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;（3） &lt;strong&gt;双方采用“对话密钥”进行加密通信&lt;/strong&gt;。 上面过程的前两步，又称为**“握手阶段”（handshake）**&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TLS/SSL的功能实现主要依赖于三类基本算法：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;散列函数 Hash、对称加密DES、3DES、IDEA、AES和非对称加密RSA、DSA、ECC、Diffie-Hellman，&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;其利用非对称加密实现身份认证和密钥协商，对称加密算法采用协商的密钥对数据加密，基于散列函数验证信息的完整性。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CA证书&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;现实中，通过CA（Certificate Authority）来保证public key的真实性。&lt;strong&gt;CA也是基于非对称加密算法&lt;/strong&gt;来工作。&lt;/p&gt;
&lt;p&gt;有了CA，&lt;strong&gt;B会先把自己的public key（和一些其他信息）交给CA。CA用自己的private key加密这些数&lt;/strong&gt;据，&lt;strong&gt;加密完的数据称为B的数字证书&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;现在B要向A传递public key，B传递的是CA加密之后的数字证书。&lt;strong&gt;A收到以后，会通过CA发布的CA证书（包含了CA的public key），来解密B的数字证书，从而获得B的public key。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;但是等等，A怎么确保CA证书不被劫持。C完全可以把一个假的CA证书发给A，进而欺骗A。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CA的大杀器就是，CA把自己的CA证书集成在了浏览器和操作系统里面&lt;/strong&gt;。A拿到浏览器或者操作系统的时候，&lt;strong&gt;已经有了CA证书，没有必要通过网络获取&lt;/strong&gt;，那自然也不存在劫持的问题。&lt;/p&gt;
&lt;h3 id=&#34;tls-握手&#34;&gt;&lt;strong&gt;TLS 握手&lt;/strong&gt;
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;TLS第一次握手&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;客户端首先会发一个「&lt;strong&gt;Client Hello&lt;/strong&gt;」消息&lt;/p&gt;
&lt;p&gt;消息里面有客户端使用的 &lt;strong&gt;TLS 版本号&lt;/strong&gt;、&lt;strong&gt;支持的密码套件列表&lt;/strong&gt;，以及生成的&lt;strong&gt;随机数（&lt;em&gt;Client Random&lt;/em&gt;）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这个随机数会被服务端保留，它是生成对称加密密钥的材料之一&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TLS 第二次握手&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;当服务端收到客户端的「Client Hello」消息后，&lt;strong&gt;会确认 TLS 版本号是否支持，和从密码套件列表中选择一个密码套件&lt;/strong&gt;，以及生成&lt;strong&gt;随机数（&lt;em&gt;Server Random&lt;/em&gt;）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;接着，返回「&lt;strong&gt;Server Hello&lt;/strong&gt;」消息，消息里面有服务器确认的 TLS 版本号，也给出了随机数（Server Random），然后从客户端的密码套件列表选择了一个合适的密码套件。&lt;/p&gt;
&lt;p&gt;这个密码套件看起来真让人头晕，好一大串，但是其实它是有固定格式和规范的。基本的形式是「&lt;strong&gt;密钥交换算法 + 签名算法 + 对称加密算法 + 摘要算法&lt;/strong&gt;」&lt;/p&gt;
&lt;p&gt;Diffie-Hellman密钥交换算法:&lt;/p&gt;
&lt;p&gt;-&lt;/p&gt;
&lt;p&gt;Diffie-Hellman密钥交换算法是一种用于在不安全的通信信道上安全地交换密钥的算法。它允许两个通信方在没有事先共享密钥的情况下协商出一个共享的对称密钥，该密钥可以用于后续的加密通信。&lt;/p&gt;
&lt;p&gt;Diffie-Hellman算法的基本思想是利用数论中的离散对数问题。具体步骤如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1. **参数选择**：选择两个大素数p和g，其中p是一个素数，g是一个原根（即对于任意小于p的正整数a，都存在一个整数k使得$g^k ≡ a \mod p$）。
2. **密钥生成**：
    - 选择私密参数：每个通信方选择一个私密参数（私钥）。假设Alice选择私钥a，Bob选择私钥b。
    - 计算公开参数：计算公开参数（公钥）。Alice计算$A = g^a \mod p$，Bob计算$B = g^b \mod p$。
    - 交换公开参数：Alice将A发送给Bob，Bob将B发送给Alice。
3. **密钥协商**：
    - 计算共享密钥：Alice使用Bob发送的B和自己的私钥a计算共享密钥$K = B^a \mod p$，Bob使用Alice发送的A和自己的私钥b计算共享密钥$K = A^b \mod p$。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由于离散对数问题的困难性，即使攻击者能够截获Alice和Bob之间的通信，也很难从A、B和p中推导出共享密钥K，因此Diffie-Hellman算法能够安全地协商出一个共享密钥，用于后续的加密通信。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TLS 第三次握手&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;公钥；&lt;/li&gt;
&lt;li&gt;持有者信息；&lt;/li&gt;
&lt;li&gt;证书认证机构（CA）的信息；&lt;/li&gt;
&lt;li&gt;CA 对这份文件的数字签名及使用的算法；&lt;/li&gt;
&lt;li&gt;证书有效期；&lt;/li&gt;
&lt;li&gt;还有一些其他额外信息；&lt;/li&gt;
&lt;li&gt;数字证书的作用，是用来认证公钥持有者的身份，以防止第三方进行冒充。说简单些，证书就是用来告诉客户端，该服务端是否是合法的，因为只有证书合法，才代表服务端身份是可信的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;客户端验证完证书后，认为可信则继续往下走。接着，客户端就会生成一个新的&lt;strong&gt;随机数 (&lt;em&gt;pre-master&lt;/em&gt;)&lt;/strong&gt;，用服务器的 RSA 公钥加密该随机数，通过**「Change Cipher Key Exchange」消息传给服务端。**&lt;/p&gt;
&lt;p&gt;那这个随机数有啥用呢？其实这两个随机数是后续作为生成「会话密钥」的条件&lt;/p&gt;
&lt;p&gt;所谓的会话密钥就是数据传输时，所使用的对称加密密钥。&lt;/p&gt;
&lt;p&gt;于是，&lt;strong&gt;双方根据已经得到的三个随机数&lt;/strong&gt;，生成&lt;strong&gt;会话密钥（Master Secret），它是对称密钥&lt;/strong&gt;，用于对后续的 HTTP 请求/响应的数据加解密。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TLS 第四次握手&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;服务器也是同样的操作，发「&lt;strong&gt;Change Cipher Spec&lt;/strong&gt;」和「&lt;strong&gt;Encrypted Handshake Message&lt;/strong&gt;」消息，&lt;strong&gt;如果双方都验证加密和解密没问题，那么握手正式完成。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;HTTPS ECDHE 握手解析&lt;/p&gt;
&lt;p&gt;分别是 RSA 和 ECDHE 算法。&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&#34;rsa-和-ecdhe-握手过程的区别&#34;&gt;RSA 和 ECDHE 握手过程的区别：
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;RSA 密钥协商算法「不支持」前向保密，&lt;strong&gt;ECDHE 密钥协商算法「支持」前向保密，非对称密钥&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;使用了 RSA 密钥协商算法，T&lt;strong&gt;LS 完成四次握手后，才能进行应用数据传输&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;而对于 ECDHE 算法，**客户端可以不用等服务端的最后一次 TLS 握手，就可以提前发出加密的 HTTP 数据，**节省了一个消息的往返时间；&lt;/li&gt;
&lt;li&gt;使用 ECDHE， 在 &lt;strong&gt;TLS 第 2 次握手中，会出现服务器端发出的「Server Key Exchange」消息&lt;/strong&gt;，而 RSA 握手过程没有该消息；&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;75-路由器是如何选择最佳路径的&#34;&gt;7.5 路由器是如何选择最佳路径的
&lt;/h2&gt;&lt;p&gt;路由器是一种用于网络互连的专用计算机设备,在网路建设中有着重要的地位.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;路由器工作在OSI参考模型的第三层(网络层),&lt;strong&gt;主要的作用是为&lt;/strong&gt;收到的报文寻找正确的路径,并把他们转发出去&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;在这个过程中,路由器被认为执行了两个最重要的基本功能:&lt;strong&gt;路由功能和交换功能.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于一个特定的路由协议,可以发现到达目的网络的所有路径, &lt;strong&gt;根据选路算法赋予每一条路径metric值,比较metric值,选择metric值最小的路径为最佳路径&lt;/strong&gt;;&lt;/p&gt;
&lt;p&gt;在路由器的交换过程中查找路由时可能会发现能匹配上多条路由条目.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;此时路由器将根据掩码长度最长匹配原则进行数据的转发&lt;/strong&gt;.路由器会进行匹配最深的,&lt;strong&gt;也就是说可以匹配的掩码长度最长的一条路由进行转发&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;第一，&lt;strong&gt;最长掩码匹配原则&lt;/strong&gt;；例如，查找去往192.168.1.1的路径时，发现路由表有如下两个表项 192.168.1.0 mask 255.255.255.0 next hop 10.1.1.1&lt;/p&gt;
&lt;p&gt;192.168.1.0 mask 255.255.0.0 next hop 172.16.1.1&lt;/p&gt;
&lt;p&gt;路由器会选择第一条路由转发，因为&lt;strong&gt;第一条的IP地址范围更小&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第二，如果路由表中目的&lt;strong&gt;网段的范围相同&lt;/strong&gt;，&lt;strong&gt;路由优先级高者优先&lt;/strong&gt;（&lt;strong&gt;优先级数值越小，优先级越高&lt;/strong&gt;）&lt;/p&gt;
&lt;p&gt;第三，如果路由表中目的网段的范围相同，并且路由优先级也相同，&lt;strong&gt;开销（metric）小的优先（metric值越小，开销越小）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;路由选择算法可分为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;全局式&lt;/strong&gt;路由选择算法：所有路由器&lt;strong&gt;掌握完整的网络拓扑和链路费用信息&lt;/strong&gt;，例如&lt;strong&gt;链路状态(LS)路由算法&lt;/strong&gt;，链路状态路由选择算法可以用Dijksua算法实现。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分散式&lt;/strong&gt;路由选择算法；路由器只掌握&lt;strong&gt;物理相连的邻居以及链路费用&lt;/strong&gt;，例如距离向量(DV)路由算法，距离向量路由选择算法可以用&lt;strong&gt;Bellman-Ford方程dx(y) = min {c(x,v) + dv(y)}实现。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;76-子网掩码的作用&#34;&gt;7.6 子网掩码的作用
&lt;/h2&gt;&lt;p&gt;1、一是用于&lt;strong&gt;屏蔽IP地址的一部分&lt;/strong&gt;以&lt;strong&gt;区别网络标识和主机标识&lt;/strong&gt;，&lt;strong&gt;并说明该IP地址是在局域网上，还是在远程网上。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;2、二是用于将一个&lt;strong&gt;大的IP网络划分为若干小的子网络。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;使用子网是&lt;strong&gt;为了减少IP的浪费&lt;/strong&gt;。因为随着互联网的发展，越来越多的网络产生，有的网络多则几百台，有的只有区区几台，这样就浪费了很多IP地址，所以要划分子网。&lt;strong&gt;使用子网可以提高网络应用的效率。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;77-http请求报文和响应报文分别由哪些部分组成&#34;&gt;7.7 HTTP请求报文和响应报文分别由哪些部分组成？
&lt;/h2&gt;&lt;h3 id=&#34;http请求报文&#34;&gt;HTTP请求报文
&lt;/h3&gt;&lt;p&gt;由请求行（request line）、请求头部（header）、空行和请求数据4个部分组成，下图给出了请求报文的一般格式。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://pic002.cnblogs.com/images/2012/426620/2012072810301161.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;img&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1.请求头&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;请求行由请&lt;strong&gt;求方法字段、URL字段和HTTP协议版本&lt;/strong&gt;字段3个字段组成，它们用空格分隔。例如，&lt;strong&gt;GET /index.html HTTP/1.1。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2.请求头部&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;请求头部由关键字/值对组成，每行一对，关键字和值用英文冒号“:”分隔。请求头部通知服务器有关于客户端请求的信息，典型的请求头有：&lt;/p&gt;
&lt;p&gt;User-Agent：产生请求的浏览器类型。&lt;/p&gt;
&lt;p&gt;Accept：客户端可识别的内容类型列表。&lt;/p&gt;
&lt;p&gt;Host：请求的主机名，允许多个域名同处一个IP地址，即虚拟主机。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3.空行&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;最后一个请求头之后是一个空行，发送回车符和换行符，通知服务器以下不再有请求头。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4.请求数据&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;请求数据不在GET方法中使用，而是在POST方法中使用。POST方法适用于需要客户填写表单的场合。与请求数据相关的最常使用的&lt;strong&gt;请求头是Content-Type和Content-Length。&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;GET /search?hl=zh-CN&amp;amp;source=hp&amp;amp;q=domety&amp;amp;aq=f&amp;amp;oq= HTTP/1.1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, application/x-silverlight, application/x-shockwave-flash, */*
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Referer: &amp;lt;a href=&amp;#34;http://www.google.cn/&amp;#34;&amp;gt;http://www.google.cn/&amp;lt;/a&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Accept-Language: zh-cn
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Accept-Encoding: gzip, deflate
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; TheWorld)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Host: &amp;lt;a href=&amp;#34;http://www.google.cn&amp;#34;&amp;gt;www.google.cn&amp;lt;/a&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Connection: Keep-Alive
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Cookie: PREF=ID=80a06da87be9ae3c:U=f7167333e2c3b714:NW=1:TM=1261551909:LM=1261551917:S=ybYcq2wpfefs4V9g; NID=31=ojj8d-IygaEtSxLgaJmqSjVhCspkviJrB6omjamNrSm8lZhKy_yMfO2M4QMRKcH1g0iQv9u-2hfBW7bUFwVh7pGaRUb0RnHcJU37y-FxlRugatx63JLv7CWMD6UB_O_r
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;POST /search HTTP/1.1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, application/x-silverlight, application/x-shockwave-flash, */*
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Referer: &amp;lt;a href=&amp;#34;http://www.google.cn/&amp;#34;&amp;gt;http://www.google.cn/&amp;lt;/a&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Accept-Language: zh-cn
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Accept-Encoding: gzip, deflate
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; TheWorld)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Host: &amp;lt;a href=&amp;#34;http://www.google.cn&amp;#34;&amp;gt;www.google.cn&amp;lt;/a&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Connection: Keep-Alive
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Cookie: PREF=ID=80a06da87be9ae3c:U=f7167333e2c3b714:NW=1:TM=1261551909:LM=1261551917:S=ybYcq2wpfefs4V9g; NID=31=ojj8d-IygaEtSxLgaJmqSjVhCspkviJrB6omjamNrSm8lZhKy_yMfO2M4QMRKcH1g0iQv9u-2hfBW7bUFwVh7pGaRUb0RnHcJU37y-FxlRugatx63JLv7CWMD6UB_O_r
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;hl=zh-CN&amp;amp;source=hp&amp;amp;q=domety
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id=&#34;http报文&#34;&gt;HTTP报文
&lt;/h3&gt;&lt;p&gt;HTTP响应也由三个部分组成，分别是：&lt;strong&gt;状态行、消息报头、响应正文&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;正如你所见，在响应中唯一真正的区别在于第一行中用状态信息代替了请求信息。状态行（status line）通过提供一个&lt;strong&gt;状态码&lt;/strong&gt;来说明所请求的资源情况。&lt;/p&gt;
&lt;p&gt;状态行格式如下：&lt;/p&gt;
&lt;p&gt;HTTP-Version Status-Code Reason-Phrase CRLF&lt;/p&gt;
&lt;p&gt;其中，HTTP-Version表示服务器HTTP协议的版本；Status-Code表示服务器发回的响应状态代码；Reason-Phrase表示状态代码的文本描述。&lt;/p&gt;
&lt;p&gt;状态代码由三位数字组成，第一个数字定义了响应的类别，且有五种可能取值。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1xx：指示信息–表示请求已接收，继续处理。&lt;/li&gt;
&lt;li&gt;2xx：成功–表示请求已被成功接收、理解、接受。&lt;/li&gt;
&lt;li&gt;3xx：重定向–要完成请求必须进行更进一步的操作。&lt;/li&gt;
&lt;li&gt;4xx：客户端错误–请求有语法错误或请求无法实现。&lt;/li&gt;
&lt;li&gt;5xx：服务器端错误–服务器未能实现合法的请求。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;HTTP/1.1 200 OK
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Date: Sat, 31 Dec 2005 23:59:59 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Content-Type: text/html;charset=ISO-8859-1
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Content-Length: 122
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;＜html＞
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;＜head＞
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;＜title＞Wrox Homepage＜/title＞
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;＜/head＞
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;＜body＞
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;＜!-- body goes here --＞
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;＜/body＞
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;＜/html＞
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id=&#34;78-代理服务器的工作原理是怎样的代理和网关有什么区别&#34;&gt;7.8 代理服务器的工作原理是怎样的？代理和网关有什么区别？
&lt;/h2&gt;&lt;p&gt;代理服务器和网关都会把&lt;strong&gt;网络内部的数据的数据发送到因特网上&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;如果把网关比&lt;strong&gt;作一扇通向因特网的门&lt;/strong&gt;，代理服务器&lt;strong&gt;就是一堵墙&lt;/strong&gt;，能够避免暴露网络内部的一些重要信息。&lt;/p&gt;
&lt;p&gt;代理服务器会&lt;strong&gt;过滤一些网络连接，只允许那些可以访问的通过&lt;/strong&gt;。而&lt;strong&gt;网关却不做任何的过滤。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;网关：&lt;/p&gt;
&lt;p&gt;如果两个网络要进行通讯，那么&lt;strong&gt;每个网络都需要一个网关&lt;/strong&gt;。网关区分了一个网络的内部和外部。&lt;strong&gt;如果一台电脑需要访问网络外的其他电脑，那么就需要配置网关来获得访问网络外部的权限&lt;/strong&gt;。如果没有网关，电脑就无法访问局域网之外的网络部分，就像是被锁在家里一样。&lt;/p&gt;
&lt;p&gt;代理服务器：&lt;/p&gt;
&lt;p&gt;对于网络外部来说，&lt;strong&gt;代理服务器代表了整个内部网络&lt;/strong&gt;。任何用户想访问带有代理服务器的网络，&lt;strong&gt;都只能看到代理服务器的IP&lt;/strong&gt;。当把电脑的Internet选项配置成通过代理服务器访问因特网，&lt;strong&gt;代理服务器就能隐藏你的网络信息。它使网络内部的电脑变成匿名的。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;功能区别：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果代理服务器不做任何信息过滤，那么它就和网关一样&lt;/strong&gt;，传递从电脑到因特网的请求。&lt;/p&gt;
&lt;p&gt;然后代理服务器是一个比网关更强大的网络组建，除了有网关的功能之外，&lt;strong&gt;还能保护网络免受外部的威胁&lt;/strong&gt;。&lt;strong&gt;网关却有暴露网络内部信息的危险，因为它没有任何过滤机制。它仅仅把网络内的信息发送到网络外。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;屏蔽网站：&lt;/p&gt;
&lt;p&gt;网关不能屏蔽网站。只要网关配置正确，电脑就能&lt;strong&gt;从网络内部访问因特网上的任何网站&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;代理服务器能把网络请求重定向到网络内部的网站上，从而&lt;strong&gt;屏蔽网站&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;管理员可以设置在某个时段或者全天时间屏蔽一些网站。&lt;strong&gt;访问这些被屏蔽的网站会重定向到特定的网站上&lt;/strong&gt;，表示你试图访问一个被屏蔽的网站。&lt;/p&gt;
&lt;p&gt;其他代理服务器的功能：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;代理服务器也能缓存一些电脑经常访问的网站&lt;/strong&gt;。它能跟踪网站点击量并使用这些信息储存每天访问的网站信息。当你第二次访问你之前访问过的网站时，代理服务器会返回缓存中的网站信息，而不会访问因特网。这个功能可以有效的减少访问外部网络的流量，节省带宽资源。可以设置每天几次获取因特网的新内容来刷新代理服务器的缓存信息。&lt;/p&gt;
&lt;p&gt;在HTTP通信链上，客户端和目标服务器之间通常存在某些**中转代理服务器，**它们提供对目标资源的中转访问。’&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一个HTTP请求可能被多个代理服务器转发&lt;/strong&gt;，后面的服务器称为&lt;strong&gt;前面服务器的上游服务器&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;代理服务器按照其使用方式和作用，分为&lt;strong&gt;正向代理服务器，反向代理服务器和透明代理服务器&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;正向代理要求客户端自己设置代理服务器的地址。客户的每次请求都将直接发送到该代理服务器，并由代理服务器来请求目标资源&lt;/strong&gt;。比如处于防火墙内的局域网机器要访问Internet，或者要访问一些被屏蔽掉的国外网站，就需要使用&lt;strong&gt;正向代理服务器&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;反向代理则被设置在服务器端，因而客户端无需进行任何设置。&lt;strong&gt;反向代理是指用代理服务器来接收Internet上的连&lt;/strong&gt;接请求，&lt;strong&gt;然后将请求转发给内部网络上的服务器&lt;/strong&gt;，并将从&lt;strong&gt;内部服务器上得到的结果返回给客户端。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这种情况下，&lt;strong&gt;代理服务器对外就表现为一个真实的服务器&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;各大网站通常分区域设置了多个代理服务器，所以在&lt;strong&gt;不同的地方ping同一个域名可能得到不同的IP地址，因为这些IP地址实际上是代理服务器的IP地址。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://img-blog.csdnimg.cn/20190529223536806.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxMDkxMzcz,size_16,color_FFFFFF,t_70&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;如图所示，正向代理服务器和客户端主机处于同一个逻辑网络中。&lt;strong&gt;该逻辑网络可以是一个本地LAN，也可以是一个更大的网络&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;反向代理服务器和真正的Web服务器也位于同一个逻辑网络中**，这通常由提供网站的公司来配置和管理。**&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;透明代理只能设置在网关上&lt;/strong&gt;。用户访问Internet的数据报必然都经过网关，如果在网关上设置代理，则该代理对用户来说显然是透明的。&lt;strong&gt;透明代理可以看作正向代理的一种特殊情况。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;代理服务器通常还提供缓存目标资源的功能&lt;/strong&gt;，这样用户下次访问同一资源时速度将很快。&lt;strong&gt;优秀的开源软件squid，varnish都是提供了缓存能力的代理服务器软件&lt;/strong&gt;，其中squid支持所有代理方式，而varnish仅能用作反向代理。&lt;/p&gt;
&lt;h2 id=&#34;79-浏览器输入百度httpswwwnowcodercomjumpsuper-jumpwordworde799bee5baa6地址中间具体访问过程是怎么样的&#34;&gt;7.9 浏览器输入&lt;a class=&#34;link&#34; href=&#34;https://www.nowcoder.com/jump/super-jump/word?word=%E7%99%BE%E5%BA%A6&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;百度&lt;/a&gt;地址，中间具体访问过程是怎么样的？
&lt;/h2&gt;&lt;p&gt;1.客户端浏览器获取用户在地址栏输入的域名,&lt;strong&gt;URL解析&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;2.客户端浏览器将域名发送给DNS域名系统，请求解析,&lt;strong&gt;DNS解析&lt;/strong&gt; 。&lt;/p&gt;
&lt;p&gt;3.DNS解析域名得到相应的IP，&lt;strong&gt;返回给客户端浏览器&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;4.客户端浏览器根据IP向服务器发起TCP三次握手，建立&lt;strong&gt;TCP连接&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;5.客户端浏览器向服务器发送&lt;strong&gt;HTTP请求&lt;/strong&gt;，请求百度首页。&lt;/p&gt;
&lt;p&gt;6.服务器通过&lt;strong&gt;HTTP响应向客户端浏览器返回百度首页文件。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;7.&lt;strong&gt;释放TCP连接&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;8.客户端浏览器&lt;strong&gt;解析HTML文件&lt;/strong&gt;，根据文件内容获取CSS、JS等资源文件，将页面渲染展示给用户。&lt;/p&gt;
&lt;h2 id=&#34;710-网卡-网桥网关路由器交换机&#34;&gt;7.10 网卡 网桥，网关，路由器，交换机
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;路由器&lt;/strong&gt;可以根据&lt;strong&gt;IP地址&lt;/strong&gt;寻找下一个设备，可以处理&lt;strong&gt;TCPIP协议&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;交换机是根据MAC地址寻址的。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;路由器是用于在&lt;strong&gt;不同网段之间&lt;/strong&gt;转发数据 （网络层）&lt;/p&gt;
&lt;p&gt;二层交换机是用于在&lt;strong&gt;同网段&lt;/strong&gt;转发数据 （&lt;strong&gt;数据链路层&lt;/strong&gt;）&lt;/p&gt;
&lt;p&gt;三层交换机是可以在&lt;strong&gt;不同网段转发数据但在同网段转发数据能力特别强的交换机&lt;/strong&gt; 即可理解为路由器➕交换机（网络层）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一个网关可以是路由器 可以是三层交换机&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;交换机是分配网络数据&lt;/strong&gt;，路由器可以给网络分配IP地址，分配给你地址而且可以随时通过地址过来找到你。&lt;/p&gt;
&lt;p&gt;路由器可以在不同时间内把一个IP分配给多台主机使用。&lt;/p&gt;
&lt;p&gt;交换机是通过&lt;strong&gt;MAC地址&lt;/strong&gt;和识别各个不同的主机。&lt;strong&gt;工作在OSI第二层（数据链路层）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;一、中继器&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;中继器（Repeater）工作于OSI的第一层（物理层），中继器是最简单的网络互联设备，连接同一个网络的两个或多个网段，主要完成物理层的功能，负责在两个网络节点的物理层上按位传递信息&lt;/strong&gt;，完成信号的复制、调整和放大功能，以此从而增加信号传输的距离，延长网络的长度和覆盖区域，支持远距离的通信。&lt;/p&gt;
&lt;p&gt;一般来说，中继器两端的网络部分是网段，而不是子网。中继器只将任何电缆段上的数据发送到另一段电缆上，并不管数据中是否有错误数据或不适于网段的数据。大家最常接触的是网络中继器，在通讯上还有微波中继器、激光中继器、红外中继器等等，机理类似，触类旁通。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;二、集线器&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;集线器也称HUB，工作在OSI七层结构的第一层物理层，属于共享型设备，接收数据广播发出，在局域网内一般都是星型连接拓扑结构，每台工作站都连接到集线器上&lt;/strong&gt;。由于集线器的带宽共享特性导致网络利用效率极低，一般在大中型的网络中不会使用到集线器。现在的集线器基本都是全双工模式，市面上常见的集线器传输速率普遍都为100Mbps。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;三、网桥&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;网桥和交换机一样都是工作在OSI模型的第二层（数据链路层）&lt;/strong&gt;，可以看成是一个二层路由器（真正的路由器是工作在网络层，根据IP地址进行信包转发）。&lt;/p&gt;
&lt;p&gt;网桥&lt;strong&gt;可有效的将两个局域网（LAN）连起来，根据MAC地址（物理地址）来转发帧&lt;/strong&gt;，使本地通信限制在本网段内，并转发相应的信号至另一网段，网桥通常用于联接数量不多的、同一类型的网段。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;四、交换机&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;交换机顾名思义以交换为主要功能，工作在OSI第二层（数据链路层），&lt;strong&gt;根据MAC地址进行数据转发&lt;/strong&gt;。交换机的&lt;strong&gt;每一个端口都属于一个冲突域&lt;/strong&gt;，而集线器&lt;strong&gt;所有端口属于一个冲突域&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;交换机通过分析Ethernet包的包头信息（其中包含了源MAC地址、目标MAC地址、信息长度等），取得目标MAC地址后，查找交换机中存储的地址对照表（MAC地址对应的端口），&lt;strong&gt;确认具有此MAC地址的网卡连接在哪个端口上&lt;/strong&gt;，然后将&lt;strong&gt;信包送到对应端口&lt;/strong&gt;，有效的抑制IP广播风暴。并且信息包处于并行状态，效率较高。&lt;/p&gt;
&lt;p&gt;数据包通过交换机转发抵达了路由器，准备要离开土生土长的子网了。此时，数据包和交换机离别时说道：“感谢交换机兄弟，帮我转发到出境的大门，我要出远门啦！”&lt;/p&gt;
&lt;p&gt;交换机的转发延迟非常小，主要的得益于其硬件设计机理非常高效，为了&lt;strong&gt;支持各端口的最&lt;a class=&#34;link&#34; href=&#34;http://lib.csdn.net/base/20&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;大数据&lt;/a&gt;传输速率&lt;/strong&gt;，交换&lt;strong&gt;机内部转发信包的背板带宽都必须远大于端口带宽&lt;/strong&gt;，具有强&lt;strong&gt;大的整体吞吐率&lt;/strong&gt;，才能为每台工作站提供更高的带宽和更高的网络利用率，可以满足大型网络环境大量数据并行处理的要求。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;五、路由器&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;路由器跟集线器和交换机不同，是工作在OSI的第三层（网络层），&lt;strong&gt;根据IP进行寻址转发数据包&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;路由器是一种可以连接多个网络或网段的网络设备，能将不同网络或网段之间（比如局域网——大网）的数据信息进行转换&lt;/strong&gt;，并为信包传输分配最合适的路径，使它们之间能够进行数据传输，从而构成一个更大的网络。&lt;/p&gt;
&lt;p&gt;路由器具有最主要的两个功能，即&lt;strong&gt;数据通道功能和控制功能&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;数据通道功能包括&lt;strong&gt;转发决定、背板转发以及输出链路调度等，一般由特定的硬件来完成；&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;控制功能一般用软件来实现&lt;/strong&gt;，包括与相邻路由器之间的信息交换、系统配置、系统管理等。实在找&lt;strong&gt;不到匹配路由时，就会选择默认路由&lt;/strong&gt;，路由表中子网掩码为 &lt;code&gt;0.0.0.0&lt;/code&gt; 的记录表示「默认路由」。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;六、网关&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;网关（Gateway）又叫协议转换器，网关是一种复杂的网络连接设备，可以支持不同协议之间的转换，&lt;strong&gt;实现不同协议网络之间的互连&lt;/strong&gt;。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在网络中，路由器为第三层网络层设备，其主要功能是根&lt;strong&gt;据最佳路由把数据包转发到下一站&lt;/strong&gt;，实现从源IP到目标IP的端到端数据传输服务；&lt;/li&gt;
&lt;li&gt;网&lt;strong&gt;关早期的时候就是路由器的别名&lt;/strong&gt;，但是在&lt;strong&gt;现在的网络模型中把它归为应用层设备&lt;/strong&gt;。主要功能是&lt;strong&gt;过滤数据包信息以实现相应的功能&lt;/strong&gt;网关的概念实际上跟上面的设备型不是一类问题，但是为了方便参考还是放到这里一并介绍。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;网关具有对不兼容的高层协议进行转换的能力，为了实现异构设备之间的通信，网关需要对不同的链路层、专用会话层、表示层和应用层协议进行翻译和转换。所以&lt;strong&gt;网关兼有路由器、网桥、中继器的特性。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;若要使两个完全不同的网络（异构网）连接在一起，一般使用网关，&lt;strong&gt;在Internet中两个网络也要通过一台称为网关的计算机实现互联&lt;/strong&gt;。这台计算机能根据用户通信目标计算机的IP地址，决定是否将用户发出的信息送出本地网络，同时，它还将外界发送给属于本地网络计算机的信息接收过来，它是一个网络与另一个网络相联的通道。为了使TCP/IP协议能够寻址，该通道被赋予一个IP地址，这个IP地址称为网关地址。&lt;/p&gt;
&lt;p&gt;所以，网关的作用就是将两个使用不同协议的网络段连接在一起的设备，&lt;strong&gt;对两个网络段中的使用不同传输协议的数据进行互相的翻译转换&lt;/strong&gt;。在互连设备中，由于协议转换的复杂性，一般只能进行一对一的转换，或是少数几种特定应用协议的转换。&lt;/p&gt;
&lt;p&gt;网卡：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;网络包只是存放在内存中的一串二进制数字信息，没有办法直接发送给对方&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;因此，我们需要将&lt;strong&gt;数字信息转换为电信号&lt;/strong&gt;，才能在网线上传输，也就是说，这才是真正的数据发送过程。负责执行这一操作的是&lt;strong&gt;网卡&lt;/strong&gt;，要控制网卡还需要靠&lt;strong&gt;网卡驱动程序&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;网卡驱动获取网络包之后，会将其&lt;strong&gt;复制&lt;/strong&gt;到网卡内的缓存区中，接着会在其&lt;strong&gt;开头加上报头和起始帧分界符，在末尾加上用于检测错误的帧校验序列&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;最后网卡会将包转为电信号，通过网线发送出去。&lt;/p&gt;
&lt;h2 id=&#34;网关和路由器区别&#34;&gt;网关和路由器区别：
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;1、本质区别&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;网关这种设备它主要是用&lt;strong&gt;来连接两种不同的网络&lt;/strong&gt;，同时，网关它还能够同时&lt;strong&gt;与两边的主机之间进行通信。但是两边的主机是不能够直接进行通信，是必须要经过网关才能进行通信。网关的工作是在应用层当中。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;路由器它是属于&lt;strong&gt;网络层设备&lt;/strong&gt;，通常是以包为单位进行数据的发送。&lt;/p&gt;
&lt;p&gt;在路由器的子接口，是有分割广播域的作用，所以当我们用&lt;strong&gt;交换机做VLAN以后，都是要在路由器上做一个三层的路由。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2、使用方式的区别&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;网关它可以是路由器，交换机或者是PC。在同一网段之内进行通信，是不需要将网关介入其中，只有当主机个非本网段设备进行通信的时候，才需要将数据包全部发给网关设备，再经由网关设备进行转发或者是有路由处理等。&lt;/p&gt;
&lt;p&gt;路由器它是一个网络层系统，路由器在现在市场上一般是被分成了两大类，一类是单协议路由器，另一类是多协议路由器。路由器它可以进行数据格式的转换，成为不同&lt;strong&gt;于协议之间的网络互连的必要设备。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3、功能上的区别&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;网关可以分为传输型网关和应用型网关，它的功能是充当转换重任，实质上就是一个网络通向其他网络的IP地址。&lt;/p&gt;
&lt;p&gt;路由器的功能主要有：连通不同的网络和信息传输作用。按照使用可分为：接入、企业级、骨干级、太比特、多WAN以及3G无线等。&lt;/p&gt;
&lt;h2 id=&#34;711-http缓存技术&#34;&gt;7.11 HTTP缓存技术
&lt;/h2&gt;&lt;p&gt;HTTP 缓存有两种实现方式，分别是&lt;strong&gt;强制缓存和协商缓存&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;强制缓存&#34;&gt;强制缓存
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;强缓存指的是只要浏览器判断缓存没有过期，则直接使用浏览器的本地缓存，决定是否使用缓存的主动性在于浏览器这边。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如下图中，返回的是 200 状态码，&lt;strong&gt;但在 size 项中标识的是 from disk cache，就是使用了强制缓存。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://img-blog.csdnimg.cn/1cb6bc37597e4af8adfef412bfc57a42.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;强缓存是利用下面这两个 HTTP 响应头部（Response Header）字段实现的，它们都用来表示资源在客户端缓存的有效期：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Cache-Control&lt;/code&gt;， 是一个相对时间；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Expires&lt;/code&gt;，是一个绝对时间；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果 HTTP 响应头部同时有 Cache-Control 和 Expires 字段的话，&lt;strong&gt;Cache-Control的优先级高于 Expires&lt;/strong&gt; 。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cache-control 选项更多一些，设置更加精细，所以建议使用 Cache-Control 来实现强缓存&lt;/strong&gt;。具体的实现流程如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当浏览器第一次请求访问服务器资源时，服务器会在返回这个资源的同时，在 Response 头部加上 Cache-Control，Cache-Control 中设置了过期时间大小；&lt;/li&gt;
&lt;li&gt;浏览器再次请求访问服务器中的该资源时，会先&lt;strong&gt;通过请求资源的时间与 Cache-Control 中设置的过期时间大小，来计算出该资源是否过期&lt;/strong&gt;，如果没有，则使用该缓存，否则重新请求服务器；&lt;/li&gt;
&lt;li&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;服务器再次收到请求后，会&lt;strong&gt;再次更新 Response 头部的 Cache-Control。&lt;/strong&gt;&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;协商缓存&#34;&gt;协商缓存
&lt;/h3&gt;&lt;p&gt;当我们在浏览器使用开发者工具的时候，你可能会看到过某些&lt;strong&gt;请求的响应码是 &lt;code&gt;304&lt;/code&gt;&lt;/strong&gt;，这个是告诉浏览器可以使用本地缓存的资源，通常这种通过服务端告知客户端是否可以使用缓存的方式被称为协商缓存。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.jsdelivr.net/gh/xiaolincoder/ImageHost4@main/%E7%BD%91%E7%BB%9C/http1.1%E4%BC%98%E5%8C%96/%E7%BC%93%E5%AD%98etag.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;上图就是一个协商缓存的过程，所以&lt;strong&gt;协商缓存就是与服务端协商之后，通过协商结果来判断是否使用本地缓存&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;协商缓存可以基于两种头部来实现。&lt;/p&gt;
&lt;p&gt;第一种：请求头部中的 &lt;strong&gt;&lt;code&gt;If-Modified-Since&lt;/code&gt; 字段&lt;/strong&gt;与&lt;strong&gt;响应头部中的 &lt;code&gt;Last-Modified&lt;/code&gt;&lt;/strong&gt; 字段实现，这两个字段的意思是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;响应头部中的 &lt;code&gt;Last-Modified&lt;/code&gt;：&lt;strong&gt;标示这个响应资源的最后修改时间&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;请求头部中的 &lt;code&gt;If-Modified-Since&lt;/code&gt;：当资源过期了，&lt;strong&gt;发现响应头中具有 Last-Modified 声明，则再次发起请求的时候带上 Last-Modified 的时间&lt;/strong&gt;，服务器收到请求后发现有 If-Modified-Since 则与被请求资源的最后修改时间进行对比（Last-Modified），如果最后修改时间较新（大），说明资源又被改过，则返回最新资源，**HTTP 200 OK；**如果最后修改时间较旧（小），说明资源无新修改，&lt;strong&gt;响应 HTTP 304 走缓存。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;第二种：请求头部中的 &lt;strong&gt;&lt;code&gt;If-None-Match&lt;/code&gt; 字段与响应头部中的 &lt;code&gt;ETag&lt;/code&gt; 字段&lt;/strong&gt;，这两个字段的意思是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;响应头部中 &lt;code&gt;Etag&lt;/code&gt;：唯一标识响应资源；&lt;/li&gt;
&lt;li&gt;请求头部中的 &lt;code&gt;If-None-Match&lt;/code&gt;：当资源过期时，浏览器发现响应头里有 Etag，则再次向服务器发起请求时，&lt;strong&gt;会将请求头If-None-Match 值设置为 Etag 的值&lt;/strong&gt;。服务器收到请求后进行比对，如果资源没有变化返回 304，如果资源变化了返回 200。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;第一种实现方式是基于时间实现的&lt;/strong&gt;，第二种&lt;strong&gt;实现方式是基于一个唯一标识实现的&lt;/strong&gt;，相对来说&lt;strong&gt;后者可以更加准确地判断文件内容是否被修改&lt;/strong&gt;，避免&lt;strong&gt;由于时间篡改导致的不可靠问题。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果 HTTP 响应头部同时有 Etag 和 Last-Modified 字段的时候， &lt;strong&gt;Etag 的优先级更高&lt;/strong&gt;，也就是先会判断 Etag 是否变化了，如果 Etag 没有变化，然后再看 Last-Modified。&lt;/p&gt;
&lt;p&gt;注意，&lt;strong&gt;协商缓存这两个字段都需要配合强制缓存中 Cache-control 字段来使用，只有在未能命中强制缓存的时候，才能发起带有协商缓存字段的请求。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://img-blog.csdnimg.cn/d92026ce085b401c95cf02b7ce9b7fae.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;使用 ETag 字段实现的协商缓存的过程如下；&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;当浏览器第一次请求访问服务器资源时，服务器会在返回这个资源的同时，&lt;strong&gt;在 Response 头部加上 ETag 唯一标识，这个唯一标识的值是根据当前请求的资源生成的；&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;当浏览器再次请求访问服务器中的该资源时，首先会先检查强制缓存是否过期，如果没有过期，则直接使用本地缓存&lt;/strong&gt;；如果缓存过期了，&lt;strong&gt;会在 Request 头部加上 If-None-Match 字段&lt;/strong&gt;，该字段的值就是 ETag 唯一标识；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;服务器再次收到请求后，&lt;/p&gt;
&lt;p&gt;会根据请求中的 If-None-Match 值与当前请求的资源生成的唯一标识进行比较&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;如果值相等，则返回 304 Not Modified，不会返回资源&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;如果&lt;strong&gt;不相等，则返回 200 状态码和返回资源，并在 Response 头部加上新的 ETag 唯一标识；&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果浏&lt;strong&gt;览器收到 304 的请求响应状态码，则会从本地缓存中加载资源，否则更新资源&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;https://s3-us-west-2.amazonaws.com/secure.notion-static.com/cde24490-d47b-49ae-b566-abdfe80c2328/Untitled.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Untitled&#34;
	
	
&gt;&lt;/p&gt;
</description>
        </item>
        <item>
        <title>计算机网络学习小结</title>
        <link>https://Salmooo.github.io/p/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%AD%A6%E4%B9%A0%E5%B0%8F%E7%BB%93/</link>
        <pubDate>Wed, 16 Oct 2019 19:32:27 +0800</pubDate>
        
        <guid>https://Salmooo.github.io/p/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%AD%A6%E4%B9%A0%E5%B0%8F%E7%BB%93/</guid>
        <description>&lt;p&gt;#1. 网络结构&lt;/p&gt;
&lt;h2 id=&#34;11-计算机网络结构&#34;&gt;1.1 计算机网络结构？
&lt;/h2&gt;&lt;p&gt;计算机网络一共有3种模型。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;OSI七层结构&lt;/li&gt;
&lt;li&gt;TCP/IP结构&lt;/li&gt;
&lt;li&gt;五层协议结构&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;https://uk-1259555870.cos.eu-frankfurt.myqcloud.com/20200216134035.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;OSI是Open Systems Interconnect&lt;/strong&gt;，也就是开放的互联系统，将复杂的互联网系统划分为不同块，方便处理。&lt;/p&gt;
&lt;p&gt;实际应用中，并没有采用这个理论模型，而是使用TCP/IP协议的四层模型。&lt;/p&gt;
&lt;p&gt;而5层模型是一个理论上的网络通信模型，方便教学的时候理解，实际上并不存在。&lt;/p&gt;
&lt;h2 id=&#34;12-计算机网络中各层作用&#34;&gt;1.2 计算机网络中各层作用
&lt;/h2&gt;&lt;p&gt;&lt;img src=&#34;https://s3-us-west-2.amazonaws.com/secure.notion-static.com/6266643a-3e0e-4338-b94d-4017814417bf/Untitled.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Untitled&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;（1）应用层&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;应用层的任务是通过应用进程间的交互来完成特定网络应用，访问OSI环境的手段&lt;/strong&gt;，应用层协议定义的是应用进程（进程:主机中正在运行的程序）间的通信和交互的规则。&lt;/p&gt;
&lt;p&gt;常见的协议有&lt;strong&gt;域名系统DNS&lt;/strong&gt;，万维网应用的&lt;strong&gt;HTTP协议&lt;/strong&gt;，支持&lt;strong&gt;电子邮件的SMTP协议&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;应用层是不用去关心数据是如何传输的，就类似于，我们寄快递的时候，只需要把包裹交给快递员，由他负责运输快递，我们不需要关心快递是如何被运输的。&lt;/p&gt;
&lt;p&gt;把&lt;strong&gt;应用层交互的数据单元&lt;/strong&gt;称为&lt;strong&gt;报文&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;（2）运输层&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;为两台主机进程之间的通信提供**通用的数据传输服务，端对端的可靠报文传递和错误恢复。**主要包含两种协议：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;传输控制协议 TCP&lt;/strong&gt;（Transmisson Control Protocol）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;面向连接 面向字节流 可靠 传输慢 流量控制阻塞控制 1v1&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;**面向连接（三次握手四次挥手）， 面向字节流（把应用层传下来的报文看成字节流，把字节流组织成大小不等的数据块），可靠（握手、ACK和重传机制），传输慢，**有流量控制阻塞控制。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;用户数据报协议 UDP&lt;/strong&gt;（User Datagram Protocol）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;无连接，面向报文，不可靠尽最大可能交付，传输快，没有流量控制和拥塞控制，可1vn to nv1&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;无连接的，尽最大可能交付，不可靠，面向报文（对于应用程序传下来的报文不合并也不拆分，只是添加 UDP 首部&lt;/strong&gt;） ，支持一对一、一对多、多对一和多对多的交互通信，传输快，没有流量控制拥塞控制。&lt;/p&gt;
&lt;p&gt;当然，&lt;strong&gt;UDP 也可以实现可靠传输，把 TCP 的特性在应用层上实现就可以&lt;/strong&gt;，不过要实现一个商用的可靠 UDP 传输协议，也不是一件简单的事情。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;应用需要传输的数据可能会非常大，如果直接传输就不好控制，因此当传输层的数据包大小超过 MSS（TCP 最大报文段长度） ，就要将数据包分块，这样即使中途有一个分块丢失或损坏了，只需要重新发送这一个分块，而不用重新发送整个数据包。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;在 TCP 协议中，我们把每个分块称为一个 TCP 段（&lt;em&gt;TCP Segment&lt;/em&gt;）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;当设备作为接收方时，传输层则要负责把数据包传给应用，但是一台设备上可能会有很多应用在接收或者传输数据，因此需要用一个编号将应用区分开来，这个编号就是&lt;strong&gt;端口&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;比如 80 端口通常是 Web 服务器用的，22 端口通常是远程登录服务器用的。&lt;/p&gt;
&lt;p&gt;而对于浏览器（客户端）中的每个标签栏都是一个独立的进程，操作系统会为这些进程分配临时的端口号。由于传输层的报文中会携带端口号，因此接收方可以识别出该报文是发送给哪个应用。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;（3）网络层&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;网络层的任务就是选择合适的网间路由和交换结点，确保数据及时传送，数据包传递与网际互连。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;网络层最常使用的是 IP 协议（&lt;em&gt;Internet Protocol&lt;/em&gt;），IP 协议会将传输层的报文作为数据部分，再加上 IP 包头组装成 IP 报文，如果 IP 报文大小超过 MTU（最大传输单元，Maximum Transmission Unit）以太网中一般为 1500 字节）就会&lt;strong&gt;再次进行分片&lt;/strong&gt;，得到一个即将发送到网络的 IP 报文。&lt;/p&gt;
&lt;p&gt;使用&lt;strong&gt;IP协议&lt;/strong&gt;，ARP协议，IP协议，ICMP协议，IGMP协议等。&lt;/p&gt;
&lt;p&gt;网络层有两个任务：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;把运输层产生的&lt;strong&gt;报文段&lt;/strong&gt;或用户数据报 &lt;strong&gt;封装成分组和包进行传送&lt;/strong&gt;。在 TCP/IP 体系结构中，由于网络层使用 &lt;strong&gt;IP 协议&lt;/strong&gt;，因此分组也叫 &lt;strong&gt;IP 数据报&lt;/strong&gt; ，简称 &lt;strong&gt;数据报&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;注意：不要把运输层的用户数据报UDP和网络层的IP数据报弄混。&lt;/li&gt;
&lt;li&gt;选择&lt;strong&gt;合适的路由，找到目的主机。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;网络层负责将数据从一个设备传输到另一个设备，世界上那么多设备，又该如何找到对方呢？因此，网络层需要有区分设备的编号。&lt;/p&gt;
&lt;p&gt;我们一般用 IP 地址给设备进行编号，对于 IPv4 协议， IP 地址共 32 位，分成了四段（比如，192.168.100.1），每段是 8 位。只有一个单纯的 IP 地址虽然做到了区分设备，但是寻址起来就特别麻烦，全世界那么多台设备，难道一个一个去匹配？这显然不科学。&lt;/p&gt;
&lt;p&gt;因此，需要将 IP 地址分成两种意义：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一个是&lt;strong&gt;网络号&lt;/strong&gt;，负责标识该 IP 地址是属于哪个「子网」的；&lt;/li&gt;
&lt;li&gt;一个是&lt;strong&gt;主机号&lt;/strong&gt;，负责标识同一「子网」下的不同主机；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;（4）数据链路层&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;数据链路层的任务是确保在直接相连的两个节点之间可靠地传输数据，并处理与物理层交互和链路管理相关的事务&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;两台主机之间的数据传输，总是在&lt;strong&gt;一段一段的链路&lt;/strong&gt;上传送的，这就需要使用专门的链路层的协议。&lt;/p&gt;
&lt;p&gt;在两个相邻节点之间传送数据时，&lt;strong&gt;数据链路层将网络层交下来的 IP 数据报组装程帧&lt;/strong&gt;，在两个相邻节点间的链路上传送帧。&lt;/p&gt;
&lt;p&gt;主要协议：1、Point-to-Point Protocal——PPP点到点。2、Ethernet——以太网。3、High-Level Data Link Control Protocal——高级链路控制协议。4、Frame Relay——帧中继。5、Asynchronous Transfer Mode——异步传输模式。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;每一帧包括数据和必要的控制信息（如同步信息，地址信息，差错控制等）。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;（5）物理层&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;物理层的任务就是&lt;strong&gt;透明地传输比特流&lt;/strong&gt;，尽可能屏蔽掉具体传输介质和物理设备的差异，确定电气规范，使其上面的数据链路层不必考虑网络的具体传输介质是什么。换句话说实际电路传送后比特流没有发生变化。&lt;/p&gt;
&lt;p&gt;Tips:&lt;/p&gt;
&lt;p&gt;网络接口层&lt;/p&gt;
&lt;p&gt;生成了 IP 头部之后，接下来要交给**网络接口层（Link Layer）在 IP 头部的前面加上 MAC 头部，**并封装成数据帧（Data frame）发送到网络上。&lt;/p&gt;
&lt;p&gt;主要为网络层提供「链路级别」传输的服务，负责在以太网、WiFi 这样的底层网络上发送原始数据包，工作在网卡这个层次，使用 MAC 地址来标识网络上的设备。&lt;/p&gt;
&lt;h1 id=&#34;2-tcpip协议&#34;&gt;2. TCP/IP协议
&lt;/h1&gt;&lt;h2 id=&#34;21-tcpip协议的结构&#34;&gt;2.1 TCP/IP协议的结构
&lt;/h2&gt;&lt;p&gt;&lt;img src=&#34;https://uk-1259555870.cos.eu-frankfurt.myqcloud.com/20200216134831.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://prod-files-secure.s3.us-west-2.amazonaws.com/55e47ff7-aae4-4da6-84db-815d6c9a8d65/bb471c0e-dc56-44d3-b972-2d88149a49cc/Untitled.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Untitled&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;-&lt;/p&gt;
&lt;p&gt;1、源端口号（Source Port）
16位的源端口字段包含初始化通信的端口号。源端口和IP地址的作用是标识报文的返回地址。&lt;/p&gt;
&lt;p&gt;2、目的端口号（Destination Port）
　16位的目的端口字段定义传输的目的。这个端口指明接收方计算机上的应用程序接口。&lt;/p&gt;
&lt;p&gt;3、序列号（Sequence Number）
该字段用来标识TCP源端设备向目的端设备发送的字节流，它表示在这个报文段中的第几个数据字节。序列号是一个32位的数。&lt;/p&gt;
&lt;p&gt;4、确认号（Acknowledge Number）
　　TCP使用32位的确认号字段标识期望收到的下一个段的第一个字节，并声明此前的所有数据已经正确无误地收到，因此，确认号应该是上次已成功收到的数据字节序列号加1。收到确认号的源计算机会知道特定的段已经被收到。确认号的字段只在ACK标志被设置时才有效。
5、首部长度
长度为4位，用于表示TCP报文首部的长度。用4位（bit）表示，十进制值就是[0,15]，一个TCP报文前20个字节是必有的，后40个字节根据情况可能有可能没有。如果TCP报文首部是20个字节，则该位应是20/4=5。
6、保留位（Reserved）
长度为6位，必须是0，它是为将来定义新用途保留的。
7、标志（Code Bits）
长度为6位，在TCP报文中不管是握手还是挥手还是传数据等，这6位标志都很重要。6位从左到右依次为：
1.
• URG：紧急标志位，说明紧急指针有效；
• ACK：确认标志位，多数情况下空，说明确认序号有效； 取1时表示应答字段有效，也即TCP应答号将包含在TCP段中，为0则反之。
• PSH：推标志位，置位时表示接收方应立即请求将报文交给应用层；
• RST：复位标志，用于重建一个已经混乱的连接，用来复位产生错误的连接，也会用来拒绝错误和非法的数据包。
• SYN：同步标志，该标志仅在三次握手建立TCP连接时有效
• FIN：结束标志，表示发送端已经发送到数据末尾，数据传送完成，发送FIN标志位的TCP段，连接将被断开。
8、窗口大小（Window Size）
长度为16位，TCP流量控制由连接的每一端通过声明的窗口大小来提供。
9、检验和（Checksum）
长度为16位，该字段覆盖整个TCP报文端，是个强制性的字段，是由发送端计算和存储，到接收端后，由接收端进行验证。
10、紧急指针（Urgent Pointer）
长度为16位，指向数据中优先部分的最后一个字节，通知接收方紧急数据的长度，该字段在URG标志置位时有效。
11、选项（Options）
长度为0-40B（字节），必须以4B为单位变化，必要时可以填充0。通常包含：最长报文大小（MaximumSegment Size，MSS）、窗口扩大选项、时间戳选项、选择性确认（Selective ACKnowlegement，SACK）等。
12、数据
可选报文段数据部分。&lt;/p&gt;
&lt;p&gt;-&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;首先，&lt;strong&gt;源端口号&lt;/strong&gt;和&lt;strong&gt;目标端口&lt;/strong&gt;号是不可少的，如果没有这两个端口号，数据就不知道应该发给哪个应用。&lt;/li&gt;
&lt;li&gt;接下来有包的&lt;strong&gt;序&lt;/strong&gt;号，这个是为了解决包乱序的问题。&lt;/li&gt;
&lt;li&gt;还有应该有的是&lt;strong&gt;确认号&lt;/strong&gt;，目的是确认发出去对方是否有收到。如果没有收到就应该重新发送，直到送达，这个是&lt;strong&gt;为了解决不丢包的问题&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;接下来还有一些&lt;strong&gt;状态位flag&lt;/strong&gt;。例如 &lt;code&gt;SYN&lt;/code&gt; 是发起一个连接，&lt;code&gt;ACK&lt;/code&gt; 是回复，&lt;code&gt;RST&lt;/code&gt; 是重新连接，&lt;code&gt;FIN&lt;/code&gt; 是结束连接等。  TCP 是面向连接的，&lt;strong&gt;因而双方要维护连接的状态，这些带状态位的包的发送，会引起双方的状态变更。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;还有一个重要的就是&lt;strong&gt;窗口大小&lt;/strong&gt;。TCP 要做&lt;strong&gt;流量控制&lt;/strong&gt;，通信双方各声明一个窗口（缓存大小），标识自己当前能够的处理能力，别发送的太快，撑死我，也别发的太慢，饿死我。&lt;/li&gt;
&lt;li&gt;除了做流量控制以外，TCP还会做&lt;strong&gt;拥塞控制&lt;/strong&gt;，对于真正的通路堵车不堵车，它无能为力，唯一能做的就是控制自己，也即控制发送的速度。不能改变世界，就改变自己嘛。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;udp报头&#34;&gt;UDP报头
&lt;/h2&gt;&lt;p&gt;&lt;img src=&#34;https://prod-files-secure.s3.us-west-2.amazonaws.com/55e47ff7-aae4-4da6-84db-815d6c9a8d65/0b2ea092-a08a-4264-bbe7-5e8107707be0/Untitled.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Untitled&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;-&lt;/p&gt;
&lt;p&gt;每个 UDP 报文分为 &lt;strong&gt;UDP 报头和 UDP 数据区&lt;/strong&gt;两部分。报头由 &lt;strong&gt;4 个 16 位长（2 字节）字段&lt;/strong&gt;组成，分别说明该报文的源端口、目的端口、报文长度和校验值。&lt;/p&gt;
&lt;p&gt;UDP 报文中每个字段的含义如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;源端口：这个字段占据 UDP 报文头的前 16 位，通常包含发送数据报的应用程序所使用的 UDP 端口。接收端的应用程序利用这个字段的值作为发送响应的目的地址。&lt;strong&gt;这个字段是可选的，所以发送端的应用程序不一定会把自己的端口号写入该字段中。如果不写入端口号，则把这个字段设置为 0。这样，接收端的应用程序就不能发送响应了。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;目的端口：接收端计算机上 &lt;strong&gt;UDP 软件使用的端口，占据 16 位。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;长度：该字段&lt;strong&gt;占据 16 位&lt;/strong&gt;，表示 UDP 数据报长度**，包含 UDP 报文头和 UDP 数据长度**。因为 &lt;strong&gt;UDP 报文头长度是 8 个字节，所以这个值最小为 8。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;校验值：&lt;strong&gt;该字段占据 16 位&lt;/strong&gt;，可以检验数据在&lt;strong&gt;传输过程中是否被损坏。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;ip数据报的首部&#34;&gt;IP数据报的首部
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;注：IP数据报的格式，能够说明IP协议都具有什么功能。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://img-blog.csdn.net/20160216233020326&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;-&lt;/p&gt;
&lt;h3 id=&#34;ip数据报首部固定部分&#34;&gt;IP数据报首部——固定部分
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;1.1 版本&lt;/strong&gt; &lt;strong&gt;占4位，指IP协议的版本。&lt;/strong&gt; 通信双方使用的IP协议的版本必须一致。 &lt;strong&gt;IP协议版本号为4(即IPv4)，IP协议版本号为6(即IPv6)。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;1.2 首部长度 占4位，可表示的最大十进制数值是15。&lt;/strong&gt; 这个字段所表示数的单位是32位字(&lt;strong&gt;即4字节&lt;/strong&gt;)，因此，当IP的首部长度为1111(即十进制的15)时，&lt;strong&gt;首部长度就达到最大值60个字节&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;最常用的首部长度就是&lt;strong&gt;20个字节(即首部长度为0101&lt;/strong&gt;)，这时不使用任何选项，是固定首部的长度。 当IP分组的&lt;strong&gt;首部长度不是4字节的整数倍时，必须利用最后的填充字段加以填充&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;1.3 区分服务&lt;/strong&gt; &lt;strong&gt;占8位，用来获得更好的服务。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;1.4 总长度&lt;/strong&gt; &lt;strong&gt;占16位，指首部和数据之和的长度。&lt;/strong&gt; 数据报的最大长度为&lt;strong&gt;2^16 -1 = 65535字节&lt;/strong&gt;。“MTU是Maximum Transmission Unit的缩写。意思是网络上传送的最大数据包&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;在IP层下面的每一种数据链路层都有其自己的帧格式，其中包括帧格式中的数据字段的最大长度，这称为最大传送单元MTU(Maximum Transfer Unit)。
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;当一个IP数据报封装成数据链路层的帧时，此数据报的总长度(即首部加上数据部分)，一定不能**超过下面的数据链路层的MTU值。**
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;虽然使用尽可能长的数据报会使传输效率提高，但由于以太网的普遍应用，所以实际上使用的**数据报长度很少有超过1500字节的。**
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;为了不使IP数据报的传输效率降低，有关IP的标准文档规定，所有的**主机和路由器必须能够处理的IP数据报的长度不得少于576字节。这个数值也就是最小的IP数据报的总长度。**
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;当数据报长度超过网络所容许的最大传送单元MTU时，就必须把**过长的数据报进行分片后才能在网络上传送(“片偏移”字段相关)。**
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;这时，**数据报首部中的总长度不是指未分片前的数据报长度，而是指分片后的每一个分片的首部长度与数据长度的总和。**
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;1.5 标识&lt;/strong&gt; &lt;strong&gt;占16位，指IP软件在存储器中维持一个计数器，每产生一个数据报&lt;/strong&gt;，&lt;strong&gt;计数器就加1&lt;/strong&gt;，并将此值付给标识字段。&lt;/p&gt;
&lt;p&gt;但这个&lt;strong&gt;标识并不是序号&lt;/strong&gt;，因为&lt;strong&gt;IP是无连接服务&lt;/strong&gt;，数据报不存在按序接收的问题。 当数据报由于&lt;strong&gt;长度超过网络的MTU而必须分片&lt;/strong&gt;时，这个标识字段的值就会被复制到所有的数据报片的标识字段中。相&lt;strong&gt;同的标识字段的值使分片后的各数据报片最后能正确地重装成为原来的数据报&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;1.6 标志&lt;/strong&gt; &lt;strong&gt;占3位，但目前只有两位有意义。&lt;/strong&gt; 标志字段中的最低位记为MF(More Fragment)。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;(1) MF=1即表示后面“还有分片”的数据报。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;(2)MF=0即表示这已是若干数据报片中的最后一个。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;标志字段中的&lt;strong&gt;中间位DF&lt;/strong&gt;(Don’t Fragment)，意思是**“不能分片”**，只有当DF=0时才允许分片。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;1.7 片偏移&lt;/strong&gt; &lt;strong&gt;占13位，指出较长的分钟再分片后，某片在原分组中的相对位置。&lt;/strong&gt; &lt;strong&gt;也就是说，相对于用户数据字段的起点，该片从何处开始。&lt;/strong&gt; 片偏移以8个字节为偏移单位。&lt;/p&gt;
&lt;p&gt;也就是说，&lt;strong&gt;每个分片的长度一定是8字节(64位)的整数倍。&lt;/strong&gt; 例子：一数据报的总长度为3820字节，其数据部分为3800字节长(使用固定首部)，需要分片为长度不超过1420字节的数据报片。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;因固定首部长度为20字节，因此每个数据报片的数据部分长度不能超过1400字节&lt;/strong&gt;。于是分成3个数据报片，其数据部分的长度分别分为&lt;strong&gt;1400,1400和1000字节&lt;/strong&gt;。&lt;strong&gt;原始数据报首部被复制为各数据报片的首部，但必须修改有关字段的值。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;1.8 生存时间&lt;/strong&gt; &lt;strong&gt;占8位，常用的英文缩写是TTL(Time To Live)，表明数据报在网络中的寿命。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;由&lt;strong&gt;发出数据报的源点设置这个字段&lt;/strong&gt;。 其目的是为了防止无法交付的数据报&lt;strong&gt;无限制地在因特网中兜圈子，因而白白浪费网络资源&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;随着技术的发展，&lt;strong&gt;TTL字段的功能改为“跳数限制”&lt;/strong&gt;。路由器在转发数据报之前就把TTL值减1。若TTL值减少到零，就丢弃这个数据报，不再转发。&lt;/p&gt;
&lt;p&gt;因此，&lt;strong&gt;现在TTL的单位不再是秒，而是跳数&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;TTL的意义是指明数据报在因特网中&lt;strong&gt;至多可经过多少个路由器&lt;/strong&gt;。显然，数据报能在因特网中经过的&lt;strong&gt;路由器的最大数是255&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;若把&lt;strong&gt;TTL的初始值设置为1，就表示这个数据报只能在本局域网中传送&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;1.9 协议&lt;/strong&gt; &lt;strong&gt;占8位，指出此数据报携带的数据是使用何种协议，一遍使目的主机的IP层知道应将数据部分上交给哪个处理过程。&lt;/strong&gt; 常用的一些协议和相应协议的字段值。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;1.10 首部检验和&lt;/strong&gt; &lt;strong&gt;占16位，只检验数据报的首部&lt;/strong&gt;，但不包括数据部分。&lt;/p&gt;
&lt;p&gt;这是因为数据报每经过一个路由器，路由器都要重新计算一下&lt;strong&gt;首部检验和&lt;/strong&gt;(一些字段，如生存时间、标志、片偏移等都可能发生变化)。 &lt;strong&gt;不检验数据部分可减少计算的工作量&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;为了进一步减少计算检验和的工作量，IP首部的检验和不采用&lt;strong&gt;复杂的CRC检验码&lt;/strong&gt;而是采用以下算法： (1)在发送方，先把IP数据报首部划分为许多16位字的序列，并把检验和字段置零。 (2)用反码算术运算把所有16位字相加后，将得到的和的反码写入检验和字段， (3)接收方收到数据报后，将首部的所有16位字再使用&lt;strong&gt;反码算术运算相加一次。将得到的和取反码&lt;/strong&gt;，即得出接收方检验和的计算结果。若首部未发生任何变化，则此结果必为0，于是就保留这个数据报，否则即认为出错，并将此数据报丢弃。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;1.11 源地址&lt;/strong&gt; &lt;strong&gt;占32位。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;1.12 目的地址&lt;/strong&gt; &lt;strong&gt;占32位。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;ip数据报首部可变部分&#34;&gt;&lt;strong&gt;IP数据报首部——可变部分&lt;/strong&gt;
&lt;/h3&gt;&lt;p&gt;IP首部的可变部分就是一个选项字段。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;2.1 可选字段(长度可变)&lt;/strong&gt; 选项字段用&lt;strong&gt;来支持排错、测量以及安全等措施，内容很丰富&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;此字段的长度可变，从&lt;strong&gt;1~40个字节不等&lt;/strong&gt;，取决于所选择的项目。 某些选项项目只需要1个字节，它只包括1个字节的选项代码。但还有些选项需要多个字节，这些选项一个个拼接起来，中间不需要有分隔符。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;2.2 填充&lt;/strong&gt; &lt;strong&gt;最后用全0的填充字段补齐成为4字节的整数倍。&lt;/strong&gt; 注：增加首部的可变部分是为了&lt;strong&gt;增加IP数据报的功能&lt;/strong&gt;，但这同时也使得IP数据报的&lt;strong&gt;首部长度成为可变的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这就增加了每一个路由器处理数据报的开销。实际上这些选项很少被使用。新的IP版本&lt;strong&gt;IPv6就把IP数据报的首部长度做成固定的&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;22-tcp和udp的区别&#34;&gt;2.2 TCP和UDP的区别
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;TCP：&lt;strong&gt;面向连接&lt;/strong&gt;（三次握手四次挥手），&lt;strong&gt;可靠&lt;/strong&gt;（握手、ACK和重传机制），&lt;strong&gt;面向字节流&lt;/strong&gt;（把应用层传下来的报文看成字节流，把字节流组织成大小不等的数据块），&lt;strong&gt;传输慢&lt;/strong&gt;， &lt;strong&gt;有流量控制阻塞控制&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;UDP：&lt;strong&gt;无连接的&lt;/strong&gt;，尽最大可能交付不可靠，面向报文（对于应用程序传下来的报文不合并也不拆分，只是添加 UDP 首部） ，支持一对一、一对多、多对一和多对多的交互通信, &lt;strong&gt;传输快&lt;/strong&gt;，&lt;strong&gt;没有流量控制拥塞控制&lt;/strong&gt;，&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;解释一下报文和字节流的区别：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;字节流&lt;/strong&gt;：**发送次数和接收次数可以不相同。**比如向水池倒了20盆水，可以开水龙头一次性全放出。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;报文&lt;/strong&gt;：&lt;strong&gt;发送次数和接收次数必须相同&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;两者的应用场景&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TCP：&lt;strong&gt;效率要求相对低&lt;/strong&gt;，但对&lt;strong&gt;准确性要求相对高&lt;/strong&gt;的场景。
&lt;ul&gt;
&lt;li&gt;比如邮件，远程登录，文件传输等对&lt;strong&gt;准确性要求较高&lt;/strong&gt;的地方, 远程控制（SSH）,File Transfer Protocol（FTP）,邮件（SMTP、IMAP）等,点对点文件传出（微信等）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;UDP：&lt;strong&gt;效率要求相对高&lt;/strong&gt;，&lt;strong&gt;传输快速&lt;/strong&gt;，对&lt;strong&gt;准确性要求相对低&lt;/strong&gt;的场景。
&lt;ul&gt;
&lt;li&gt;比如&lt;strong&gt;QQ聊天、在线视频、网络语音电话&lt;/strong&gt;等响应&lt;strong&gt;速度要求高&lt;/strong&gt;的场景，广播通信（广播、多播）。网络游戏, 音视频传输, DNS, Ping, 直播&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;**第三类：模糊地带（TCP、UDP 都可以考虑），HTTP（目前以 TCP 为主），**文件传输？&lt;/p&gt;
&lt;h3 id=&#34;tcp报文段&#34;&gt;TCP报文段
&lt;/h3&gt;&lt;p&gt;&lt;img src=&#34;https://img-blog.csdnimg.cn/20190421142056148.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3psMTMwMTUyMTQ0NDI=,size_16,color_FFFFFF,t_70&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;序号 ：&lt;strong&gt;用于对字节流进行编号&lt;/strong&gt;，例如序号为 301，表示第一个字节的编号为301，&lt;strong&gt;如果携带的数据长度为 100 字节，那么下一个报文段的序号应为 401&lt;/strong&gt;，&lt;strong&gt;没有携带数据就是302&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;确认号ack ：&lt;strong&gt;期望收到的下一个报文段的序号&lt;/strong&gt;。例如 B 正确收到 A 发送来的一个报文段，序号为 501，携带的数据长度为 200 字节，因此 B 期望下一个报文段 的序号为 701，B 发送给 A 的确认报文段中确认号就为 701，没有携带数据就是502。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据偏移&lt;/strong&gt; ：指的是&lt;strong&gt;数据部分距离报文段起始处的偏移量&lt;/strong&gt;，实际上&lt;strong&gt;指的是首部的长度&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;确认 ACK ：&lt;strong&gt;当 ACK=1 时确认号字段ack有效，否则无效&lt;/strong&gt;。TCP 规定，&lt;strong&gt;在连接建立后所有传送的报文段都必须把 ACK 置 1&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;同步 SYN ：&lt;strong&gt;在连接建立时用来同步序号&lt;/strong&gt;。当 &lt;strong&gt;SYN=1，ACK=0 时表示这是一个连接请求报文段&lt;/strong&gt;。若对方同意建立连接，则响应报文中 &lt;strong&gt;SYN=1，ACK=1&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;终止 FIN ：&lt;strong&gt;用来释放一个连接&lt;/strong&gt;，&lt;strong&gt;当 FIN=1 时，表示此报文段的发送方的数据已发送完毕&lt;/strong&gt;，并要求释放连接。&lt;/li&gt;
&lt;li&gt;窗口 ：&lt;strong&gt;窗口值作为接收方让发送方设置其发送窗口&lt;/strong&gt;的依据。之所以要有这个限制，是因为&lt;strong&gt;接收方的数据缓存空间&lt;/strong&gt;是有限的。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MTU&lt;/code&gt;：一个网络包的最大长度，以太网中一般为 &lt;code&gt;1500&lt;/code&gt; 字节。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MSS&lt;/code&gt;：除去 IP 和 TCP 头部之后，一个网络包所能容纳的 TCP 数据的最大长度。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;如何查看 TCP 的连接状态？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;TCP 的连接状态查看，在 Linux 可以通过 &lt;code&gt;netstat -napt&lt;/code&gt; 命令查看。&lt;/p&gt;
&lt;h2 id=&#34;24-三次握手和四次挥手&#34;&gt;2.4 三次握手和四次挥手
&lt;/h2&gt;&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://jiangren.work/2019/08/01/Socket%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B%E5%8E%9F%E7%90%86/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://jiangren.work/2019/08/01/Socket%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B%E5%8E%9F%E7%90%86/&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;三次握手&#34;&gt;&lt;strong&gt;三次握手&lt;/strong&gt;
&lt;/h3&gt;&lt;p&gt;所谓&lt;strong&gt;三次握手&lt;/strong&gt;是指&lt;strong&gt;建立&lt;/strong&gt;一个TCP连接时，&lt;strong&gt;需要客户端和服务器发送3个包&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://i.bmp.ovh/imgs/2019/07/8dabc100eb0549e0.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;名词解释：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SYN：Synchronize，&lt;strong&gt;同步标志位&lt;/strong&gt;，&lt;strong&gt;为1时表示序列号有效&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;ACK：Acknowledgment，&lt;strong&gt;确认标志位&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;seq：Synchronize Sequence Number，&lt;strong&gt;同步序列号&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;ack：&lt;strong&gt;确认序列号&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;握手过程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;第一次握手：客户端发送&lt;strong&gt;SYN标志为1&lt;/strong&gt;的包，以及&lt;strong&gt;同步序列号x&lt;/strong&gt;，并指明打算连接的&lt;strong&gt;服务器端口&lt;/strong&gt;。此时，&lt;strong&gt;connect进入阻塞状态。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;客户端会随机初始化序号（&lt;code&gt;client_isn&lt;/code&gt;），将此序号置于 TCP 首部的「序号」字段中，同时把 &lt;code&gt;SYN&lt;/code&gt; 标志位置为 &lt;code&gt;1&lt;/code&gt; ，表示 &lt;code&gt;SYN&lt;/code&gt; 报文。接着把第一个 SYN 报文发送给服务端，表示向服务端发起连接，该报文不包含应用层数据，之后客户端处于 &lt;code&gt;SYN-SENT&lt;/code&gt; 状态。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;第二次握手：服务器收到后，发送&lt;strong&gt;SYN和ACK标志为1&lt;/strong&gt;的包，同时也发送一个自己的&lt;strong&gt;同步序列号y&lt;/strong&gt;，外加一个确认序列号&lt;strong&gt;ack=x+1&lt;/strong&gt;。此时accept进入阻塞状态。&lt;/p&gt;
&lt;p&gt;服务端收到客户端的 &lt;code&gt;SYN&lt;/code&gt; 报文后，首先服务端也随机初始化自己的序号（&lt;code&gt;server_isn&lt;/code&gt;），将此序号填入 TCP 首部的「序号」字段中，其次把 TCP 首部的「确认应答号」字段填入 &lt;code&gt;client_isn + 1&lt;/code&gt;, 接着把 &lt;code&gt;SYN&lt;/code&gt; 和 &lt;code&gt;ACK&lt;/code&gt; 标志位置为 &lt;code&gt;1&lt;/code&gt;。最后把该报文发给客户端，该报文也不包含应用层数据，之后服务端处于 &lt;code&gt;SYN-RCVD&lt;/code&gt; 状态。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;第三次握手：客户端收到后，再次发送&lt;strong&gt;ACK=1&lt;/strong&gt;，以及&lt;strong&gt;同步序列号seq(x+1)和确认序列号ack(y+1)&lt;/strong&gt;，与此同时，connect返回。当服务器收到ACK=1时，accept返回。&lt;/p&gt;
&lt;p&gt;客户端收到服务端报文后，还要向服务端回应最后一个应答报文，首先该应答报文 TCP 首部 &lt;code&gt;ACK&lt;/code&gt; 标志位置为 &lt;code&gt;1&lt;/code&gt; ，其次「确认应答号」字段填入 &lt;code&gt;server_isn + 1&lt;/code&gt; ，最后把报文发送给服务端，&lt;strong&gt;这次报文可以携带客户到服务器的数据&lt;/strong&gt;，之后客户端处于 &lt;code&gt;ESTABLISHED&lt;/code&gt; 状态。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;服务器收到客户端的应答报文后，也进入 &lt;code&gt;ESTABLISHED&lt;/code&gt; 状态。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;从上面的过程可以发现&lt;strong&gt;第三次握手是可以携带数据的，前两次握手是不可以携带数据的&lt;/strong&gt;，这也是面试常问的题。&lt;/p&gt;
&lt;h3 id=&#34;四次挥手&#34;&gt;&lt;strong&gt;四次挥手&lt;/strong&gt;
&lt;/h3&gt;&lt;p&gt;&lt;img src=&#34;https://s3-us-west-2.amazonaws.com/secure.notion-static.com/69db7c9b-4035-4d2c-98a7-48038e4fa9db/Untitled.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Untitled&#34;
	
	
&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;客户端打算关闭连接，此时会发送一个 TCP 首部 &lt;code&gt;FIN&lt;/code&gt; 标志位被置为 &lt;code&gt;1&lt;/code&gt; 的报文，也即 &lt;code&gt;FIN&lt;/code&gt; 报文，之后客户端进入 &lt;code&gt;FIN_WAIT_1&lt;/code&gt; 状态。&lt;/li&gt;
&lt;li&gt;服务端收到该报文后，就向客户端发送 &lt;code&gt;ACK&lt;/code&gt; 应答报文，接着服务端进入 &lt;code&gt;CLOSED_WAIT&lt;/code&gt; 状态。&lt;/li&gt;
&lt;li&gt;客户端收到服务端的 &lt;code&gt;ACK&lt;/code&gt; 应答报文后，之后进入 &lt;code&gt;FIN_WAIT_2&lt;/code&gt; 状态。&lt;/li&gt;
&lt;li&gt;等待服务端处理完数据后，也向客户端发送 &lt;code&gt;FIN&lt;/code&gt; 报文，之后服务端进入 &lt;code&gt;LAST_ACK&lt;/code&gt; 状态。&lt;/li&gt;
&lt;li&gt;客户端收到服务端的 &lt;code&gt;FIN&lt;/code&gt; 报文后，回一个 &lt;code&gt;ACK&lt;/code&gt; 应答报文，之后进入 &lt;code&gt;TIME_WAIT&lt;/code&gt; 状态&lt;/li&gt;
&lt;li&gt;服务器收到了 &lt;code&gt;ACK&lt;/code&gt; 应答报文后，就进入了 &lt;code&gt;CLOSED&lt;/code&gt; 状态，至此服务端已经完成连接的关闭。&lt;/li&gt;
&lt;li&gt;客户端在经过 &lt;code&gt;2MSL&lt;/code&gt; 一段时间后，自动进入 &lt;code&gt;CLOSED&lt;/code&gt; 状态，至此客户端也完成连接的关闭&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;q1-为什么不能用两次握手连接&#34;&gt;Q&lt;strong&gt;1. 为什么不能用两次握手连接&lt;/strong&gt;
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;三次握手才可以&lt;strong&gt;阻止重复历史连接的初始化&lt;/strong&gt;（主要原因）&lt;/li&gt;
&lt;li&gt;三次握手才可以&lt;strong&gt;同步双方的初始序列号&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;三次握手&lt;strong&gt;才可以避免资源浪费&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;原因一：避免历史连接&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;一个「旧 SYN 报文」比「最新的 SYN 」 报文早到达了服务端；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;那么此时服务端就会回一个 &lt;code&gt;SYN + ACK&lt;/code&gt; 报文给客户端；&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;客户端&lt;strong&gt;收到后可以根据自身的上下文，判断这是一个历史连接&lt;/strong&gt;（序列号过期或超时），那么客户端就会发送 &lt;code&gt;RST&lt;/code&gt; 报文给服务端，表示中止这一次连接。&lt;/p&gt;
&lt;p&gt;主要是因为&lt;strong&gt;在两次握手的情况下，「被动发起方」没有中间状态给「主动发起方」来阻止历史连接，导致「被动发起方」可能建立一个历史连接，造成资源浪费&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;两次握手的情况下，「被动发起方」在收到 SYN 报文后，就进入 ESTABLISHED 状态，意味着这时可以给对方发送数据给，但是「主动发」起方此时还没有进入 ESTABLISHED 状态，假设这次是历史连接，主动发起方判断到此次连接为历史连接，那么就会回 RST 报文来断开连接，而「被动发起方」在第一次握手的时候就进入 ESTABLISHED 状态，所以它可以发送数据的，但是它并不知道这个是历史连接，它只有在收到 RST 报文后，才会断开连接&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;原因二：同步双方初始序列号&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;TCP 协议的通信双方， 都必须维护一个「序列号」， 序列号是可靠传输的一个关键因素，它的作用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;接收方可以去除重复的数据；&lt;/li&gt;
&lt;li&gt;接收方可以根据数据包的序列号按序接收；&lt;/li&gt;
&lt;li&gt;可以标识发送出去的数据包中， 哪些是已经被对方收到的（&lt;strong&gt;通过 ACK 报文中的序列号知道&lt;/strong&gt;）；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;四次握手其实也能够可靠的同步双方的初始化序号，但由于&lt;strong&gt;第二步和第三步可以优化成一步&lt;/strong&gt;，所以就成了「三次握手」。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;而两次握手只保证了一方的初始序列号能被对方成功接收，没办法保证双方的初始序列号都能被确认接收&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;原因三：避免资源浪费&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;如果只有「两次握手」，当客户端的 &lt;code&gt;SYN&lt;/code&gt; 请求连接在网络中阻塞，客户端没有接收到 &lt;code&gt;ACK&lt;/code&gt; 报文，就会重新发送 &lt;code&gt;SYN&lt;/code&gt; ，由于没有第三次握手，&lt;strong&gt;服务器不清楚客户端是否收到了自己发送的建立连接的 &lt;code&gt;ACK&lt;/code&gt; 确认信号&lt;/strong&gt;，&lt;strong&gt;所以每收到一个 &lt;code&gt;SYN&lt;/code&gt; 就只能先主动建立一个连接，这会造成什么情况呢？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果客户端的 &lt;code&gt;SYN&lt;/code&gt; 阻塞了，重复发送多次 &lt;code&gt;SYN&lt;/code&gt; 报文，那么服务器在收到请求后就会&lt;strong&gt;建立多个冗余的无效链接，造成不必要的资源浪费。这样就会造成死锁。也有可能打开两个连接或更多。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://img-blog.csdn.net/20180609003511513&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;如果握手只是两次，服务器端在没有确定客户端是否对自己做出了正确应答的情况下就建立了连接，此时客户端因为意外连接请求报文早就失效了，也不可能再理服务器端，但是&lt;strong&gt;服务器端会一直傻傻地等待客户端会发来点数据，造成了资源的浪费&lt;/strong&gt;。这真是服务器端自己自作多情啊。&lt;/p&gt;
&lt;p&gt;三次握手如果第三次失败了会怎么样：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;失败了服务端收到不确认包，会超时重发5次，若还是没有收到确认包，或者收到了数据包，则服务端直接发送reset重置包结束本次连接。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;q2-为什么连接是三次握手而关闭时是四次挥手&#34;&gt;Q&lt;strong&gt;2. 为什么连接是三次握手，而关闭时是四次挥手&lt;/strong&gt;
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;为什么连接是三次握手：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;为了&lt;strong&gt;保证服务端能收接受到客户端的信息并能做出正确的应答而进行前两次(第一次和第二次)握手。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;为了&lt;strong&gt;保证客户端能够接收到服务端的信息并能做出正确的应答而进行后两次(第二次和第三次)握手&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;具体原因见Q1：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关闭时四次挥手：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;关闭连接时，&lt;strong&gt;服务端需要回复两次。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;四次挥手之所以结束时需要多一次请求是因为：&lt;strong&gt;客户端单方面无数据发送认为可以结束了，但是服务端不一定没有数据发送。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;所以服务端要将&lt;strong&gt;确信信息&lt;/strong&gt;和&lt;strong&gt;自身发起断开&lt;/strong&gt;分作两步。&lt;/p&gt;
&lt;p&gt;再来回顾下四次挥手双方发 &lt;code&gt;FIN&lt;/code&gt; 包的过程，就能理解为什么需要四次了。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;关闭连接时，客户端向服务端发送 &lt;code&gt;FIN&lt;/code&gt; 时，仅仅&lt;strong&gt;表示客户端不再发送数据了但是还能接收数据&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;服务器收到客户端的 &lt;code&gt;FIN&lt;/code&gt; 报文时，先回一个 &lt;code&gt;ACK&lt;/code&gt; 应答报文，而&lt;strong&gt;服务端可能还有数据需要处理和发送，等服务端不再发送数据时&lt;/strong&gt;，才发送 &lt;code&gt;FIN&lt;/code&gt; 报文给客户端来表示同意现在关闭连接。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;q3-为什么time_wait状态需要经过2msl&#34;&gt;Q3. &lt;strong&gt;为什么TIME_WAIT状态需要经过2MSL？&lt;/strong&gt;
&lt;/h3&gt;&lt;p&gt;2MSL是一次&lt;strong&gt;发送和回复的最大时间&lt;/strong&gt; (Maximum Segment Lifetime&lt;strong&gt;报文最大生存时间&lt;/strong&gt;)&lt;/p&gt;
&lt;p&gt;主要目的是怕最后一个ACK包对方没收到，那么对方在超时后将&lt;strong&gt;重发第三次握手的FIN包&lt;/strong&gt;，主动关闭端接到重发的FIN包后可以再发一个ACK应答包，客户端最后一次发送ACK可能会丢失，如果此时冒然关闭，&lt;strong&gt;会导致服务器没收到ACK，然后一直不断地发Fin。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;所以需要等2MSL，&lt;strong&gt;如果超过这个时间，都还没有收到服务器的信息，说明已经完成，可以关闭&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;MSL&lt;/code&gt; 是 Maximum Segment Lifetime，&lt;strong&gt;报文最大生存时间&lt;/strong&gt;，它是任何报文在网络上存在的最长时间，超过这个时间报文将被丢弃。&lt;/p&gt;
&lt;p&gt;因为 TCP 报文基于是 IP 协议的，而 IP 头中有一个 &lt;code&gt;TTL&lt;/code&gt; 字段，是 IP 数据报可以经过的最大路由数，&lt;strong&gt;每经过一个处理他的路由器此值就减 1&lt;/strong&gt;，当此值为 0 则数据报将被丢弃，同时发送 ICMP 报文通知源主机。&lt;/p&gt;
&lt;p&gt;MSL 与 TTL 的区别： MSL 的单位是时间，而 TTL 是经过路由跳数。所以 &lt;strong&gt;MSL 应该要大于等于 TTL 消耗为 0 的时间&lt;/strong&gt;，以确保报文已被自然消亡。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TTL 的值一般是 64，Linux 将 MSL 设置为 60秒，意味着 Linux 认为数据报文经过 64 个路由器的时间不会超过 30 秒，如果超过了，就认为报文已经消失在网络中了&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;q4-为什么每次建立-tcp-连接时初始化的序列号都要求不一样呢&#34;&gt;Q4. 为什么每次建立 TCP 连接时，初始化的序列号都要求不一样呢？
&lt;/h3&gt;&lt;p&gt;主要原因有两个方面：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;为了&lt;strong&gt;防止历史报文被下一个相同四元组的连接接收（主要方面）&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;为了&lt;strong&gt;安全性&lt;/strong&gt;，防止&lt;strong&gt;黑客伪造的相同序列号的 TCP 报文&lt;/strong&gt;被对方接收；&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;q5-初始序列号-isn-是如何随机产生的&#34;&gt;Q5. 初始序列号 ISN 是如何随机产生的？
&lt;/h3&gt;&lt;p&gt;起始 &lt;code&gt;ISN&lt;/code&gt; 是基于时钟的，每 4 微秒 + 1，转一圈要 4.55 个小时。&lt;/p&gt;
&lt;p&gt;RFC793 提到初始化序列号 ISN 随机生成算法：ISN = M + F(localhost, localport, remotehost, remoteport)。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;M&lt;/code&gt; 是一个计时器，这个计时器每隔 4 微秒加 1。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;F&lt;/code&gt; 是一个 Hash 算法，根据源 IP、目的 IP、源端口、目的端口生成一个随机数值。要保证 Hash 算法不能被外部轻易推算得出，用 MD5 算法是一个比较好的选择。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可以看到，随机数是会&lt;strong&gt;基于时钟计时器递增的&lt;/strong&gt;，基本不可能会随机成一样的初始化序列号。&lt;/p&gt;
&lt;h3 id=&#34;q6-既然-ip-层会分片为什么-tcp-层还需要-mss-呢&#34;&gt;Q6. 既然 IP 层会分片，为什么 TCP 层还需要 MSS 呢？
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;MTU&lt;/code&gt;&lt;/strong&gt;：一个网络包的最大长度，以太网中一般为 &lt;code&gt;1500&lt;/code&gt; 字节；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;MSS&lt;/code&gt;&lt;/strong&gt;：除去 IP 和 TCP 头部之后，一个网络包所能容纳的 TCP 数据的最大长度；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果在 TCP 的整个报文（头部 + 数据）交给 IP 层进行分片，会有什么异常呢？&lt;/p&gt;
&lt;p&gt;当 IP 层有一个超过 &lt;code&gt;MTU&lt;/code&gt; 大小的数据（TCP 头部 + TCP 数据）要发送，那么 IP 层就要进行分片，把数据分片成若干片，保证每一个分片都小于 MTU。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;把一份 IP 数据报进行分片以后，由目标主机的 IP 层来进行重新组装后，再交给上一层 TCP 传输层。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这看起来井然有序，但这存在隐患的，&lt;strong&gt;那么当如果一个 IP 分片丢失，整个 IP 报文的所有分片都得重传&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;因为 IP 层本身没有超时重传机制，它由传输层的 TCP 来负责超时和重传&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;所以，为了达到最佳的传输效能 TCP 协议在&lt;strong&gt;建立连接的时候通常要协商双方的 MSS 值&lt;/strong&gt;，当 TCP 层发现数据超过 MSS 时，则就先会进行分片，当然由它形成的 IP 包的长度也就不会大于 MTU ，自然也就不用 IP 分片了。&lt;/p&gt;
&lt;h3 id=&#34;q7-第一次第二次第三次握手丢失了会发生什么&#34;&gt;Q7. 第一次，第二次，第三次握手丢失了，会发生什么？
&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;第一次握手丢失了，会发生什么？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;当客户端想和服务端建立 TCP 连接的时候，首先第一个发的就是 SYN 报文，然后进入到 &lt;code&gt;SYN_SENT&lt;/code&gt; 状态。在这之后，&lt;strong&gt;如果客户端迟迟收不到服务端的 SYN-ACK 报文（第二次握手），就会触发「超时重传」机制，重传 SYN 报文。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;不同版本的操作系统可能超时时间不同，有的 1 秒的，也有 3 秒的，&lt;strong&gt;这个超时时间是写死在内核里的，如果想要更改则需要重新编译内核&lt;/strong&gt;，比较麻烦。当客户端在 1 秒后没收到服务端的 SYN-ACK 报文后，客户端就会重发 SYN 报文，那到底重发几次呢？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;在 Linux 里，客户端的 SYN 报文最大重传次数由 &lt;code&gt;tcp_syn_retries&lt;/code&gt;内核参数控制&lt;/strong&gt;，这个参数是可以自定义的，默认值一般是 5。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;通常，第一次超时重传是在 1 秒后，第二次超时重传是在 2 秒，第三次超时重传是在 4 秒后，第四次超时重传是在 8 秒后，第五次是在超时重传 16 秒后。没错，每次超时的时间是上一次的 2 倍。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;当第五次超时重传后，会继续等待 32 秒，如果服务端仍然没有回应 ACK，客户端就不再发送 SYN 包，然后断开 TCP 连接。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;所以，总耗时是 1+2+4+8+16+32=63 秒，大约 1 分钟左右。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;第二次握手丢失了，会发生什么？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;当第二次握手丢失了，客户端和服务端都会重传&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;客户端会重传 SYN 报文，也就是第一次握手，最大重传次数由 &lt;code&gt;tcp_syn_retries&lt;/code&gt;内核参数决定；&lt;/li&gt;
&lt;li&gt;服务端会重传 SYN-ACK 报文，也就是第二次握手，最大重传次数由 &lt;code&gt;tcp_synack_retries&lt;/code&gt; 内核参数决定。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果第二次握手丢失了，服务端就收不到第三次握手，于是&lt;strong&gt;服务端这边会触发超时重传机制，重传 SYN-ACK 报文&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在 Linux 下，SYN-ACK 报文的最大重传次数由 &lt;code&gt;tcp_synack_retries&lt;/code&gt;内核参数决定，默认值是 5。&lt;/p&gt;
&lt;p&gt;当服务端收到客户端的第一次握手后，就会回 SYN-ACK 报文给客户端，这个就是第二次握手，此时服务端会进入 &lt;code&gt;SYN_RCVD&lt;/code&gt; 状态。&lt;/p&gt;
&lt;p&gt;第二次握手的 &lt;code&gt;SYN-ACK&lt;/code&gt; 报文其实有两个目的 ：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第二次握手里的 ACK， 是对第一次握手的确认报文；&lt;/li&gt;
&lt;li&gt;第二次握手里的 SYN，是服务端发起建立 TCP 连接的报文；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以，如果第二次握手丢了，就会发送比较有意思的事情，具体会怎么样呢？&lt;/p&gt;
&lt;p&gt;因为第二次握手报文里是包含对客户端的第一次握手的 ACK 确认报文。&lt;/p&gt;
&lt;p&gt;如果客户端迟迟没有收到第二次握手，&lt;strong&gt;那么客户端就觉得可能自己的 SYN 报文（第一次握手）丢失了，于是客户端就会触发超时重传机制，重传 SYN 报文&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;然后，因为第二次握手中包含服务端的 SYN 报文，所以当客户端收到后，需要给服务端发送 ACK 确认报文（第三次握手），服务端才会认为该 SYN 报文被客户端收到了。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;第三次握手丢失了，会发生什么？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;当第三次握手丢失了，服务端那一方迟迟收不到这个确认报文，就会触发超时重传机制，重传 SYN-ACK 报文&lt;/strong&gt;，直到收到第三次握手，或者达到最大重传次数。&lt;/p&gt;
&lt;p&gt;客户端收到服务端的 SYN-ACK 报文后，就会给服务端回一个 ACK 报文，也就是第三次握手，此时客户端状态进入到 &lt;code&gt;ESTABLISH&lt;/code&gt; 状态。&lt;/p&gt;
&lt;p&gt;因为这个第三次握手的 ACK 是对第二次握手的 SYN 的确认报文，&lt;strong&gt;所以当第三次握手丢失了，如果服务端那一方迟迟收不到这个确认报文，就会触发超时重传机制，重传 SYN-ACK 报文&lt;/strong&gt;，直到收到第三次握手，或者达到最大重传次数。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;注意，&lt;strong&gt;ACK 报文是不会有重传的，当 ACK 丢失了，就由对方重传对应的报文&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;q8-第一次第二次第三次第四次挥手丢失了都会发生什么&#34;&gt;Q8. 第一次，第二次，第三次，第四次挥手丢失了，都会发生什么？
&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;第一次挥手丢失了，会发生什么？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;如果第一次挥手丢失了，那么客户端迟迟收不到被动方的 ACK 的话，也就会触发超时重传机制，重传 FIN 报文，重发次数由 &lt;code&gt;tcp_orphan_retries&lt;/code&gt; 参数控制。当客户端重传 FIN 报文的次数超过 &lt;code&gt;tcp_orphan_retries&lt;/code&gt; 后，就不再发送 FIN 报文，直接进入到 &lt;code&gt;close&lt;/code&gt; 状态。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;当客户端（主动关闭方）调用 close 函数后，就会向服务端发送 FIN 报文，试图与服务端断开连接，此时客户端的连接进入到 &lt;code&gt;FIN_WAIT_1&lt;/code&gt; 状态。正常情况下，如果能及时收到服务端（被动关闭方）的 ACK，则会很快变为 &lt;code&gt;FIN_WAIT2&lt;/code&gt;状态。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;第二次挥手丢失了，会发生什么？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;所以如果服务端的第二次挥手丢失了，&lt;strong&gt;客户端就会触发超时重传机制，重传 FIN 报文，直到收到服务端的第二次挥手，或者达到最大的重传次数。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;当服务端收到客户端的第一次挥手后，就会先回一个 ACK 确认报文，此时服务端的连接进入到 &lt;code&gt;CLOSE_WAIT&lt;/code&gt; 状态。在前面我们也提了，ACK 报文是不会重传的。&lt;/p&gt;
&lt;p&gt;这里提一下，当客户端收到第二次挥手，也就是收到服务端发送的 ACK 报文后，客户端就会处于 &lt;code&gt;FIN_WAIT2&lt;/code&gt; 状态，在这个状态需要等服务端发送第三次挥手，也就是服务端的 FIN 报文。&lt;/p&gt;
&lt;p&gt;对于 close 函数关闭的连接，由于&lt;strong&gt;无法再发送和接收数据&lt;/strong&gt;，&lt;strong&gt;所以&lt;code&gt;FIN_WAIT2&lt;/code&gt; 状态不可以持续太久，而 &lt;code&gt;tcp_fin_timeout&lt;/code&gt; 控制了这个状态下连接的持续时长，默认值是 60 秒。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这意味着对于调用 close 关闭的连接，如果在 60 秒后还没有收到 FIN 报文，客户端（主动关闭方）的连接就会直接关闭。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;但是注意，如果主动关闭方&lt;strong&gt;使用 shutdown 函数关闭连接且指定只关闭发送方向，而接收方向并没有关闭，那么意味着主动关闭方还是可以接收数据的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;如果主动关闭方一直没收到第三次挥手，那么主动关闭方的连接将会一直处于 &lt;code&gt;FIN_WAIT2&lt;/code&gt; 状态（&lt;code&gt;tcp_fin_timeout&lt;/code&gt; 无法控制 shutdown 关闭的连接）。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;第三次挥手丢失了，会发生什么？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;如果迟迟收不到这个 ACK，服务端就会重发 FIN 报文&lt;/strong&gt;，重发次数仍然由 &lt;strong&gt;&lt;code&gt;tcp_orphan_retries&lt;/code&gt;&lt;/strong&gt; 参数控制，这与客户端重发 FIN 报文的重传次数控制方式是一样的。&lt;/p&gt;
&lt;p&gt;当服务端（被动关闭方）收到客户端（主动关闭方）的 FIN 报文后，内核会自动回复 ACK，同时连接处于 &lt;code&gt;CLOSE_WAIT&lt;/code&gt; 状态，顾名思义，它表示等待应用进程调用 close 函数关闭连接。&lt;/p&gt;
&lt;p&gt;此时，内核是没有权利替代进程关闭连接，必须由&lt;strong&gt;进程主动调用 close 函数来触发服务端发送&lt;/strong&gt; FIN 报文。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;服务端处于 CLOSE_WAIT 状态时，调用了 close 函数，内核就会发出 FIN 报文，同时连接进入 LAST_ACK 状态，等待客户端返回 ACK 来确认连接关闭&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;第四次挥手丢失了，会发生什么？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;如果第四次挥手的 ACK 报文没有到达服务端，服务端就会重发 FIN 报文，重发次数仍然由前面介绍过的 &lt;code&gt;tcp_orphan_retries&lt;/code&gt; 参数控制。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;当客户端收到服务端的第三次挥手的 FIN 报文后，就会回 ACK 报文，也就是第四次挥手，此时客户端连接进入 &lt;code&gt;TIME_WAIT&lt;/code&gt; 状态。&lt;/p&gt;
&lt;p&gt;在 Linux 系统，TIME_WAIT 状态会持续 2MSL 后才会进入关闭状态。&lt;/p&gt;
&lt;p&gt;然后，服务端（被动关闭方）&lt;strong&gt;没有收到 ACK 报文前，还是处于 LAST_ACK&lt;/strong&gt; 状态。&lt;/p&gt;
&lt;h3 id=&#34;q9--为什么需要-time_wait-状态&#34;&gt;Q9.  为什么需要 TIME_WAIT 状态？
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;避免连接混淆或者连接冲突，防止历史连接中的数据，被后面相同四元组的连接错误的接收&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;保证「被动关闭连接」的一方，能被正确的关闭，确保最后的数据包被接收，或处理延迟包&lt;/strong&gt;；&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;TIME_WAIT 过多有什么危害？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;过多的 TIME-WAIT 状态主要的危害有两种：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;第一是内存资源占用&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;第二是对端口资源的占用&lt;/strong&gt;，一个 TCP 连接至少消耗「发起连接方」的一个本地端口；&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;25-tcp协议如何保证可靠性&#34;&gt;2.5 TCP协议如何保证可靠性
&lt;/h2&gt;&lt;p&gt;TCP 是通过&lt;strong&gt;序列号、确认应答、重发控制、连接管理以及窗口控制&lt;/strong&gt;等机制实现可靠性传输的&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;（1）&lt;strong&gt;采用&lt;/strong&gt;三次握手四次挥手&lt;/strong&gt;保证建立的&lt;strong&gt;传输信道是可靠的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;（2）&lt;strong&gt;采用了&lt;/strong&gt;ARQ自动(超时)重传请求&lt;/strong&gt;协议&lt;strong&gt;数据传输的可靠性&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;（3）&lt;strong&gt;采用&lt;/strong&gt;滑动窗口&lt;/strong&gt;协议进行&lt;strong&gt;流量控制。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;（4）&lt;strong&gt;使用&lt;/strong&gt;慢开始&lt;/strong&gt;、&lt;strong&gt;拥塞避免&lt;/strong&gt;、&lt;strong&gt;快重传&lt;/strong&gt;和&lt;strong&gt;快恢复&lt;/strong&gt;来进行&lt;strong&gt;拥塞控制&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;（5）校验和CRC计算方式&lt;/strong&gt;：在数据传输的过程中，将发送的数据段都当做一个&lt;strong&gt;16位的整数&lt;/strong&gt;。将这些&lt;strong&gt;整数加起来。并且前面的进位不能丢弃，补在后面，最后取反，得到校验和&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;发送方：在发送数据之前计算检验和，并进行校验和的填充。&lt;/p&gt;
&lt;p&gt;接收方：收到数据后，对数据以同样的方式进行计算，求出校验和，与发送方的进行比对。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;（6）确认应答与序列号&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;序列号：TCP传输时将每个字节的数据都进行了编号。&lt;/p&gt;
&lt;p&gt;确认应答：TCP传输的过程中，每次接收方收到数据后，都会对传输方进行确认应答。也就是发送ACK报文。这个ACK报文当中带有对应的确认序列号，告诉发送方，接收到了哪些数据，下一次的数据从哪里发。&lt;/p&gt;
&lt;h3 id=&#34;自动超时重传机制&#34;&gt;&lt;strong&gt;自动超时重传机制&lt;/strong&gt;
&lt;/h3&gt;&lt;p&gt;简单理解就是&lt;strong&gt;发送方在发送完数据后等待一个时间，时间到达没有接收到ACK报文，那么对刚才发送的数据进行重新发送&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;由于TCP传输时&lt;strong&gt;保证能够在任何环境下都有一个高性能的通信&lt;/strong&gt;，因此这个&lt;strong&gt;最大超时时间（也就是等待的时间）是动态计算的&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;在Linux中（BSD Unix和Windows下也是这样）超时以500ms为一个单位进行控制，每次判定超时重发的超时时间都是&lt;strong&gt;500ms的整数倍&lt;/strong&gt;。重发一次后，仍未响应，那么等待2500ms的时间后，再次重传。等待4500ms的时间继续重传。&lt;strong&gt;以一个指数的形式增长&lt;/strong&gt;。&lt;strong&gt;累计到一定的重传次数&lt;/strong&gt;，TCP就认为&lt;strong&gt;网络或者对端出现异常，强制关闭连接。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;具体步骤如下：&lt;/p&gt;
&lt;p&gt;（1）&lt;strong&gt;为了保证数据包的可靠传递，发送方必须把已发送的数据包保留在缓冲区&lt;/strong&gt;；&lt;/p&gt;
&lt;p&gt;（2）并为每个&lt;strong&gt;已发送的数据包启动一个超时定时器&lt;/strong&gt;；&lt;/p&gt;
&lt;p&gt;（3）如在&lt;strong&gt;定时器超时之前收到了对方发来的应答信息&lt;/strong&gt;（可能是对本包的应答，也可以是对本包后续包的应答），则&lt;strong&gt;释放该数据包占用的缓冲区;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;（4）否则，&lt;strong&gt;重传该数据包，直到收到应答或重传次数超过规定的最大次数为止。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;（5）接收方收到数据包后，&lt;strong&gt;先进行CRC校验，如果正确则把数据交给上层协议，然后给发送方发送一个累计应答包，表明该数据已收到&lt;/strong&gt;，&lt;strong&gt;如果接收方正好也有数据要发给发送方，应答包也可方在数据包中捎带过去。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果接收方收到二次重发的数据后，便进行ACK应答。&lt;/p&gt;
&lt;p&gt;如果接收方发现接收的数据已存在（判断存在的根据就是序列号，所以上面说&lt;strong&gt;序列号还有去除重复数据的作用&lt;/strong&gt;），那么&lt;strong&gt;直接丢弃，仍旧发送ACK应答&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&#34;26-tcp协议如何进行流量控制&#34;&gt;2.6 TCP协议如何进行流量控制？
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;控制流量的前提当然需要保证正确率可靠性，因此首先要引入ARQ(自动重传请求（Automatic Repeat-reQuest，&lt;em&gt;ARQ&lt;/em&gt;）协议。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;TCP采用&lt;strong&gt;大小可变的滑动窗口&lt;/strong&gt;进行流量控制，&lt;strong&gt;窗口大小的单位是字节&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;数据接收端将&lt;strong&gt;自己可以接受的缓冲区大小放入TCP首部中“窗口大小”字段&lt;/strong&gt;，通过ACK来通知数据传输。（&lt;strong&gt;在TCP的首部，有一个16位窗口字段，此字段就是用来存放窗口大小信息的。）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;无差错时，A向B发送&lt;strong&gt;分组&lt;/strong&gt;M1，B收到M1后向A回复，A收到回复后，发送下一个M2…..&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://uk-1259555870.cos.eu-frankfurt.myqcloud.com/20200214122158.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;如果出现差错，B没有收到信息，自然不会回复，&lt;strong&gt;A等待超时后&lt;/strong&gt;，&lt;strong&gt;自动重传&lt;/strong&gt;一个信息M，这就是所谓的ARQ。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;但停止等待ARQ协议信道利用率太低。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;所以需要使用&lt;strong&gt;连续ARQ协议来进行改善&lt;/strong&gt;。这个协议会&lt;strong&gt;连续发送一组数据包&lt;/strong&gt;，然后&lt;strong&gt;再等待这些数据包的ACK&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://uk-1259555870.cos.eu-frankfurt.myqcloud.com/20200214123227.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;连续ARQ协议&lt;/strong&gt;通常是&lt;strong&gt;结合滑动窗口协议&lt;/strong&gt;来使用的，&lt;strong&gt;发送方需要维持一个发送窗口&lt;/strong&gt;，如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://uk-1259555870.cos.eu-frankfurt.myqcloud.com/20200214123327.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;位于&lt;strong&gt;发送窗口内的5个分组&lt;/strong&gt;都可以连续发送出去，而不需要等待对方的确认，这样就提高了&lt;strong&gt;信道利用率&lt;/strong&gt;。、&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;发送方每收到一个确认，就把发送窗口向前滑动一个分组的位置。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;接收方一般都是采用累积确认的方式&lt;/strong&gt;。收到几个分组后，对按序到达的&lt;strong&gt;最后一个分组发送确认&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;滑动窗口需掌握的知识点：&lt;/p&gt;
&lt;p&gt;A、数据&lt;strong&gt;接收端将自己可以接受的缓冲区大小放入TCP首部中“窗口大小”字段&lt;/strong&gt;，&lt;strong&gt;通过ACK来通知数据传输端。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;B、&lt;strong&gt;窗口大小字段越大，说明网络的吞吐率越高。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;C、窗口大小指的是&lt;strong&gt;无需等待确认应答而可以继续发送数据的最大值&lt;/strong&gt;，即就是说&lt;strong&gt;不需要数据接收端的应答，可以一次连续的发送数据&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;D、&lt;strong&gt;操作系统内核为了维护滑动窗口，需要开辟发送缓冲区&lt;/strong&gt;，&lt;strong&gt;来记录当前还有哪些数据没有应答&lt;/strong&gt;，&lt;strong&gt;只有确认应答过的数据，才能从缓冲区删除&lt;/strong&gt;。 &lt;strong&gt;(PS：发送缓冲区如果太大，会有空间开销）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;E、数据接收端一旦&lt;strong&gt;发现自己的缓冲区快满了&lt;/strong&gt;，&lt;strong&gt;就会将窗口大小设置成一个更小的值通知给数据传输端，数据传输端收到这个值后，就会减慢自己的发送速度&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;F、如果&lt;strong&gt;数据接收端发现自己的缓冲区满了&lt;/strong&gt;，&lt;strong&gt;就会将窗口大小设置为0&lt;/strong&gt;，此时数据传输端不再传输数据，但是&lt;strong&gt;需要定期发送一个窗口探测数据段&lt;/strong&gt;，将&lt;strong&gt;数据接收端把窗口大小告诉数据传输端&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;TCP 规定是不允许同时减少缓存又收缩窗口的，而是采用先收缩窗口，过段时间再减少缓存，这样就可以避免了丢包情况。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;q1-tcp-是如何解决窗口关闭时潜在的死锁现象呢&#34;&gt;Q1. TCP 是如何解决窗口关闭时，潜在的死锁现象呢？
&lt;/h3&gt;&lt;p&gt;为了解决这个问题，&lt;strong&gt;TCP 为每个连接设有一个持续定时器&lt;/strong&gt;，&lt;strong&gt;只要 TCP 连接一方收到对方的零窗口通知，就启动持续计时器。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果&lt;strong&gt;持续计时器超时，就会发送窗口探测 ( Window probe ) 报文&lt;/strong&gt;，而对方在确认这个探测报文时，给出自己现在的接收窗口大小。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;窗口探测的次数一般为 3 次，每次大约 30-60 秒&lt;/strong&gt;（不同的实现可能会不一样）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果 3 次过后接收窗口还是 0 的话，有的 TCP 实现就会发 &lt;code&gt;RST&lt;/code&gt; 报文来中断连接。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;27-tcp协议如何进行拥塞控制&#34;&gt;2.7 TCP协议如何进行拥塞控制？
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;下载时我们的速度一般都是由慢变快，原因就是拥塞控制。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;网络拥塞是指在&lt;strong&gt;分组交换网络中传送分组的数目太多&lt;/strong&gt;时，由于&lt;strong&gt;存储转发节点的资源有限&lt;/strong&gt;而造成&lt;strong&gt;网络传输性能下降&lt;/strong&gt;的情况。&lt;/p&gt;
&lt;p&gt;常见的拥塞控制有：&lt;strong&gt;慢开始，拥塞避免，快重传，快恢复&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;慢开始：不要一开始就发送大量的数据，由小到大逐渐增加拥塞窗口的大小&lt;/strong&gt;, 一次RTT(&lt;em&gt;RTT&lt;/em&gt;(Round-Trip Time)：往返时延)后，也就是&lt;strong&gt;收到一次ACK后拥塞窗口就翻倍&lt;/strong&gt;，也就是指数型增长。&lt;/p&gt;
&lt;p&gt;**拥塞避免：**拥塞避免算法让拥塞窗口缓慢增长，&lt;strong&gt;即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1而不是加倍&lt;/strong&gt;。这样拥塞窗口按线性规律缓慢增长。&lt;/p&gt;
&lt;p&gt;发送方维持一个叫做&lt;strong&gt;拥塞窗口cwnd（congestion window）的状态变量&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;当cwnd到达ssthresh（慢启动阈值）时&lt;/strong&gt;，改用&lt;strong&gt;拥塞避免&lt;/strong&gt;算法。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;拥塞窗口 cwnd&lt;/strong&gt;是&lt;strong&gt;发送方维护的一个的状态变量&lt;/strong&gt;，它会根据&lt;strong&gt;网络的拥塞程度动态变化的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我们在前面提到过&lt;strong&gt;发送窗口 &lt;code&gt;swnd&lt;/code&gt; 和接收窗口 &lt;code&gt;rwnd&lt;/code&gt; 是约等于的关系&lt;/strong&gt;，那么由于&lt;strong&gt;加入了拥塞窗口的概念后，此时发送窗口的值是swnd = min(cwnd, rwnd)&lt;/strong&gt;，也就是&lt;strong&gt;拥塞窗口和接收窗口中的最小值。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;拥塞窗口 &lt;code&gt;cwnd&lt;/code&gt; 变化的规则：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;只要网络中&lt;strong&gt;没有出现拥塞，&lt;code&gt;cwnd&lt;/code&gt; 就会增大；&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;但网络中&lt;strong&gt;出现了拥塞，&lt;code&gt;cwnd&lt;/code&gt; 就减少；&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;快重传：&lt;strong&gt;我们可以剔除一些不必要的拥塞报文，提高网络吞吐量。比如接收方在&lt;/strong&gt;收到一个失序的报文段后就立即发出重复确认，而不要等到自己发送数据时捎带确认。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;快重传规定：&lt;strong&gt;发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段&lt;/strong&gt;，&lt;strong&gt;而不必继续等待设置的重传计时器时间到期&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://uk-1259555870.cos.eu-frankfurt.myqcloud.com/20200214125708.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;**快恢复：**主要是配合快重传。&lt;strong&gt;当发送方连续收到三个重复确认时，就执行“乘法减小”算法&lt;/strong&gt;，&lt;strong&gt;把ssthresh门限减半&lt;/strong&gt;（&lt;strong&gt;为了预防网络发生拥塞&lt;/strong&gt;）&lt;/p&gt;
&lt;p&gt;但&lt;strong&gt;接下来并不执行慢开始算法&lt;/strong&gt;，因&lt;strong&gt;为如果网络出现拥塞的话就不会收到好几个重复的确认&lt;/strong&gt;，&lt;strong&gt;收到三个重复确认说明网络状况还可以&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://uk-1259555870.cos.eu-frankfurt.myqcloud.com/20200214130136.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;快速重传机制&lt;strong&gt;只解决了一个问题，就是超时时间&lt;/strong&gt;的问题，但是它依然面临着另外一个问题。&lt;/p&gt;
&lt;p&gt;就是&lt;strong&gt;重传的时候，是重传之前的一个，还是重传所有的问题。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;比如对于上面的例子，是重传 Seq2 呢？还是重传 Seq2、Seq3、Seq4、Seq5 呢？因为发送端并不清楚这连续的三个 Ack 2 是谁传回来的。&lt;/p&gt;
&lt;p&gt;根据 TCP 不同的实现，以上两种情况都是有可能的。可见，这是一把双刃剑。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为了解决不知道该重传哪些 TCP 报文，于是就有 &lt;code&gt;SACK&lt;/code&gt; 方法。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;sack-方法&#34;&gt;SACK 方法
&lt;/h3&gt;&lt;p&gt;一种实现重传机制的方式叫：&lt;code&gt;SACK&lt;/code&gt;（ Selective Acknowledgment 选择性确认）。&lt;/p&gt;
&lt;p&gt;这种方式需要&lt;strong&gt;在 TCP 头部「选项」字段里加一个 &lt;code&gt;SACK&lt;/code&gt; 的东西&lt;/strong&gt;，它可以&lt;strong&gt;将缓存的地图发送给发送方&lt;/strong&gt;，这样发送方就可以知道哪些数据收到了，哪些数据没收到，知道了这些信息，就可以&lt;strong&gt;只重传丢失的数据&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;如某图例子(发送方收到了三次同样的 ACK 确认报文，于是就会触发快速重发机制，&lt;strong&gt;通过 &lt;code&gt;SACK&lt;/code&gt; 信息发现只有 &lt;code&gt;200~299&lt;/code&gt; 这段数据丢失&lt;/strong&gt;，则重发时，就只选择了这个 TCP 段进行重复。&lt;/p&gt;
&lt;h2 id=&#34;28-socket编程tcp&#34;&gt;2.8 Socket编程TCP
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;服务端和客户端初始化 &lt;code&gt;socket&lt;/code&gt;&lt;/strong&gt;，得到文件描述符；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;服务端调用 &lt;code&gt;bind&lt;/code&gt;()，将文件描述符绑定在 IP 地址和端口;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;服务端&lt;strong&gt;调用 &lt;code&gt;listen&lt;/code&gt;，进行监听&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;服务端调用 &lt;code&gt;accept&lt;/code&gt;，等待客户端连接；&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;客户端调用 &lt;code&gt;connect&lt;/code&gt;，向服务器端的地址和端口发起连接请求；&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;服务端 &lt;code&gt;accept&lt;/code&gt; &lt;strong&gt;返回用于传输的 &lt;code&gt;socket&lt;/code&gt; 的文件描述符；&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;客户端&lt;strong&gt;调用 &lt;code&gt;write&lt;/code&gt; 写入数据；服务端调用 &lt;code&gt;read&lt;/code&gt; 读取数据&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;客户端断开连接时，会调用 &lt;code&gt;close&lt;/code&gt;，那么&lt;strong&gt;服务端 &lt;code&gt;read&lt;/code&gt; 读取数据的时候，就会读取到了 &lt;code&gt;EOF&lt;/code&gt;&lt;/strong&gt;，待处理完数据后，服务端调用 &lt;code&gt;close&lt;/code&gt;，表示连接关闭。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Linux内核中会维护两个队列：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;半连接队列（SYN 队列）：接收到&lt;strong&gt;一个 SYN 建立连接请求，处于 SYN_RCVD 状态&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;全连接队列（Accpet 队列）：&lt;strong&gt;已完成 TCP 三次握手过程，处于 ESTABLISHED 状态；&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从上面的描述过程，我们可以得知&lt;strong&gt;客户端 connect 成功返回是在第二次握手，服务端 accept 成功返回是在三次握手成功之后。&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;客户端调用 close 了，连接是断开的流程是什么？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;我们看看客户端主动调用了 &lt;code&gt;close&lt;/code&gt;，会发生什么？&lt;/p&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://imgconvert.csdnimg.cn/aHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L2doL3hpYW9saW5jb2Rlci9JbWFnZUhvc3QyLyVFOCVBRSVBMSVFNyVBRSU5NyVFNiU5QyVCQSVFNyVCRCU5MSVFNyVCQiU5Qy9UQ1AtJUU0JUI4JTg5JUU2JUFDJUExJUU2JThGJUExJUU2JTg5JThCJUU1JTkyJThDJUU1JTlCJTlCJUU2JUFDJUExJUU2JThDJUE1JUU2JTg5JThCLzM3LmpwZw?x-oss-process=image/format,png&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;客户端调用 close 过程&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;客户端调用 &lt;code&gt;close&lt;/code&gt;，表明客户端没有数据需要发送了，则此时会向服务端发送 FIN 报文，进入 FIN_WAIT_1 状态；&lt;/li&gt;
&lt;li&gt;服务端接收到了 FIN 报文，TCP 协议栈会为 FIN 包&lt;strong&gt;插入一个文件结束符 &lt;code&gt;EOF&lt;/code&gt; 到接收缓冲区中，应用程序可以通过 &lt;code&gt;read&lt;/code&gt; 调用来感知这个 FIN 包&lt;/strong&gt;。
&lt;ul&gt;
&lt;li&gt;这个 &lt;code&gt;EOF&lt;/code&gt; 会被&lt;strong&gt;放在已排队等候的其他已接收的数据之后&lt;/strong&gt;，这就意味着&lt;strong&gt;服务端需要处理这种异常情况&lt;/strong&gt;，&lt;strong&gt;因为 EOF 表示在该连接上再无额外数据到达。此时，服务端进入 CLOSE_WAIT 状态&lt;/strong&gt;；&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;接着，当处理完数据后，自然就会读到 &lt;code&gt;EOF&lt;/code&gt;，于是也调用 &lt;code&gt;close&lt;/code&gt; 关闭它的套接字，这会使得服务端发出一个 FIN 包，之后处于 LAST_ACK 状态；&lt;/li&gt;
&lt;li&gt;客户端接收到服务端的 FIN 包，并发送 ACK 确认包给服务端，此时客户端将进入 TIME_WAIT 状态；&lt;/li&gt;
&lt;li&gt;服务端收到 ACK 确认包后，就进入了最后的 CLOSE 状态；&lt;/li&gt;
&lt;li&gt;客户端经过 &lt;code&gt;2MSL&lt;/code&gt; 时间之后，也进入 CLOSE 状态。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;29-增大-tcp-半连接队列和全连接队列的方式&#34;&gt;2.9 增大 TCP 半连接队列和全连接队列的方式
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;增大 TCP 半连接队列的方式是增大 &lt;strong&gt;/proc/sys/net/ipv4/tcp_max_syn_backlog；&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;增大 TCP 全连接队列的方式是增大 &lt;strong&gt;listen() 函数中的 backlog；&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Linux系统中，则使用两个队列syn queue, accept queue分别存储状态为SYN_REVD和ESTABLISHED的连接，并且在linux2.2及以后，backlog表示accept queue的大小，而syn queue大小由 &lt;code&gt;/proc/sys/net/ipv4/tcp_max_syn_backlog&lt;/code&gt;配置。&lt;/p&gt;
&lt;h2 id=&#34;内核参数somaxconn&#34;&gt;&lt;strong&gt;内核参数somaxconn&lt;/strong&gt;
&lt;/h2&gt;&lt;p&gt;全称：socket max connections 位置：&lt;code&gt;/proc/sys/net/core/somaxconn&lt;/code&gt; 这是系统层面对于backlog的控制，实际上accept queue的大小 = min(somaxconn, backlog)。&lt;/p&gt;
&lt;p&gt;因此在listen这个系统调用层面，backlog最终还是受限于somaxconn。&lt;/p&gt;
&lt;h2 id=&#34;查看队列&#34;&gt;&lt;strong&gt;查看队列&lt;/strong&gt;
&lt;/h2&gt;&lt;p&gt;`ss -l&lt;/p&gt;
&lt;p&gt;Netid   State    Recv-Q   Send-Q                                      Local Address:Port                     Peer Address:Port
tcp     LISTEN   0        128                                                  [::]:ssh                              [::]:*`&lt;/p&gt;
&lt;p&gt;在LISTEN状态下 Recv-Q 表示当前accept queue中的已连接数。Send-Q 表示总大小&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;210-如何优化-tcp&#34;&gt;2.10 如何优化 TCP
&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;你可以根据网络的稳定性和目标服务器的繁忙程度修改 **SYN 的重传次数**，**调整客户端的三次握手时间上限**。TCP 三次握手的性能提升；TCP 四次挥手的性能提升；TCP 数据传输的性能提升；
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&#34;q1-三次握手优化&#34;&gt;Q1: &lt;strong&gt;三次握手优化&lt;/strong&gt;
&lt;/h3&gt;&lt;blockquote&gt;
&lt;p&gt;客户端的优化&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;当客户端发起 SYN 包时，可以通过 &lt;code&gt;tcp_syn_retries&lt;/code&gt; 控制其重传的次数。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;服务端的优化&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;当服务端 SYN 半连接队列溢出后，会导致后续连接被丢弃，可以通过 &lt;code&gt;netstat -s&lt;/code&gt; 观察半连接队列溢出的情况。&lt;/p&gt;
&lt;p&gt;如果 SYN 半连接队列溢出情况比较严重，可以通过 &lt;code&gt;tcp_max_syn_backlog、somaxconn、backlog&lt;/code&gt; 参数来调整 SYN 半连接队列的大小。&lt;/p&gt;
&lt;p&gt;服务端回复 SYN+ACK 的重传次数由 &lt;code&gt;tcp_synack_retries&lt;/code&gt; 参数控制。&lt;/p&gt;
&lt;p&gt;如果遭受 SYN 攻击，应把 &lt;code&gt;tcp_syncookies&lt;/code&gt; 参数设置为 1，表示仅在 SYN 队列满后开启 syncookie 功能，可以保证正常的连接成功建立。&lt;/p&gt;
&lt;p&gt;服务端收到客户端返回的 ACK，会把连接移入 accpet 队列，等待进行调用 accpet() 函数取出连接。&lt;/p&gt;
&lt;p&gt;可以通过 &lt;code&gt;ss -lnt&lt;/code&gt; 查看服务端进程的 accept 队列长度&lt;/p&gt;
&lt;p&gt;如果 &lt;strong&gt;accept 队列溢出，系统默认丢弃 ACK&lt;/strong&gt;，如果可以把 &lt;code&gt;tcp_abort_on_overflow&lt;/code&gt; 设置为 1 ，表示用 RST 通知客户端连接建立失败。&lt;/p&gt;
&lt;p&gt;如果 accpet 队列溢出严重，可以通过 listen 函数的 &lt;code&gt;backlog&lt;/code&gt; 参数和 &lt;code&gt;somaxconn&lt;/code&gt; 系统参数提高队列大小，accept 队列长度取决于 min(backlog, somaxconn)。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;绕过三次握手&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;TCP Fast Open 功能可以绕过三次握手，使得 HTTP 请求减少了 1 个 RTT 的时间，Linux 下可以通过 &lt;code&gt;tcp_fastopen&lt;/code&gt; 开启该功能，同时必须保证服务端和客户端同时支持。&lt;/p&gt;
&lt;h3 id=&#34;q2-tcp-四次挥手的性能提升&#34;&gt;Q2: &lt;strong&gt;TCP 四次挥手的性能提升&lt;/strong&gt;
&lt;/h3&gt;&lt;p&gt;针对 TCP 四次挥手的优化，我们需要根据主动方和被动方四次挥手状态变化来调整系统 TCP 内核参数。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.jsdelivr.net/gh/xiaolincoder/ImageHost/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/TCP-%E5%8F%82%E6%95%B0/39.jpg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;四次挥手的优化策略&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;主动方的优化&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;主动发起 FIN 报文断开连接的一方，如果迟迟没收到对方的 ACK 回复，则会重传 FIN 报文，重传的次数由 &lt;code&gt;tcp_orphan_retries&lt;/code&gt; 参数决定。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当主动方收到 ACK 报文后，连接就进入 FIN_WAIT2 状态，根据关闭的方式不同，优化的方式也不同：&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;如果这是 close 函数关闭的连接，那么它就是孤儿连接。如果 &lt;code&gt;tcp_fin_timeout&lt;/code&gt; 秒内没有收到对方的 FIN 报文，连接就直接关闭。同时，为了应对孤儿连接占用太多的资源，&lt;code&gt;tcp_max_orphans&lt;/code&gt; 定义了最大孤儿连接的数量，超过时连接就会直接释放。&lt;/li&gt;
&lt;li&gt;反之是 shutdown 函数关闭的连接，则不受此参数限制；&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;当主动方接收到 FIN 报文，并返回 ACK 后，主动方的连接进入 TIME_WAIT 状态。这一状态会持续 1 分钟，为了防止 TIME_WAIT 状态占用太多的资源，&lt;code&gt;tcp_max_tw_buckets&lt;/code&gt; 定义了最大数量，超过时连接也会直接释放。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当 TIME_WAIT 状态过多时，还可以通过设置 &lt;code&gt;tcp_tw_reuse&lt;/code&gt; 和 &lt;code&gt;tcp_timestamps&lt;/code&gt; 为 1 ，将 TIME_WAIT 状态的端口复用于作为客户端的新连接，注意该参数只适用于客户端。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;被动方的优化&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;被动关闭的连接方应对非常简单，它在回复 ACK 后就进入了 CLOSE_WAIT 状态，等待进程调用 close 函数关闭连接。因此，出现大量 CLOSE_WAIT 状态的连接时，应当从应用程序中找问题。&lt;/p&gt;
&lt;p&gt;当被动方发送 FIN 报文后，连接就进入 LAST_ACK 状态，在未等到 ACK 时，会在 &lt;code&gt;tcp_orphan_retries&lt;/code&gt; 参数的控制下重发 FIN 报文。&lt;/p&gt;
&lt;h3 id=&#34;q3-tcp-数据传输的性能提升&#34;&gt;Q3: TCP 数据传输的性能提升
&lt;/h3&gt;&lt;p&gt;&lt;img src=&#34;https://cdn.jsdelivr.net/gh/xiaolincoder/ImageHost/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/TCP-%E5%8F%82%E6%95%B0/49.jpg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;h2 id=&#34;211-如何解决粘包&#34;&gt;2.11 如何解决粘包？
&lt;/h2&gt;&lt;p&gt;（1）&lt;strong&gt;发送方引起的粘包是由TCP协议本身造成的&lt;/strong&gt;，TCP为提高传输效率，&lt;strong&gt;发送方往往要收集到足够多的数据后才发送一包数据&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;若连续几次发送的数据都很少，&lt;strong&gt;通常TCP会根据优化算法把这些数据合成一包后一次发送出去&lt;/strong&gt;，这样接收方就收到了粘包数据。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;UDP不存在粘包问题, 是由于UDP发送的时候, 没有经过Negal算法优化, 不会将多个小包合并一次发送出去&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;另外，&lt;strong&gt;在UDP协议的接收端,采用了链式结构来记录每一个到达的UDP包，这样接收端应用程序一次recv只能从socket接收缓冲区中读出一个数据包&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;也就是说,发送端send了几次，接收端必须recv几次(无论recv时指定了多大的缓冲区)&lt;/p&gt;
&lt;p&gt;（2）&lt;strong&gt;接收方引起的粘包是由于接收方用户进程不及时接收数据，从而导致粘包现象。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这是因为接收方先把收到的数据放在系统接收缓冲区，用户进程从该缓冲区取数据，若下一包数据到达时前一包数据尚未被用户进程取走&lt;/strong&gt;，则下一包数据放到系统接收缓冲区时就接到前一包数据之后，而&lt;strong&gt;用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据，这样就一次取到了多包数据。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;粘包的问题出现是&lt;strong&gt;因为不知道一个用户消息的边界在哪，如果知道了边界在哪，接收方就可以通过边界来划分出有效的用户消息&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;一般有三种方式分包的方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;固定长度的消息；&lt;/li&gt;
&lt;li&gt;特殊字符作为边界；&lt;/li&gt;
&lt;li&gt;自定义消息结构。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;固定长度的消息固定长度的消息&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这种是最简单方法，即每个用户消息都是固定长度的，比如规定一个消息的长度是 64 个字节，当接收方接满 64 个字节，就认为这个内容是一个完整且有效的消息&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;但是这种方式灵活性不高，实际中很少用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;特殊字符作为边界&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们可以在两个用户消息之间插入一个特殊的字符串，这样接收方在接收数据时，读到了这个特殊字符，就把认为已经读完一个完整的消息。&lt;/p&gt;
&lt;p&gt;HTTP 是一个非常好的例子。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&#34;https://img-blog.csdnimg.cn/img_convert/a49a6bb8cd38ae1738d9c00aec68b444.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;HTTP 通过设置回车符、换行符作为 HTTP 报文协议的边界。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;有一点要注意，这个作为边界点的特殊字符，如果&lt;strong&gt;刚好消息内容里有这个特殊字符，我们要对这个字符转义&lt;/strong&gt;，&lt;strong&gt;避免被接收方当作消息的边界点而解析到无效的数据。&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;自定义消息结构&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们可以&lt;strong&gt;自定义一个消息结构，由包头和数据组成&lt;/strong&gt;，其中包头包是固定大小的，而且&lt;strong&gt;包头里有一个字段来说明紧随其后的数据有多大&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;比如这个消息结构体，首先 &lt;strong&gt;4 个字节大小的变量来表示数据长度&lt;/strong&gt;，真正的数据则在后面。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;struct&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;kt&#34;&gt;u_int32_t&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;message_length&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;kt&#34;&gt;char&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;message_data&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;message&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;当接收方接收到包头的大小（比如 4 个字节）后，就解析包头的内容，于是就可以知道数据的长度，然后接下来就继续读取数据，直到读满数据的长度，就可以组装成一个完整到用户消息来处理了。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;212-syn-报文什么时候情况下会被丢弃&#34;&gt;2.12 SYN 报文什么时候情况下会被丢弃？
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;开启 tcp_tw_recycle 参数，并且在 NAT 环境下，造成 SYN 报文被丢弃，由于 NAT 环境中的客户端可能有不同的时间戳时钟和值，&lt;strong&gt;tcp_tw_recycle&lt;/strong&gt; 在这种环境中可能会导致合法的 SYN 报文被误丢弃。因此，如果你的服务器有 NAT 后面的客户端连接，通常建议不要启用 &lt;strong&gt;tcp_tw_recycle&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;TCP 两个队列满了（半连接队列和全连接队列）&lt;/strong&gt;，造成 SYN 报文被丢弃&lt;/p&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://xiaolincoding.com/network/3_tcp/syn_drop.html#%E5%9D%91%E7%88%B9%E7%9A%84-tcp-tw-recycle&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;4.8 SYN 报文什么时候情况下会被丢弃？ | 小林coding (xiaolincoding.com)&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在 Linux 操作系统下，&lt;strong&gt;TIME_WAIT 状态的持续时间是 60 秒&lt;/strong&gt;，这意味着这 60 秒内，客户端一直会占用着这个端口。要知道，端口资源也是有限的，一般可以开启的端口为 32768~61000 ，也可以通过如下参数设置指定范围：&lt;/p&gt;
&lt;p&gt;不过，Linux 操作系统提供了两个可以系统参数来快速回收处于 TIME_WAIT 状态的连接，这两个参数都是默认关闭的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;net.ipv4.tcp_tw_reuse，如果开启该选项的话，客户端（连接发起方） 在调用 connect() 函数时，&lt;strong&gt;内核会随机找一个 time_wait 状态超过 1 秒的连接给新的连接复用&lt;/strong&gt;，所以该选项只适用于连接发起方。&lt;/li&gt;
&lt;li&gt;net.ipv4.tcp_tw_recycle，如果开启该选项的话，允许处于 TIME_WAIT 状态的连接被快速回收；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;要使得这两个选项生效，有一个前提条件，就是要打开 &lt;strong&gt;TCP 时间戳&lt;/strong&gt;，即&lt;strong&gt;net.ipv4.tcp_timestamps=1&lt;/strong&gt;（默认即为 1）。&lt;/p&gt;
&lt;p&gt;但是，&lt;strong&gt;tcp_tw_recycle 在使用了 NAT 的网络下是不安全的！&lt;/strong&gt;&lt;/p&gt;
&lt;h1 id=&#34;3-dns协议和arp协议&#34;&gt;3. DNS协议和ARP协议
&lt;/h1&gt;&lt;p&gt;这两个协议都是用于&lt;strong&gt;地址间的转化&lt;/strong&gt;，起到了“翻译官”的职责。&lt;/p&gt;
&lt;h2 id=&#34;31-dns解析过程是什么&#34;&gt;3.1 DNS解析过程是什么？
&lt;/h2&gt;&lt;p&gt;DNS (Domain Name System) 是 &lt;strong&gt;域名系统&lt;/strong&gt; 的英文缩写，是一种组织成域层次结构的计算机和网络服务命名系统。&lt;/p&gt;
&lt;p&gt;它用于 TCP/IP 网络，它从事将&lt;strong&gt;主机名或域名转换为实际 IP 地址&lt;/strong&gt;的工作，类似于&lt;strong&gt;翻译官&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;DNS查询时优先考虑&lt;strong&gt;本地的Host文件&lt;/strong&gt;和&lt;strong&gt;本地的DNS解析器&lt;/strong&gt;是否保留有&lt;strong&gt;缓存映射&lt;/strong&gt;，如果没有就&lt;strong&gt;向上一级请求&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;依次按照&lt;strong&gt;DNS根服务器，DNS顶层服务器，DNS管理方服务器&lt;/strong&gt;的顺序请求。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://uk-1259555870.cos.eu-frankfurt.myqcloud.com/20200120162326.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;所谓&lt;strong&gt;递归查询就是变更查询者，迭代查询则没有变更。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;32-什么是mac地址&#34;&gt;3.2 什么是MAC地址？
&lt;/h2&gt;&lt;p&gt;MAC地址是数据链路层和物理层&lt;strong&gt;使用的地址（硬件地址）&lt;/strong&gt;，IP地址网络层和以上各层使用的地址，是一种&lt;strong&gt;逻辑地址&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在发送数据时，数据从高层到低层，然后才到通信链路上传输。&lt;strong&gt;使用IP地址的IP数据报一旦交给了数据链路层，就被封装成了MAC帧&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;MAC帧在传送时使用的源地址和目的地址都是硬件地址。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://uk-1259555870.cos.eu-frankfurt.myqcloud.com/20200216103838.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;有了IP，为啥还需要MAC地址？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;事实上，&lt;strong&gt;IP协议的产生并不只是为解决上述的“广播问题”&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;还解决了很多其他网络传输过程会遇到的问题，&lt;strong&gt;比如一次传输的消息过大时，如何对消息进行分组等问题&lt;/strong&gt;。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;由于历史原因，MAC 地址及相关技术先出现，但是后来发现它并不能解决所有（已知）的问题，所以，先驱们发明了 IP 地址及相关技术来解决。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;另一个角度，个人认为，&lt;strong&gt;由于 MAC 地址没有办法表达网络中的子网的概念，而 IP 地址可以&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;如果网络互换设备（比如路由器）能从目标 MAC 地址中分析出目标网络，而不是只是目标主机，IP 地址还会出现吗？&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;有另一个有趣的问题：**如果历史反过来，**一开始就使用的是 IP 地址，而不是 MAC 地址，我们现在的网络世界会怎么样？&lt;/p&gt;
&lt;h2 id=&#34;33-arp协议工作机制是什么&#34;&gt;3.3 ARP协议工作机制是什么？
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;ARP（Address Resolution Protocol）即地址解析协议， 用于实现从 IP 地址到 MAC 地址的映射，即询问目标IP对应的MAC地址&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在每台安装有TCP/IP协议的&lt;strong&gt;电脑或路由器&lt;/strong&gt;里都有一个&lt;strong&gt;ARP缓存表&lt;/strong&gt;，&lt;strong&gt;表里的IP地址与MAC地址是一对应的&lt;/strong&gt;，如下表所示。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://uk-1259555870.cos.eu-frankfurt.myqcloud.com/20200216104026.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;解析MAC地址时，主机A&lt;strong&gt;首先在其ARP高速缓存中&lt;/strong&gt;查找有无主机B的IP地址。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果没有就就向本地网段发起一个ARP请求的广播包&lt;/strong&gt;，&lt;strong&gt;查询此目的主机对应的MAC地址&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;网络中所有的主机收到这个&lt;strong&gt;ARP请求&lt;/strong&gt;后，会检查数据包中的目的IP是否和自己的IP地址一致。&lt;/p&gt;
&lt;p&gt;如果相同，该主机首先&lt;strong&gt;将发送端的MAC地址和IP地址添加到自己的ARP列表中&lt;/strong&gt;，如果ARP表中已经存在该IP的信息，&lt;strong&gt;则将其覆盖&lt;/strong&gt;，然后给源主机发送一个&lt;strong&gt;ARP响应数据包&lt;/strong&gt;，&lt;strong&gt;告诉对方自己是它需要查找的MAC地址&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;源主机收到后&lt;strong&gt;在其ARP高速缓存中写入主机B的IP地址到硬件地址的映射&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;并且采用LRU机制，及时淘汰。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://uk-1259555870.cos.eu-frankfurt.myqcloud.com/20200216104350.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;查看 ARP 缓存内容&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在 Linux 系统中，&lt;strong&gt;我们可以使用 &lt;code&gt;arp -a&lt;/code&gt; 命令来查看 ARP 缓存的内容。&lt;/strong&gt;&lt;/p&gt;
&lt;h1 id=&#34;4-http协议&#34;&gt;4. HTTP协议
&lt;/h1&gt;&lt;h2 id=&#34;41-http常见的请求方法和状态码&#34;&gt;4.1 HTTP常见的请求方法和状态码
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;OPTIONS&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;返回服务器针对特定资源&lt;strong&gt;所支持的HTTP请求方法&lt;/strong&gt;，也可以利用向web服务器发送‘*’的请求来测试服务器的功能性。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;HEAD&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;向服务器索与GET请求相一致的响应，&lt;strong&gt;只不过响应体将不会被返回&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这一方法可以&lt;strong&gt;再不必传输整个响应内容的情况&lt;/strong&gt;下，就可以获取包&lt;strong&gt;含在响应小消息头中的元信息&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;GET&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;向特定的资源发出请求&lt;/strong&gt;。注意：&lt;strong&gt;GET方法不应当被用于产生“副作用”的操作中&lt;/strong&gt;，例如在Web Application中，其中一个原因是GET可能会被网络蜘蛛等随意访问。Loadrunner中对应get请求函数：web_link和web_url&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;POST&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;向指定资源提交数据进行处理请求&lt;/strong&gt;（例如提交表单或者上传文件）。&lt;/p&gt;
&lt;p&gt;数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。 Loadrunner中对应POST请求函数：web_submit_data,web_submit_form&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PUT&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;向指定资源位置&lt;strong&gt;上传其最新内容&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;DELETE&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;请求服务器&lt;strong&gt;删除Request-URL所标识的资源&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;TRACE&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;回显服务器收到的请求&lt;/strong&gt;，主要用于&lt;strong&gt;测试或诊断&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CONNECT&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;1）方法名称是&lt;strong&gt;区分大小写&lt;/strong&gt;的，当某个请求所针对的资源&lt;strong&gt;不支持对应的请求方法的时候，服务器应当返回状态码405&lt;/strong&gt;（Mothod Not Allowed）；&lt;/p&gt;
&lt;p&gt;当服务器&lt;strong&gt;不认识或者不支持对应的请求方法时，应返回状态码501&lt;/strong&gt;（Not Implemented）。&lt;/p&gt;
&lt;p&gt;2）HTTP服务器&lt;strong&gt;至少应该实现GET和HEAD/POST方法&lt;/strong&gt;，其他方法都是可选的，此外除上述方法，特定的HTTP服务器支持扩展自定义的方法。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://uk-1259555870.cos.eu-frankfurt.myqcloud.com/20200214133230.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;100 客户端必须继续发出请求  101 客户端要求服务器根据请求转换HTTP协议版本&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;200 交易成功 201 提示知道新文件的URL 202 接受和处理、但处理未完成&lt;/p&gt;
&lt;p&gt;203 返回信息不确定或不完整 204 请求收到，但返回信息为空&lt;/p&gt;
&lt;p&gt;205 服务器完成了请求，用户代理必须复位当前已经浏览过的文件 206 服务器已经完成了部分用户的GET请求&lt;/p&gt;
&lt;p&gt;300 请求的资源可在多处得到 301 永久重定向，在Location响应首部的值仍为当前URL(隐式重定向) 302 临时重定向，在Location响应首部的值仍为新的URL(显示重定向)&lt;/p&gt;
&lt;p&gt;303 建议客户端访问其他URL或访问方式 304 Not Modified 请求的资源没有改变 可以继续使用缓存 305 请求的资源必须从服务器指定的地址得到&lt;/p&gt;
&lt;p&gt;306 前一版本HTTP中使用的代码，现行版本中不再使用 307 声明请求的资源临时性删除&lt;/p&gt;
&lt;p&gt;400 错误请求，如语法错误 401 未授权402 保留有效ChargeTo头响应 403 禁止访问&lt;/p&gt;
&lt;p&gt;404 没有发现文件、查询或URL 405 用户在Request-Line字段定义的方法不允许&lt;/p&gt;
&lt;p&gt;406 根据用户发送的Accept拖，请求资源不可访问 407 类似401，用户必须首先在代理服务器上得到授权&lt;/p&gt;
&lt;p&gt;500 - 内部服务器错误 HTTP 500.100 - 内部服务器错误 HTTP 500-11 服务器关闭 HTTP&lt;/p&gt;
&lt;p&gt;500-12 应用程序重新启动 HTTP 500-13 - 服务器太忙 HTTP 500-14 - 应用程序无效 HTTP 500-15 - 不允许请求&lt;/p&gt;
&lt;p&gt;501 - 未实现   502 - 网关错误   503 - 服务不可用   504 - 网关超时。&lt;/p&gt;
&lt;h2 id=&#34;42-http协议和其他协议之间的关系&#34;&gt;4.2 HTTP协议和其他协议之间的关系
&lt;/h2&gt;&lt;p&gt;HTTP(超文本传输协议)是利用&lt;strong&gt;TCP在两台电脑(通常是Web服务器和客户端)之间传输信息&lt;/strong&gt;的协议。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果把TCP比作是高速路，HTTP就是卡车&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Socket是对TCP/IP协议的封装&lt;/strong&gt;，Socket本身并不是协议，而是一个&lt;strong&gt;调用接口（API Application Programming Interface,应用程序编程接口）。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;通过Socket，我们能方便地使用TCP/IP协议。&lt;/p&gt;
&lt;h2 id=&#34;43-http长连接和短连接&#34;&gt;4.3 HTTP长连接和短连接
&lt;/h2&gt;&lt;p&gt;短连接：客户端和服务器&lt;strong&gt;每进行一次HTTP操作&lt;/strong&gt;，&lt;strong&gt;就建立一次连接，任务结束就中断连接&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;长连接：客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭，客户端再次访问这个服务器时，会继续使用这一条已经建立的连接。&lt;strong&gt;有一个保持时间，&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;通过Keep-Alive头字段, 服务器配置Nginx, 应用程序代码设置&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;44-http和https安全性端口证书url明密&#34;&gt;4.4 HTTP和HTTPS（安全性，端口，证书，URL，明密)
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;HTTP 是超文本传输协议，信息是明文传输&lt;/strong&gt;，存在安全风险的问题。HTTPS 则解决 HTTP 不安全的缺陷，在 TCP 和 HTTP 网络层&lt;strong&gt;之间加入了 SSL/TLS 安全协议，使得报文能够加密传输&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;HTTP 连接建立相对简单无状态的， TCP 三次握手之后便可进行 HTTP 的报文传输。&lt;/p&gt;
&lt;p&gt;而 HTTPS 在 TCP 三次握手之后，&lt;strong&gt;还需进行 SSL/TLS 的握手过程&lt;/strong&gt;，才可进入加密报文传输。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;HTTP 的&lt;strong&gt;端口号是 80，HTTPS 的端口号是 443。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;HTTPS 协议需要向 CA（证书权威机构）申请数字证书&lt;/strong&gt;，来保证服务器的身份是可信的，一般免费证书较少，因而需要一定费用。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;HTTP：&lt;strong&gt;运行在TCP之上，明文传输&lt;/strong&gt;，客户端与服务器端都&lt;strong&gt;无法验证对方的身份。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;HTTPS：&lt;strong&gt;Https是身披SSL&lt;/strong&gt;(Secure Socket Layer)外壳的Http，&lt;strong&gt;运行于SSL上&lt;/strong&gt;，&lt;strong&gt;SSL运行于TCP之上，是添加了加密和认证机制的HTTP。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;「&lt;strong&gt;HTTPS 是先进行 TCP 三次握手，再进行 TLS v1.2四次握手&lt;/strong&gt;」&lt;/p&gt;
&lt;p&gt;ps: 这句话一点问题都没有，怀疑这句话是错的人，才有问题。&lt;/p&gt;
&lt;p&gt;「&lt;strong&gt;HTTPS 中的 TLS 握手过程可以同时进行三次握手&lt;/strong&gt;」&lt;/p&gt;
&lt;p&gt;这个场景是可能存在到，但是在没有说任何前提条件，&lt;strong&gt;而说这句话就等于耍流氓。需要下面这两个条件同时满足才可以&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;客户端和服务端都开启了 TCP Fast Open 功能，且 TLS 版本是 1.3；&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;客户端和服务端已经完成过一次通信；&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;441-https-是如何建立连接的其间交互了什么&#34;&gt;4.4.1 HTTPS 是如何建立连接的？其间交互了什么？
&lt;/h3&gt;&lt;p&gt;HTTPS 在HTTP的基础上加入了SSL/TLS协议，SSL/TLS依靠证书来验证服务器的身份，并为浏览器和服务器之间的通信加密。&lt;/p&gt;
&lt;p&gt;SSL/TLS 协议基本流程：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;客户端向服务器索要并验证服务器的公钥。&lt;/li&gt;
&lt;li&gt;双方协商生产「会话秘钥」。&lt;/li&gt;
&lt;li&gt;双方采用「会话秘钥」进行加密通信。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://img-blog.csdn.net/20180920154005922?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM1NjQyMDM2/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;HTTPS的缺点，虽然说HTTPS有很大的优势，但其相对来说，还是存在不足之处的：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;（耗电，效率，证钱，IP，范围)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;（1）HTTPS协议&lt;strong&gt;握手阶段比较费时&lt;/strong&gt;，会使页面的加载&lt;strong&gt;时间延长近50%，增加10%到20%的耗电&lt;/strong&gt;；&lt;/p&gt;
&lt;p&gt;（2）HTTPS连接&lt;strong&gt;缓存不如HTTP高效&lt;/strong&gt;，会增加&lt;strong&gt;数据开销和功耗&lt;/strong&gt;，甚至已有的安全措施也会因此而受到影响；&lt;/p&gt;
&lt;p&gt;（3）&lt;strong&gt;SSL证书需要钱&lt;/strong&gt;，&lt;strong&gt;功能越强大的证书费用越高&lt;/strong&gt;，个人网站、小网站没有必要一般不会用。&lt;/p&gt;
&lt;p&gt;（4）&lt;strong&gt;SSL证书通常需要绑定IP&lt;/strong&gt;，&lt;strong&gt;不能在同一IP上绑定多个域名&lt;/strong&gt;，IPv4资源不可能支撑这个消耗。&lt;/p&gt;
&lt;p&gt;（5）HTTPS协议的&lt;strong&gt;加密范围也比较有限&lt;/strong&gt;，在黑客攻击、&lt;strong&gt;拒绝服务攻击、服务器劫持&lt;/strong&gt;等方面几乎起不到什么作用。&lt;/p&gt;
&lt;p&gt;最关键的，&lt;strong&gt;SSL证书的信用链体系并不安全&lt;/strong&gt;，特别是在&lt;strong&gt;某些国家可以控制CA根证书的情况&lt;/strong&gt;下，中间人攻击一样可行。&lt;/p&gt;
&lt;h2 id=&#34;45-get和post的区别&#34;&gt;4.5 GET和POST的区别
&lt;/h2&gt;&lt;p&gt;GET和POST本质上就是TCP链接，并无差别。但是由于&lt;strong&gt;HTTP的规定和浏览器/服务器的限制，导致他们在应用过程中体现出一些不同&lt;/strong&gt;。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;GET在浏览器&lt;strong&gt;回退或者刷新时时无害的&lt;/strong&gt;，而POST会再次提交请求，因为GET请求&lt;strong&gt;是安全幂等&lt;/strong&gt;的，而POST不是&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;GET&lt;strong&gt;参数通过URL传递&lt;/strong&gt;，&lt;strong&gt;POST放在Request body中&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;GET请求在URL中&lt;strong&gt;传送的参数是有长度限制的；&lt;strong&gt;而POST没有，因为大多数浏览器通常都会&lt;/strong&gt;限制url长度在2K个字节&lt;/strong&gt;，而大多数服务器&lt;strong&gt;最多处理64K大小的url&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;GET请求只能进行&lt;strong&gt;url编码&lt;/strong&gt;，而POST&lt;strong&gt;支持多种编码方式，form， json， xml&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对&lt;strong&gt;参数的数据类型，GET只接受ASCII字符&lt;/strong&gt;，&lt;strong&gt;而POST没有限制&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;GET比POST更不安全，&lt;strong&gt;因为参数直接暴露在URL上，所以不能用来传递敏感信息&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;GET请&lt;strong&gt;求参数会被完整保留在浏览器历史记录里，可被收藏为书签&lt;/strong&gt;，&lt;strong&gt;而POST中的参数不会被保留&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;GET&lt;strong&gt;请求会被浏览器主动cache&lt;/strong&gt;，而POST不会，除非手动设置&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;GET产生一个TCP数据包；POST产生两个TCP数据包。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对于GET方式的请求，浏览器会把http header和data一并发送出去，服务器响应200（返回数据）；&lt;/p&gt;
&lt;p&gt;而对于POST，&lt;strong&gt;浏览器先发送header，服务器响应100 continue，浏览器再发送data，服务器响应200 ok&lt;/strong&gt;（返回数据）。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;get方式的安全性较Post方式要差些，包含机密信息的话，建议用Post数据提交方式；&lt;/li&gt;
&lt;li&gt;在&lt;strong&gt;做数据查询时，建议用Get方式&lt;/strong&gt;；  而&lt;strong&gt;在做数据添加、修改或删除时，建议用Post方式&lt;/strong&gt;；&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;安全的是指没有明显的对用户有影响的副作用(包括修改该资源的状态)仅指该方法的多次调用不会产生副作用，不涉及传统意义上的“安全”，这里的副作用是指资源状态。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;即，安全的方法不会修改资源状态&lt;/strong&gt;，尽管多次调用的返回值可能不一样(被其他非安全方法修改过)。HTTP方法里的GET和HEAD都是安全的。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;幂等&lt;/strong&gt;指的是&lt;strong&gt;一个方法不论多少次操作，结果都是一样&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;**PUT(把内容放到指定URL)，**DELETE(&lt;strong&gt;删除某个URL代表的资源)&lt;/strong&gt;，虽然都修改了资源内容，但多次操作，结果是相同的，&lt;strong&gt;因此和HEAD，GET一样都是幂等的。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;46-cookie和session的区别&#34;&gt;4.6 Cookie和Session的区别
&lt;/h2&gt;&lt;p&gt;Cookie和Session都是客户端与服务器之间保持状态的解决方案&lt;/p&gt;
&lt;p&gt;具体来说，&lt;strong&gt;cookie机制采用的是在客户端保持状态的方案，而session机制采用的是在服务器端保持状态的方案。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cookie实际上是&lt;strong&gt;一小段文本信息&lt;/strong&gt;。客户端请求服务器，如果服务器需要记录该用户状态，就向客户端浏览器&lt;strong&gt;颁发一个Cookie&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;客户端浏览器会把Cookie保存起来&lt;/strong&gt;。当浏览器再请求该网站时，浏览器把请求的网址连同该Cookie一同提交给服务器，服务器检查该Cookie，以此来辨认用户状态。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cookie对象使用key-value属性对的形式保存用户状态&lt;/strong&gt;，一个Cookie对象保存一个属性对。&lt;/p&gt;
&lt;p&gt;一个request或者response同时使用多个Cookie。&lt;/p&gt;
&lt;p&gt;因为Cookie类位于包javax.servlet.http.*下面，所以JSP中不需要import该类。每个属性对应一个getter方法与一个setter方法。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cookie并不提供修改、删除操作。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如果要修改某个Cookie，只需要新建一个同名的Cookie，添加到response中覆盖原来的Cookie。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://s3-us-west-2.amazonaws.com/secure.notion-static.com/66f870bb-756b-458e-b92b-9ef1473d77c6/Untitled.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
		alt=&#34;Untitled&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;Session的区别在于，&lt;strong&gt;会话状态完全保存在服务器&lt;/strong&gt;。客户端请求服务器，如果服务器记录该用户状态，就获取Session来保存状态，这时，如果服务器已经为此客户端创建过session就按照sessionid把这个session检索出来使用。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;服务器Session常常依赖于Cookie机制检索ID。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;但Cookie被禁用时也有其他方法比如URL重写机制&lt;/strong&gt;，使用上比Cookie简单一些，相应的也&lt;strong&gt;增加了服务器的存储压力&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;Session对应的类为javax.servlet.http.HttpSession类。每个来访者对应一个Session对象，&lt;strong&gt;所有该客户的状态信息都保存在这个Session对象里&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Session对象是在客户端第一次请求服务器的时候创建的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;Session也是一种key-value的属性对，通过getAttribute(Stringkey)和setAttribute(String key，Objectvalue)方法读写客户状态信息。Servlet里通过request.getSession()方法获取该客户的Session，&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为了获得更高的存取速度，服务器一般把Session放在内存里。每个用户都会有一个独立的Session。如果Session内容过于复杂，当大量客户访问服务器时可能会导致内存溢出。因此，Session里的信息应该尽量精简。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;由于会有越来越多的用户访问服务器，因此Session也会越来越多。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为防止内存溢出，服务器会把长时间内没有活跃的Session从内存删除。这个时间就是Session的超时时间。如果超过了超时时间没访问过服务器，Session就自动失效了。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;它的正常运行仍然需要客户端浏览器的支持。这是因为Session需要使用Cookie作为识别标志。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;HTTP协议是无状态的&lt;/strong&gt;，Session不能依据HTTP连接来判断是否为同一客户，因此服务器向客户端浏览器发送一个名为&lt;strong&gt;JSESSIONID的Cookie&lt;/strong&gt;，&lt;strong&gt;它的值为该Session的id（也就是HttpSession.getId()的返回值）。Session依据该Cookie来识别是否为同一用户。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;该Cookie为服务器自动生成的&lt;/strong&gt;，它的&lt;strong&gt;maxAge属性一般为–1，表示仅当前浏览器内有效&lt;/strong&gt;，并且&lt;strong&gt;各浏览器窗口间不共享&lt;/strong&gt;，关闭浏览器就会失效。&lt;/p&gt;
&lt;p&gt;因此同一机器的两个浏览器窗口访问服务器时，&lt;strong&gt;会生成两个不同的Session&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;但是由浏览器窗口内的链接、脚本等打开的新窗口（也就是说不是双击桌面浏览器图标等打开的窗口）除外。&lt;strong&gt;这类子窗口会共享父窗口的Cookie，因此会共享一个Session&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;URL地址重写是对客户端不支持Cookie的解决方案&lt;/strong&gt;。&lt;strong&gt;URL地址重写的原理是将该用户Session的id信息重写到URL地址中&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;服务器能够解析重写后的URL获取Session的id。这样即使客户端不支持Cookie，也可以使用Session来记录用户状态。&lt;/p&gt;
&lt;h2 id=&#34;47-http请求报文和响应报文的格式&#34;&gt;4.7 HTTP请求报文和响应报文的格式
&lt;/h2&gt;&lt;p&gt;请求报文格式：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;请求行（&lt;strong&gt;请求方法+URI协议+版本&lt;/strong&gt;）&lt;/li&gt;
&lt;li&gt;请求头部&lt;/li&gt;
&lt;li&gt;空行&lt;/li&gt;
&lt;li&gt;请求主体&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;GET/sample.jsp HTTP/1.1 请求行
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Accept:image/gif.image/jpeg, 请求头部
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Accept-Language:zh-cn
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Connection:Keep-Alive
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Host:localhost
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;User-Agent:Mozila/4.0(compatible;MSIE5.01;Window NT5.0)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Accept-Encoding:gzip,deflate
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;username=jinqiao&amp;amp;password=1234 请求主体
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;响应报文：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;状态行（版本+状态码+原因短语）&lt;/li&gt;
&lt;li&gt;响应首部&lt;/li&gt;
&lt;li&gt;空行&lt;/li&gt;
&lt;li&gt;响应主体&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;HTTP/1.1 200 OK
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Server:Apache Tomcat/5.0.12
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Date:Mon,6Oct2003 13:23:42 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Content-Length:112
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&amp;lt;html&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &amp;lt;head&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &amp;lt;title&amp;gt;HTTP响应示例&amp;lt;title&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &amp;lt;/head&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &amp;lt;body&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        Hello HTTP!
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &amp;lt;/body&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&amp;lt;/html&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id=&#34;48-http11http2http3-演变&#34;&gt;4.8 HTTP/1.1、HTTP/2、HTTP/3 演变
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;HTTP/1.1 相比 HTTP/1.0 性能上的改进：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;(长连接，管道网络)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;使用 TCP 长连接的方式改善了 HTTP/1.0 短连接造成的性能开销&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;支持管道（pipeline）网络传输&lt;/strong&gt;，只要第一个请求发出去了，&lt;strong&gt;不必等其回来，就可以发第二个请求出去&lt;/strong&gt;，可以&lt;strong&gt;减少整体的响应时间&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但 HTTP/1.1 还是有性能瓶颈：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;(头部未压缩，相同首部浪费，依然有响应的队头阻塞，无请求优先级，服务器只能被动响应)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;请求 / 响应头部（Header）未经压缩就发送&lt;/strong&gt;，首部信息越多延迟越大。只能压缩 &lt;code&gt;Body&lt;/code&gt; 的部分；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;发送冗长的首部。每次互相发送相同的首部造成的浪费较多&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;服务器是按请求的顺序响应的&lt;/strong&gt;，&lt;strong&gt;如果服务器响应慢，会导致客户端一直请求不到数据&lt;/strong&gt;，&lt;strong&gt;也就是响应的队头阻塞&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;没有请求优先级控制&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;请求只能从客户端开始，服务器只能被动响应。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;HTTP/1.1 管道解决了请求的队头阻塞，但是没有解决响应的队头阻塞&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;HTTP/2 相比 HTTP/1.1 性能上的改进：&lt;/p&gt;
&lt;p&gt;HTTP/2 协议是基于 HTTPS 的，所以 HTTP/2 的安全性也是有保障的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;(双向数据流（同一连接并行请求响应)，资源处理优先级，服务器推送，压缩头部二进制）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可以使用&lt;strong&gt;同一个连接并行发送多个请求和相应&lt;/strong&gt;，&lt;strong&gt;可以承接双向数据流&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;允许设定数据流中不同资源的&lt;strong&gt;优先级&lt;/strong&gt;，&lt;strong&gt;明确资源处理的先后顺序&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;打破了&lt;strong&gt;请求-响应的束缚&lt;/strong&gt;，除了最初的请求响应外，&lt;strong&gt;服务器还能向客户端推送额外的资源&lt;/strong&gt;（客户端没有明确要求的情况下）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;压缩头部，头信息和数据体都是二进制格式&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;1. 头部压缩&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;HTTP/2 会&lt;strong&gt;压缩头&lt;/strong&gt;（Header）如果你同时发出多个请求，他们的头是一样的或是相似的，那么，协议会帮你&lt;strong&gt;消除重复的部分&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这就是所谓的 &lt;strong&gt;&lt;code&gt;HPACK&lt;/code&gt; 算法&lt;/strong&gt;：在客户端和服务器同时&lt;strong&gt;维护一张头信息表&lt;/strong&gt;，所有&lt;strong&gt;字段都会存入这个表&lt;/strong&gt;，&lt;strong&gt;生成一个索引号&lt;/strong&gt;，以后就不发送同样字段了，只发送索引号，这样就&lt;strong&gt;提高速度&lt;/strong&gt;了。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;2. 二进制格式&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;HTTP/2 不再像 HTTP/1.1 里的纯文本形式的报文，而是全面采用了&lt;strong&gt;二进制格式&lt;/strong&gt;，头信息和数据体都是二进制，并且统称为帧（frame）：&lt;strong&gt;头信息帧（Headers Frame）和数据帧（Data Frame）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.jsdelivr.net/gh/xiaolincoder/ImageHost4@main/%E7%BD%91%E7%BB%9C/http2/%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%B8%A7.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;这样虽然对人不友好，但是对计算机非常友好，因为计算机只懂二进制，&lt;strong&gt;那么收到报文后，无需再将明文的报文转成二进制&lt;/strong&gt;，&lt;strong&gt;而是直接解析二进制报文&lt;/strong&gt;，这&lt;strong&gt;增加了数据传输的效率&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;HTTP/1.1 中的管道（ pipeline）虽然解决了请求的队头阻塞，但是&lt;strong&gt;没有解决响应的队头阻塞&lt;/strong&gt;，因为服务端需要按顺序响应收到的请求，如果服务端处理某个请求消耗的时间比较长，那么只能等相应完这个请求后， 才能处理下一个请求，&lt;strong&gt;这属于 HTTP 层队头阻塞&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;HTTP/2 虽然通过&lt;strong&gt;多个请求复用一个 TCP 连接解决了 HTTP 的队头阻塞&lt;/strong&gt; ，&lt;strong&gt;但是一旦发生丢包，就会阻塞住所有的 HTTP 请求&lt;/strong&gt;，&lt;strong&gt;这属于 TCP 层队头阻塞。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.jsdelivr.net/gh/xiaolincoder/ImageHost/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/HTTP/25-HTTP2.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;HTT/1 ~ HTTP/2&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;HTTP/2 &lt;strong&gt;队头阻塞的问题是因为 TCP&lt;/strong&gt;，所以 &lt;strong&gt;HTTP/3 把 HTTP 下层的 TCP 协议改成了 UDP！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.jsdelivr.net/gh/xiaolincoder/ImageHost/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/HTTP/27-HTTP3.png&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;HTTP/1 ~ HTTP/3&lt;/p&gt;
&lt;p&gt;UDP 发生是&lt;strong&gt;不管顺序，也不管丢包&lt;/strong&gt;的，所以&lt;strong&gt;不会出现像 HTTP/2 队头阻塞的问题。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;大家都知道 UDP 是不可靠传输的，但基于 UDP 的 &lt;strong&gt;QUIC 协议&lt;/strong&gt; 可以实现类似 &lt;strong&gt;TCP 的可靠性传输。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;49-如何减少-http-请求次数&#34;&gt;4.9 如何减少 HTTP 请求次数？
&lt;/h2&gt;&lt;p&gt;减少 HTTP 请求次数自然也就提升了 HTTP 性能，可以从这 3 个方面入手：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;（&lt;em&gt;减少重定向请求次数，合并请求，延迟发送请求）&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;em&gt;减少重定向请求次数&lt;/em&gt;；服务器上的一个资源可能由于迁移、维护等原因从 url1 移至 url2 后，而客户端不知情，它还是继续请求 url1，这时服务器不能粗暴地返回错误，而是通过 &lt;code&gt;302&lt;/code&gt; 响应码和 &lt;code&gt;Location&lt;/code&gt; 头部，告诉客户端该资源已经迁移至 url2 了&lt;/strong&gt;，于是客户端需要再发送 url2 请求以获得服务器的资源。&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&lt;strong&gt;合并请求&lt;/strong&gt;&lt;/em&gt;；如果把&lt;strong&gt;多个访问小文件的请求合并成一个大的请求&lt;/strong&gt;，虽然传输的总资源还是一样，但是减少请求，也就意味着&lt;strong&gt;减少了重复发送的 HTTP 头部&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;em&gt;延迟发送请求&lt;/em&gt;；请求网页的时候，没必要把全部资源都获取到，而是只获取当前用户所看到的页面资源，当用户向下滑动页面的时候，再向服务器获取接下来的资源&lt;/strong&gt;，这样就达到了延迟发送请求的效果。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;410-如何减少-http-响应的数据大小&#34;&gt;4.10 如何减少 HTTP 响应的数据大小？
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;（有损无损压缩，质量因子）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们可以考虑对响应的资源进行&lt;strong&gt;压缩&lt;/strong&gt;，这样就可以减少响应的数据大小，从而提高网络传输的效率。&lt;/p&gt;
&lt;p&gt;压缩的方式一般分为 2 种，分别是：&lt;strong&gt;&lt;em&gt;无损压缩&lt;/em&gt;；&lt;em&gt;有损压缩&lt;/em&gt;；&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;无损压缩&#34;&gt;无损压缩
&lt;/h3&gt;&lt;p&gt;无损压缩是指资源经过压缩后，信息不被破坏，还能完全恢复到压缩前的原样，&lt;strong&gt;适合用在文本文件、程序可执行文件、程序源代码&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;gzip 就是比较常见的无损压缩&lt;/strong&gt;。（客户端支持的压缩算法，会在 HTTP 请求中通过头部中的 &lt;code&gt;Accept-Encoding&lt;/code&gt; 字段告诉服务器）&lt;/p&gt;
&lt;h3 id=&#34;有损压缩&#34;&gt;有损压缩
&lt;/h3&gt;&lt;p&gt;与无损压缩相对的就是有损压缩，经过此方法压缩，&lt;strong&gt;解压的数据会与原始数据不同但是非常接近&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;有损压缩主要将次要的数据舍弃，牺牲一些质量来减少数据量、提高压缩比，这种方法经常用于压缩多媒体数据，比如&lt;strong&gt;音频、视频、图片。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;可以通过 HTTP 请求头部中的 &lt;code&gt;Accept&lt;/code&gt; 字段里的「 &lt;strong&gt;q 质量因子&lt;/strong&gt;」，告诉服务器期望的资源质量。&lt;/p&gt;
&lt;p&gt;关于图片的压缩，&lt;strong&gt;目前压缩比较高的是 Google 推出的 WebP 格式&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;411-http如何优化&#34;&gt;4.11 Http如何优化
&lt;/h2&gt;&lt;p&gt;对于硬件优化的方向，因为 HTTPS 是属于&lt;strong&gt;计算密集型，应该选择计算力更强的 CPU&lt;/strong&gt;，而且最好选择&lt;strong&gt;支持 AES-NI 特性的 CPU&lt;/strong&gt;，这个特性可以在硬件级别&lt;strong&gt;优化 AES 对称加密算法&lt;/strong&gt;，加快应用数据的加解密。&lt;/p&gt;
&lt;p&gt;对于&lt;strong&gt;软件优化&lt;/strong&gt;的方向，如果可以，把软件升级成较新的版本，比如将 &lt;strong&gt;Linux 内核 2.X 升级成 4.X&lt;/strong&gt;，将 &lt;strong&gt;openssl 1.0.1 升级到 1.1.1&lt;/strong&gt;，因为新版本的软件不仅会提供新的特性，而且还会修复老版本的问题。&lt;/p&gt;
&lt;p&gt;对于&lt;strong&gt;协议优化&lt;/strong&gt;的方向：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;密钥交换算法应该选择 &lt;strong&gt;ECDHE 算法&lt;/strong&gt;，而不用 RSA 算法，因为 ECDHE 算法具备前向安全性，而且客户端可以在第三次握手之后，就发送加密应用数据，节省了 1 RTT。&lt;/li&gt;
&lt;li&gt;将 TSL1.2 升级 &lt;strong&gt;TSL1.3&lt;/strong&gt;，因为 &lt;strong&gt;TSL1.3 的握手过程只需要 1 RTT&lt;/strong&gt;，而且安全性更强。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对于&lt;strong&gt;证书优化&lt;/strong&gt;的方向：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;服务器应该选用 &lt;strong&gt;ECDSA 证书&lt;/strong&gt;，而非 RSA 证书，因为在相同安全级别下，&lt;strong&gt;ECC 的密钥长度比 RSA 短很多&lt;/strong&gt;，这样可以提高证书传输的效率；&lt;/li&gt;
&lt;li&gt;服务器应该开启 &lt;strong&gt;OCSP Stapling&lt;/strong&gt; 功能，由服务器预先获得 OCSP 的响应，并把响应结果缓存起来，这样 TLS 握手的时候就不用再访问 CA 服务器，减少了网络通信的开销，提高了证书验证的效率；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对于&lt;strong&gt;重连 HTTPS 时，我们可以使用一些技术让客户端和服务端使用上一次 HTTPS 连接使用的会话密钥&lt;/strong&gt;，直接恢复会话，而不用再重新走完整的 TLS 握手过程。&lt;/p&gt;
&lt;p&gt;常见的&lt;strong&gt;会话重用&lt;/strong&gt;技术有 &lt;strong&gt;Session ID 和 Session Ticket，用了会话重用技术，当再次重连 HTTPS 时，只需要 1 RTT 就可以恢复会话&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;对于 TLS1.3 使用 Pre-shared Key 会话重用技术，只需要 0 RTT 就可以恢复会话。这些&lt;strong&gt;会话重用技术虽然好用，但是存在一定的安全风险，它们不仅不具备前向安全&lt;/strong&gt;，而且有&lt;strong&gt;重放攻击的风险&lt;/strong&gt;，所以应当对会话密钥设定一个合理的过期时间。&lt;/p&gt;
&lt;h2 id=&#34;412-hsts协议&#34;&gt;4.12 HSTS协议
&lt;/h2&gt;&lt;p&gt;HSTS（HTTP Strict Transport Security）是一种安全协议，旨在增强网站的安全性，特别是针对HTTPS连接。H&lt;strong&gt;STS通过强制客户端（如浏览器）只能通过加密连接（HTTPS）与服务器通信来防止中间人攻击和SSL剥离攻击。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;HSTS的工作原理是在服务器的响应头中包含一个特殊的&lt;strong&gt;HTTP头部字段（Strict-Transport-Security），该字段告知浏览器在未来一段时间内（例如一年）只能通过HTTPS连接访问该网站&lt;/strong&gt;。一旦浏览器接收到这个头部字段，它将会记住并在接下来的请求中自动使用HTTPS连接。&lt;/p&gt;
&lt;p&gt;使用HSTS可以有效减少网站受到中间人攻击和SSL剥离攻击的风险，提高网站的安全性。&lt;/p&gt;
&lt;h1 id=&#34;5-ip地址&#34;&gt;5. IP地址
&lt;/h1&gt;&lt;h2 id=&#34;51-ip地址的格式是什么&#34;&gt;5.1 IP地址的格式是什么？
&lt;/h2&gt;&lt;p&gt;什么是IP地址？IP协议提供的一种统一的地址格式，&lt;strong&gt;它为互联网上的每一个网络和每一台主机分配一个逻辑地址，以此来屏蔽物理地址的差异。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;IP地址编址方案将IP地址空间划分为A、B、C、D、E五类，其中A、B、C是基本类，D、E类作为多播和保留使用，为特殊地址。&lt;/p&gt;
&lt;p&gt;每个IP地址包括两个标识码（ID）：&lt;strong&gt;网络ID和主机ID。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;同一个物理网络上的所有主机都使用同一个网络ID，网络上的一个主机（包括网络上工作站，服务器和路由器等）有一个主机ID与其对应。3字节的网络地址 + 1字节主机地址的意思就是：前三段号码为网络号码，剩下的一段号码为本地计算机的号码。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A类地址&lt;/strong&gt;：1字节的网络地址 + 3字节主机地址，&lt;strong&gt;网络地址的最高位必须是0&lt;/strong&gt;。A类IP地址的地址范围&lt;strong&gt;1.0.0.0到127.255.255.255&lt;/strong&gt;，IP地址的子网掩码为255.0.0.0，&lt;strong&gt;每个网络支持的最大主机数为&lt;/strong&gt;&lt;code&gt;256^3-2&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;B类地址&lt;/strong&gt;：2字节的网络地址 + 2字节主机地址，&lt;strong&gt;网络地址的最高位必须是10&lt;/strong&gt;。B类IP地址地址范围**128.0.0.0-191.255.255.255，**B类IP地址的子网掩码为255.255.0.0，&lt;strong&gt;每个网络支持的最大主机数&lt;/strong&gt;为&lt;code&gt;256^2-2&lt;/code&gt;。注：1000 0000=128&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;C类地址&lt;/strong&gt;：3字节的网络地址 + 1字节主机地址，&lt;strong&gt;网络地址的最高位必须是110&lt;/strong&gt;。C类IP地址范围**192.0.0.0-223.255.255.255。**每个网络支持的最大主机数为&lt;code&gt;256-2&lt;/code&gt;。&lt;strong&gt;适用于小规模局域网络&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;D类地址：&lt;strong&gt;多播地址，用于1对多通信，&lt;strong&gt;最高位必须是1110&lt;/strong&gt;。范围从&lt;/strong&gt;224.0.0.0到239.255.255.255&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;E类地址&lt;/strong&gt;:：为保留地址，&lt;strong&gt;最高位必须是“11110”&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;IPv4 首部与 IPv6 首部&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;IPv4 首部与 IPv6 首部的差异如下图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://cdn.jsdelivr.net/gh/xiaolincoder/ImageHost/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/IP/31.jpg&#34;
	
	
	
	loading=&#34;lazy&#34;
	
	
&gt;&lt;/p&gt;
&lt;p&gt;IPv6 相比 IPv4 的首部改进：&lt;/p&gt;
&lt;p&gt;(&lt;strong&gt;取消了首部校验和字段, 取消了中间路由分片/重新组装相关字段，取消选项字段）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;取消了首部校验和字段。&lt;/strong&gt; 因为&lt;strong&gt;在数据链路层和传输层都会校验&lt;/strong&gt;，因此 &lt;strong&gt;IPv6 直接取消了 IP 的校验。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;取消了分片/重新组装相关字段。&lt;/strong&gt; 分片与重组是耗时的过程，&lt;strong&gt;IPv6 不允许在中间路由器进行分片与重组&lt;/strong&gt;，&lt;strong&gt;这种操作只能在源与目标主机&lt;/strong&gt;，&lt;strong&gt;这将大大提高了路由器转发的速度。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;取消选项字段。&lt;/strong&gt; &lt;strong&gt;选项字段不再是标准 IP 首部的一部分了，但它并没有消失，而是可能出现在 IPv6 首部中的「下一个首部」指出的位置上&lt;/strong&gt;。删除该选项字段使的 IPv6 的首部成为固定长度的 &lt;code&gt;40&lt;/code&gt; 字节。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;52-单播广播多播的区别是什么&#34;&gt;5.2 单播广播多播的区别是什么？
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;单播：主机间一对一通信&lt;/strong&gt;。   优点：&lt;strong&gt;个性化服务，及时响应&lt;/strong&gt;；缺点：&lt;strong&gt;流量压力大&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;广播：主机间一对所有通信&lt;/strong&gt;。优点：布局简单，维护方便，&lt;strong&gt;流量负载低&lt;/strong&gt;。缺点：&lt;strong&gt;缺乏个性化服务&lt;/strong&gt;，无法在Internet宽带上传播。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;多播（组播）：主机间一对一组通信&lt;/strong&gt;。优点：&lt;strong&gt;兼具流量负载和个性化的优点&lt;/strong&gt;，允许在Internet宽带上传播。缺点：&lt;strong&gt;与单播协议相比没有纠错机制&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&#34;53-如何划分子网&#34;&gt;5.3 如何划分子网？
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;划分子网的方法是从主机号借用若干个位作为子网号&lt;/strong&gt;，&lt;strong&gt;而主机号也就相应减少了若干个位。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;于是两级IP地址在本单位内部就变为三级IP地址：&lt;strong&gt;网络号、子网号和主机号。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;区分子网号和主机号的办法是：通过&lt;strong&gt;子网掩码&lt;/strong&gt;将&lt;strong&gt;网络号和子网号全设为1&lt;/strong&gt;的IP地址为子网掩码。&lt;/p&gt;
&lt;p&gt;假设公司有4个部门，A部门有10台主机，B部门有15台主机，C部门有30台主机，D部门有20台主机。分配了一个总的网段为：192.168.2.0/24。请问该如何划分子网？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;网段前面的数字是我们的网络地址，后面的24表示用24位来表示网络位，用32-24=8位来表示主机位&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;主机数目不多，可以小型组网，因此&lt;strong&gt;采用C类地址(最大254个主机)，默认掩码为225.255.255.0。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;首先假设借用主机位&lt;strong&gt;2位来划分4个子网&lt;/strong&gt;，则子网掩码组合为：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;11111111.11111111.11111111.00 000000
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;11111111.11111111.11111111.01 000000
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;11111111.11111111.11111111.10 000000
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;11111111.11111111.11111111.11 000000
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;然而全为0和全为1的地址不能用&lt;/strong&gt;，所以我们需要借用主机位3位，划分&lt;code&gt;8-2=6&lt;/code&gt;个子网：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;11111111.11111111.11111111.00100000
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;11111111.11111111.11111111.01000000
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;11111111.11111111.11111111.01100000
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;11111111.11111111.11111111.10000000
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;11111111.11111111.11111111.10100000
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;11111111.11111111.11111111.11000000
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;验证一下：最后提供的主机位数是&lt;code&gt;2^5=32&lt;/code&gt;，也就是说每个子网&lt;strong&gt;最大的主机数是32-2=30&lt;/strong&gt;，符合题目要求。所以子网划分如下：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;（1）255.255.255.32:    192.168.2.33~ 192.168.2.62
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;（2）255.255.255.64:   192.168.2.65~ 192.168.2.94
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;（3）255.255.255.96:   192.168.2.97~ 192.168.2.126
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;（4）255.255.255.128:  192.168.2.129~ 192.168.2.158
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;（5）255.255.255.160:  192.168.2.161~ 192.168.2.190
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;（6）255.255.255.192:  192.168.2.193~ 192.168.2.222
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;子网掩码是：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;11111111.11111111.11111111.111 00000
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;255.255.255.224
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h1 id=&#34;6-网络安全&#34;&gt;6. 网络安全
&lt;/h1&gt;&lt;h2 id=&#34;61-什么是ddos攻击&#34;&gt;6.1 什么是DDos攻击？
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;DDos全称Distributed Denial of Service&lt;/strong&gt;，&lt;strong&gt;分布式拒绝服务攻击&lt;/strong&gt;。最基本的DOS攻击过程如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;客户端向服务端发送请求链接数据包&lt;/li&gt;
&lt;li&gt;服务端向客户端发送确认数据包&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;客户端不向服务端发送确认数据包，服务器一直等待来自客户端的确认&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;DDoS则是采用分布式的方法，通过在网络上占领多台“肉鸡”，用多台计算机发起攻击。&lt;/p&gt;
&lt;p&gt;DOS攻击现在基本没啥作用了，因为&lt;strong&gt;服务器的性能都很好&lt;/strong&gt;，而且是多台服务器共同作用，1V1的模式黑客无法占上风。对于DDOS攻击，预防方法有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;减少SYN timeout时间。&lt;strong&gt;在&lt;/strong&gt;握手的第三步&lt;/strong&gt;，&lt;strong&gt;服务器会等待30秒-120秒的时间，减少这个等待时间就能释放更多的资源&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;限制同时打开的SYN半连接数目。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;62-什么是xss攻击&#34;&gt;6.2 什么是XSS攻击？
&lt;/h2&gt;&lt;p&gt;XSS也称 cross-site scripting，&lt;strong&gt;跨站脚本&lt;/strong&gt;。攻击者在web页面中会&lt;strong&gt;插入一些恶意的script代码&lt;/strong&gt;。当用户浏览该页面的时候，&lt;strong&gt;那么嵌入到web页面中script代码会执行&lt;/strong&gt;，因此会达到恶意攻击用户的目的。&lt;/p&gt;
&lt;p&gt;那么XSS攻击最主要有如下分类：&lt;strong&gt;反射型、存储型、及 DOM-based型。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;反射性和DOM-baseed型可以归类为非持久性XSS攻击，存储型可以归类为持久性XSS攻击。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;比如一个存在XSS漏洞的&lt;strong&gt;论坛，用户发帖时&lt;/strong&gt;就可以引入&lt;strong&gt;带有＜script＞标签的代码&lt;/strong&gt;，导致恶意代码的执行。&lt;/p&gt;
&lt;p&gt;预防措施有：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;前端：&lt;strong&gt;过滤&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;后端：&lt;strong&gt;转义，比如go自带的处理器就具有转义功能。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;反射性xss一般指攻击者通过特定的方式来&lt;strong&gt;诱惑受害者去访问一个包含恶意代码的URL&lt;/strong&gt;。当受害者点击恶意链接url的时候，&lt;strong&gt;恶意代码会直接在受害者的主机上的浏览器执行。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;存储型XSS的原理是：&lt;strong&gt;主要是&lt;/strong&gt;将恶意代码上传或存储到服务器中，下次只要受害者浏览包含此恶意代码的页面就会执行恶意代码。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&#34;63-什么是注入sql攻击&#34;&gt;6.3 什么是注入SQL攻击？
&lt;/h2&gt;&lt;p&gt;XSS是将脚本代码注入，而SQL注入攻击顾名思义就是注入SQL语句。&lt;/p&gt;
&lt;p&gt;SQL注入是通过客户端的输入把SQL命令注入到一个应用的数据库中，从而执行恶意的SQL语句。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果通过参数进行拼接，拼接后的sql语句&lt;/strong&gt;就是： &lt;strong&gt;select * from user where username = ’’ and password = ’ ’ or ‘123’ = ‘123’;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这样的了，那么会有一个or语句，只要这两个有一个是正确的话，就条件成立，因此 123 = 123 是成立的。因此验证就会被跳过。&lt;/p&gt;
&lt;p&gt;这只是一个简单的列子，比如还有密码比如是这样的：’; drop table user;, 这样的话，那么sql命令就变成了：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;select * from user where username = ’’ and password = ’‘; drop table user;’ , 那么这个时候我们会把user表直接删除了&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;比如代码：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;username&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Form&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;username&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;password&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;Form&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;password&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;sql&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;SELECT * FROM user WHERE username=&amp;#39;&amp;#34;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;username&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;&amp;#39; AND password=&amp;#39;&amp;#34;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;password&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;&amp;#39;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;当用户输入&lt;code&gt;myuser&#39; or &#39;foo&#39; = &#39;foo&#39; --&lt;/code&gt;，那么SQL就变成了：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;SELECT&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;FROM&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;user&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;WHERE&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;username&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;myuser&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;&amp;#39;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;or&lt;/span&gt; &lt;span class=&#34;err&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;foo&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;==&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;foo&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;&amp;#39;&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;--&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;AND&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;password&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;&amp;#39;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;xxx&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;在SQL里面&lt;code&gt;--&lt;/code&gt;是注释标记，所以查询语句会在此中断。&lt;/p&gt;
&lt;p&gt;这就让攻击者在不知道任何合法用户名和密码的情况下成功登录了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;预防方法：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;限制数据库权限&lt;/strong&gt;，给用户提供仅仅能够满足其工作的最低权限。&lt;/li&gt;
&lt;li&gt;对进入数据库的&lt;strong&gt;特殊字符（’”&amp;amp;*;等）转义处理&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提供参数化查询接口&lt;/strong&gt;，&lt;strong&gt;不要直接使用原生SQL&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;永远&lt;strong&gt;不要信任用户的输入&lt;/strong&gt;。对用户的输入进行校验，可以通过正&lt;strong&gt;则表达式&lt;/strong&gt;，或限制长度；对&lt;strong&gt;单引号和 双“-”进行转换&lt;/strong&gt;等。&lt;/li&gt;
&lt;li&gt;永远&lt;strong&gt;不要使用动态拼装sql&lt;/strong&gt;，可以使用&lt;strong&gt;参数化的sql&lt;/strong&gt;或者&lt;strong&gt;直接使用存储过程进行数据查询存取&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;永远&lt;strong&gt;不要使用管理员权限的数据库连接&lt;/strong&gt;，为&lt;strong&gt;每个应用使用单独的权限有限的数据库连接。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;不要把&lt;strong&gt;机密信息直接存放&lt;/strong&gt;，加密或者&lt;strong&gt;hash掉密码和敏感&lt;/strong&gt;的信息。&lt;/li&gt;
&lt;li&gt;应用的异&lt;strong&gt;常信息应该给出尽可能少的提示&lt;/strong&gt;，最好使&lt;strong&gt;用自定义的错误信息对原始错误信息进行包装&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;sql注入的检测方法一般采取辅助软件或网站平台来检测&lt;/strong&gt;，软件一般采用sql注入检测工具&lt;strong&gt;jsky&lt;/strong&gt;，网站平台就有&lt;strong&gt;亿思网站&lt;/strong&gt;安全平台检测工具。MDCSOFT SCAN等。采用MDCSOFT-IPS可以有效的防御SQL注入，XSS攻击等。&lt;/li&gt;
&lt;/ol&gt;
</description>
        </item>
        
    </channel>
</rss>
