在学习的时候找到几个十分好的工程和个人博客,先码一下,内容都摘自其中,有些重难点做了补充!
才鲸 / 嵌入式软件笔试题汇总
嵌入式与Linux那些事
阿秀的学习笔记
小林coding
百问网linux
嵌入式软件面试合集
2022年春招实习十四面(嵌入式面经)
TCP(Transmission Control Protocol)和UDP(User Datagram Protocol)是两种网络传输协议,它们在数据传输时有以下区别:
TCP的优点:
TCP的缺点:
UDP的优点:
UDP的缺点:
总之,TCP和UDP各有优缺点,在实际应用中需要根据具体的需求进行选择。如果需要传输大量的数据或保证数据的可靠传输,应该选择TCP;如果需要传输实时性要求较高的数据,或者需要提高传输效率,应该选择UDP。
参考链接:TCP的三次握手与四次挥手
最开始的时候客户端和服务器都是处于CLOSED状态。主动打开连接的为客户端,被动打开连接的是服务器。
TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号
。这个报文也不能携带数据,但是同样要消耗一个序号
。TCP规定,ACK报文段可以携带数据,但是如果不携带数据则不消耗序号
。主要从以下三个方面分析:
原因一:避免历史连接
TCP 使用三次握手建立连接的最主要原因就是防止「历史连接」初始化了连接。
如果是两次握手连接,就无法阻止历史连接。主要是因为在两次握手的情况下,服务端没有中间状态给客户端来阻止历史连接,导致服务端可能建立一个历史连接,造成资源浪费。
因此,要解决这种现象,最好就是在服务端发送数据前,也就是建立连接之前,要阻止掉历史连接,这样就不会造成资源浪费,而要实现这个功能,就需要三次握手。
原因二:同步双方初始序列号
TCP 协议的通信双方, 都必须维护一个「序列号」, 序列号是可靠传输的一个关键因素,它的作用:
四次握手其实也能够可靠的同步双方的初始化序号,但由于第二步和第三步可以优化成一步,所以就成了「三次握手」。
而两次握手只保证了一方的初始序列号能被对方成功接收,没办法保证双方的初始序列号都能被确认接收。
原因三:避免资源浪费
如果只有「两次握手」,当客户端发生的 SYN 报文在网络中阻塞,客户端没有接收到 ACK 报文,就会重新发送 SYN ,由于没有第三次握手,服务端不清楚客户端是否收到了自己回复的 ACK 报文,所以服务端每收到一个 SYN 就只能先主动建立一个连接。
如果客户端发送的 SYN 报文在网络中阻塞了,重复发送多次 SYN 报文,那么服务端在收到请求后就会建立多个冗余的无效链接,造成不必要的资源浪费。
小结:
TCP 建立连接时,通过三次握手能防止历史连接的建立,能减少双方不必要的资源开销,能帮助双方同步初始化序列号
。序列号能够保证数据包不重复、不丢弃和按序传输
。
不使用「两次握手」和「四次握手」的原因:
主要原因有两个方面:
起始 ISN 是基于时钟的,每 4 微秒 + 1,转一圈要 4.55 个小时。
RFC793 提到初始化序列号 ISN 随机生成算法:ISN = M + F(localhost, localport, remotehost, remoteport)。
可以看到,随机数是会基于时钟计时器递增的,基本不可能会随机成一样的初始化序列号
假设攻击者短时间伪造不同 IP 地址的 SYN 报文,服务端每接收到一个 SYN 报文,就进入SYN_RCVD 状态,但服务端发送出去的 ACK + SYN 报文,无法得到未知 IP 主机的 ACK 应答,久而久之就会占满服务端的半连接队列
,使得服务端不能为正常用户服务。
在 TCP 三次握手的时候,Linux 内核会维护两个队列,分别是:
避免 SYN 攻击方式,可以有以下四种方法:
net.core.netdev_max_backlog = 10000
TCP规定,FIN报文段即使不携带数据,也要消耗一个序号
。客户端向服务器的方向就释放了
,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受
。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)
。2*MSL(最长报文段寿命)
的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。主动关闭连接的,才有 TIME_WAIT 状态
。MSL (Maximum Segment Lifetime) 报文最大生存时间
,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。因为 TCP 报文基于是 IP 协议的,而 IP 头中有一个 TTL 字段,是 IP 数据报可以经过的最大路由数,每经过一个处理他的路由器此值就减 1,当此值为 0 则数据报将被丢弃,同时发送 ICMP 报文通知源主机。
MSL 与 TTL 的区别: MSL 的单位是时间,而 TTL 是经过路由跳数。所以 MSL 应该要大于等于 TTL 消耗为 0 的时间,以确保报文已被自然消亡。
TTL 的值一般是 64,Linux 将 MSL 设置为 30 秒,意味着 Linux 认为数据报文经过 64 个路由器的时间不会超过 30 秒,如果超过了,就认为报文已经消失在网络中了。
TIME_WAIT 等待 2 倍的 MSL,比较合理的解释是: 网络中可能存在来自发送方的数据包,当这些发送方的数据包被接收方处理后又会向对方发送响应,所以一来一回需要等待 2 倍的时间
。
主动发起关闭连接的一方,才会有 TIME-WAIT 状态。需要 TIME-WAIT 状态,主要是两个原因:
过多的 TIME-WAIT 状态主要的危害有两种:
OSI七层网络模型和TCP/IP网络模型都是网络通信方面的标准模型,但它们之间存在一些区别:
层数不同:OSI七层网络模型由下至上分别为物理层、数据链路层、网络层、传输层、会话层、表示层和应用层,共七层。而TCP/IP网络模型由下至上分别为物理层、数据链路层、网络层、传输层和应用层,共五层。
名称不同:OSI七层网络模型中的每一层都有独立的名称和功能,而TCP/IP网络模型中的每一层通常只有一个名称,有时也称为协议族。
组成不同:OSI七层网络模型是一个理论模型,没有具体的协议实现,而TCP/IP网络模型是一个实际使用的模型,具有大量的协议实现,如TCP、IP、UDP等。
技术发展不同:OSI七层网络模型是由国际标准化组织(ISO)在20世纪80年代开发的,而TCP/IP网络模型是在20世纪70年代由美国国防部开发的。
通信方式不同:OSI七层网络模型中的每一层都是独立的,通信方式是通过每一层之间的接口进行数据传输;而TCP/IP网络模型中的每一层不完全独立,通信方式是通过封装和解封装数据包来实现数据传输。
HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web)服务器传输超文本到本地浏览器的传送协议。
HTTP是一个基于TCP/IP通信协议来传递数据(HTML 文件,图片文件,查询结果等)。
HTTP是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。
它于1990年提出,经过几年的使用与发展,得到不断地完善和扩展。目前在WWW中使用的是HTTP/1.0的第六版,HTTP/1.1的规范化工作正在进行之中,而且HTTP-NG(Next Generation of HTTP)的建议已经提出。
HTTP协议工作于客户端-服务端架构为上。浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。Web服务器根据接收到的请求后,向客户端发送响应信息。
HTTP 是一个在计算机世界里专门在「两点」之间「传输」文字、图片、音频、视频等「超文本」数据的「约定和规范」。
根据 RFC 规范,GET 的语义是从服务器获取指定的资源
,这个资源可以是静态的文本、页面、图片视频等。GET 请求的参数位置一般是写在 URL 中,URL 规定只能支持 ASCII,所以 GET 请求的参数只允许 ASCII 字符 ,而且浏览器会对 URL 的长度有限制(HTTP协议本身对 URL长度并没有做任何规定)。
根据 RFC 规范,POST 的语义是根据请求负荷(报文body)对指定的资源做出处理
,具体的处理方式视资源类型而不同。POST 请求携带数据的位置一般是写在报文 body 中,body 中的数据可以是任意格式的数据,只要客户端与服务端协商好即可,而且浏览器不会对 body 大小做限制。
安全和幂等的概念:
如果从 RFC 规范定义的语义来看:
但是实际过程中,开发者不一定会按照 RFC 规范定义的语义来实现 GET 和 POST 方法。比如:
对于一些具有重复性的 HTTP 请求,比如每次请求得到的数据都一样的,我们可以把这对「请求-响应」的数据都缓存在本地,那么下次就直接读取本地的数据,不必在通过网络获取服务器的响应了,这样的话 HTTP/1.1 的性能肯定肉眼可见的提升。
所以,避免发送 HTTP 请求的方法就是通过缓存技术,HTTP 设计者早在之前就考虑到了这点,因此 HTTP 协议的头部有不少是针对缓存的字段。
HTTP 缓存有两种实现方式,分别是强制缓存和协商缓存
。
强缓存指的是只要浏览器判断缓存没有过期,则直接使用浏览器的本地缓存,决定是否使用缓存的主动性在于浏览器这边。
如下图中,返回的是 200 状态码,但在 size 项中标识的是 from disk cache,就是使用了强制缓存。
强缓存是利用下面这两个 HTTP 响应头部(Response Header)字段实现的,它们都用来表示资源在客户端缓存的有效期:
如果 HTTP 响应头部同时有 Cache-Control 和 Expires 字段的话,Cache-Control 的优先级高于 Expires 。
Cache-control 选项更多一些,设置更加精细,所以建议使用 Cache-Control 来实现强缓存。具体的实现流程如下:
协商缓存可以基于两种头部来实现。
第一种:请求头部中的 If-Modified-Since 字段与响应头部中的 Last-Modified 字段实现,这两个字段的意思是:
第二种:请求头部中的 If-None-Match 字段与响应头部中的 ETag 字段,这两个字段的意思是:
HTTP 最突出的优点是「简单、灵活和易于扩展、应用广泛和跨平台」。
简单
HTTP 基本的报文格式就是 header + body,头部信息也是 key-value 简单文本的形式,易于理解,降低了学习和使用的门槛。
灵活和易于扩展
HTTP 协议里的各类请求方法、URI/URL、状态码、头字段等每个组成要求都没有被固定死,都允许开发人员自定义和扩充。
同时 HTTP 由于是工作在应用层( OSI 第七层),则它下层可以随意变化,比如:
HTTPS 就是在 HTTP 与 TCP 层之间增加了 SSL/TLS 安全传输层;
HTTP/1.1 和 HTTP/2.0 传输协议使用的是 TCP 协议,而到了 HTTP/3.0 传输协议改用了 UDP 协议。
应用广泛和跨平台
互联网发展至今,HTTP 的应用范围非常的广泛,从台式机的浏览器到手机上的各种 APP,从看新闻、刷贴吧到购物、理财、吃鸡,HTTP 的应用遍地开花,同时天然具有跨平台的优越性。
HTTP 协议里有优缺点一体的双刃剑,分别是「无状态、明文传输」,同时还有一大缺点「不安全」。
无状态双刃剑
无状态的好处,因为服务器不会去记忆 HTTP 的状态,所以不需要额外的资源来记录状态信息,这能减轻服务器的负担,能够把更多的 CPU 和内存用来对外提供服务。
无状态的坏处,既然服务器没有记忆能力,它在完成有关联性的操作时会非常麻烦。
对于无状态的问题,解法方案有很多种,其中比较简单的方式用 Cookie 技术。
Cookie 通过在请求和响应报文中写入 Cookie 信息来控制客户端的状态。
明文传输双刃剑
明文意味着在传输过程中的信息,是可方便阅读的,比如 Wireshark 抓包都可以直接肉眼查看,为我们调试工作带了极大的便利。
但是这正是这样,HTTP 的所有信息都暴露在了光天化日下,相当于信息裸奔
。在传输的漫长的过程中,信息的内容都毫无隐私可言,很容易就能被窃取,如果里面有你的账号密码信息,那你号没了。
不安全
HTTP 比较严重的缺点就是不安全:
早期 HTTP/1.0 性能上的一个很大的问题,那就是每发起一个请求,都要新建一次 TCP 连接(三次握手),而且是串行请求,做了无谓的 TCP 连接建立和断开,增加了通信开销。
为了解决上述 TCP 连接问题,HTTP/1.1 提出了长连接的通信方式,也叫持久连接。这种方式的好处在于减少了 TCP 连接的重复建立和断开所造成的额外开销,减轻了服务器端的负载。
持久连接的特点是,只要任意一端没有明确提出断开连接,则保持 TCP 连接状态。
HTTP/1.1 采用了长连接的方式,这使得管道(pipeline)网络传输成为了可能。
即可在同一个 TCP 连接里面,客户端可以发起多个请求,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。
举例来说,客户端需要请求两个资源。以前的做法是,在同一个 TCP 连接里面,先发送 A 请求,然后等待服务器做出回应,收到后再发出 B 请求。那么,管道机制则是允许浏览器同时发出 A 请求和 B 请求,
但是服务器必须按照接收请求的顺序发送对这些管道化请求的响应。
如果服务端在处理 A 请求时耗时比较长,那么后续的请求的处理都会被阻塞住,这称为「队头堵塞」。
所以,HTTP/1.1 管道解决了请求的队头阻塞,但是没有解决响应的队头阻塞。
注意:
实际上 HTTP/1.1 管道化技术不是默认开启,而且浏览器基本都没有支持,有这个功能,但是没有被使用.
SSL/TLS 安全协议
,使得报文能够加密传输。默认端口
不一样,HTTP 默认端口号是 80,HTTPS 默认端口号是 443。CA(证书权威机构)申请数字证书
,来保证服务器的身份是可信的。HTTP 由于是明文传输,所以安全上存在以下三个风险:
HTTPS 在 HTTP 与 TCP 层之间加入了 SSL/TLS 协议,可以很好的解决了上述的风险:
HTTPS 采用的是对称加密和非对称加密结合的「混合加密」方式:
采用「混合加密」的方式的原因:
为了保证传输的内容不被篡改,我们需要对内容计算出一个「指纹」,然后同内容一起传输给对方。
对方收到后,先是对内容也计算出一个「指纹」,然后跟发送方发送的「指纹」做一个比较,如果「指纹」相同,说明内容没有被篡改,否则就可以判断出内容被篡改了。
那么,在计算机里会用摘要算法(哈希函数)来计算出内容的哈希值
,也就是内容的「指纹」,这个哈希值是唯一的,且无法通过哈希值推导出内容。
通过哈希算法可以确保内容不会被篡改
,但是并不能保证「内容 + 哈希值」不会被中间人替换
,因为这里缺少对客户端收到的消息是否来源于服务端的证明。
那为了避免这种情况,计算机里会用非对称加密算法来解决,共有两个密钥:
这两个密钥可以双向加解密
的,比如可以用公钥加密内容,然后用私钥解密,也可以用私钥加密内容,公钥解密内容。
流程的不同,意味着目的也不相同:
公钥加密,私钥解密
。这个目的是为了保证内容传输的安全
,因为被公钥加密的内容,其他人是无法解密的,只有持有私钥的人,才能解密出实际的内容;
私钥加密,公钥解密
。这个目的是为了保证消息不会被冒充
,因为私钥是不可泄露的,如果公钥能正常解密出私钥加密的内容,就能证明这个消息是来源于持有私钥身份的人发送的。
一般我们不会用非对称加密来加密实际的传输内容,因为非对称加密的计算比较耗费性能
的。
所以非对称加密的用途主要在于通过「私钥加密,公钥解密」的方式,来确认消息的身份,我们常说的数字签名算法,就是用的是这种方式,不过私钥加密内容不是内容本身,而是对内容的哈希值加密。
SSL/TLS 协议基本流程:
HTTPS建立连接的过程如下:
在HTTPS建立连接的过程中,客户端和服务器之间进行了以下交互:
具体过程如下:
经过压缩的片段会被加上消息认证码(MAC 值,这个是通过哈希算法生成的),这是为了保证完整性
,并进行数据的认证。通过附加消息认证码的 MAC 值,可以识别出篡改。与此同时,为了防止重放攻击,在计算消息认证码时,还加上了片段的编码。记录协议完成后,最终的报文数据将传递到传输控制协议 (TCP) 层进行传输。
HTTPS 协议本身到目前为止还是没有任何漏洞的,即使你成功进行中间人攻击,本质上是利用了客户端的漏洞(用户点击继续访问或者被恶意导入伪造的根证书),并不是 HTTPS 不够安全。
为什么抓包工具能截取 HTTPS 数据?
很多抓包工具 之所以可以明文看到 HTTPS 数据,工作原理与中间人一致的。对于 HTTPS 连接来说,中间人要满足以下两点,才能实现真正的明文代理:
中间人要拿到私钥只能通过如下方式:
抓包工具只能使用第三种方式取得中间人的身份。使用抓包工具进行 HTTPS 抓包的时候,需要在客户端安装 Fiddler 的根证书
,这里实际上起认证中心(CA)的作用。
抓包工具能够抓包的关键是客户端会往系统受信任的根证书列表中导入抓包工具生成的证书,而这个证书会被浏览器信任,也就是抓包工具给自己创建了一个认证中心 CA,客户端拿着中间人签发的证书去中间人自己的 CA 去认证,当然认为这个证书是有效的。
如何避免被中间人抓取数据?
我们要保证自己电脑的安全,不要被病毒乘虚而入,而且也不要点击任何证书非法的网站,这样 HTTPS 数据就不会被中间人截取到了。
当然,我们还可以通过 HTTPS 双向认证
来避免这种问题。
一般我们的 HTTPS 是单向认证,客户端只会验证了服务端的身份,但是服务端并不会验证客户端的身份。
TCP协议的服务器端常用的Socket函数:
TCP协议的客户端常用的Socket函数:
UDP协议的服务器端常用的Socket函数:
UDP协议的客户端常用的Socket函数:
TCP协议的客户端和服务器端常用的Socket函数:
connect()函数
向服务器端发起连接请求,而服务器端需要使用accept()函数
接受客户端的连接请求。先建立连接
,客户端和服务器端都需要使用send()函数和recv()函数
进行数据的发送和接收。listen()函数
开始监听客户端连接请求,并使用bind()函数
将套接字与特定的IP地址和端口号绑定。UDP协议的客户端和服务器端常用的Socket函数:
不需要建立连接
,客户端可以直接使用sendto()函数
向服务器端发送数据,服务器端也可以直接使用recvfrom()函数
接收客户端发送的数据。sendto()函数和recvfrom()函数
进行数据的发送和接收。bind()函数
将套接字与特定的IP地址和端口号绑定,并使用recvfrom()函数
接收客户端发送的数据。socket()函数:
socket()函数用于创建一个新的套接字(socket)
,并指定套接字的类型和协议。该函数返回一个整数值用于标识新创建的套接字。在使用socket()函数时,需要指定套接字的类型(如SOCK_STREAM表示TCP协议,SOCK_DGRAM表示UDP协议),以及套接字所使用的协议(如IPPROTO_TCP表示使用TCP协议)。
send()函数:
send()函数用于向已连接的套接字发送数据
。该函数需要指定发送数据的套接字描述符、要发送的数据指针和数据长度等参数。send()函数返回实际发送的数据长度,如果发送失败则返回-1。
recv()函数:
recv()函数用于从已连接的套接字接收数据
。该函数需要指定接收数据的套接字描述符、接收数据的缓冲区指针和缓冲区长度等参数。recv()函数返回实际接收到的数据长度,如果接收失败则返回-1。
accept()函数:
accept()函数用于接受客户端的连接请求
,并创建一个新的套接字用于与客户端通信。该函数需要指定服务器端的套接字描述符和一个用于存放客户端地址信息的结构体指针等参数。accept()函数返回一个新的套接字描述符,用于与客户端进行通信。
在网络编程中,长链接和短链接是两种不同的连接方式,它们的区别主要在于连接的建立和维护方式
。
长链接
长链接是指客户端和服务器端建立的连接可以在一段时间内一直保持打开状态,以便于多次发送和接收数据。在长链接中,连接的建立和维护需要一定的资源和时间成本,但可以避免频繁地建立和关闭连接,从而提高通信效率。长链接通常用于需要频繁通信的场景,如在线游戏、即时通讯等。
短链接
短链接是指客户端和服务器端每次发送和接收数据都需要建立一个新的连接,完成数据传输后立即关闭连接。在短链接中,连接的建立和维护成本较低,但频繁地建立和关闭连接也会影响通信效率。短链接通常用于数据传输量较小、通信频率较低的场景,如HTTP请求等。