置之死地而后生
抖音后端开发实习一面
-
自我介绍
-
你参加了PAT比赛?介绍一下?
-
平时有刷题吗?有的,那来做一下算法题目吧,单词拆分(动态规划1h过去了...)
-
TCP有哪些状态?每种状态代表着什么含义?为什么要有这些状态?
-
select/poll/epoll了解吗?有什么区别?
-
epoll为什么高效?(红黑树)
-
epoll里面的回调是什么?
-
场景:如果上传文件,你会使用哪个?为什么?
-
反问 (业务是什么?(你打开抖音,里面的功能有哪些就是我们的业务)实习多久(根据你来定))
-
1个半小时
总结:算法能力欠缺?常见八股的值得细究!
单词拆分
注意是ACM风格,动态规划
class Solution {
public:bool wordBreak(string s, vector<string>& wordDict) {int max_len = ranges::max(wordDict, {}, &string::length).length();unordered_set<string> words(wordDict.begin(), wordDict.end());int n = s.length();vector<int> memo(n + 1, -1); // -1 表示没有计算过auto dfs = [&](this auto&& dfs, int i) -> bool {if (i == 0) { // 成功拆分!return true;}int& res = memo[i]; // 注意这里是引用if (res != -1) { // 之前计算过return res;}for (int j = i - 1; j >= max(i - max_len, 0); j--) {if (words.count(s.substr(j, i - j)) && dfs(j)) {return res = true; // 记忆化}}return res = false; // 记忆化};return dfs(n);}
};
TCP的三次握手,四次挥手
TCP(传输控制协议)是互联网协议族中的一种面向连接的、可靠的、基于字节流的传输层通信协议。为了建立和关闭连接,TCP 使用了三次握手(Three-Way Handshake)来建立连接,以及四次挥手(Four-Way Wavehand)来关闭连接。在这个过程中,TCP 连接会经历多种状态,每种状态都有其特定的含义。
三次握手
三次握手是 TCP 建立连接的过程,涉及两个端点之间的三个步骤:
-
第一次握手(SYN):客户端发送一个 SYN(同步序列编号)包到服务器,表示想要建立连接,并包含一个初始序列号
seq=x
。 -
第二次握手(SYN-ACK):服务器收到客户端的 SYN 包后,会回复一个 SYN-ACK 包,包含服务器的初始序列号
seq=y
和对客户端序列号的确认ack=x+1
。 -
第三次握手(ACK):客户端收到服务器的 SYN-ACK 包后,会回复一个 ACK 包,确认服务器的序列号
ack=y+1
,并再次确认自己的序列号seq=x+1
。此时,连接建立完成。
四次挥手
四次挥手是 TCP 关闭连接的过程,涉及两个端点之间的四个步骤:
-
第一次挥手(FIN):一方(例如客户端)发送一个 FIN 包到另一方(服务器),表示数据发送完毕,请求关闭连接。
-
第二次挥手(ACK):接收方(服务器)收到 FIN 包后,回复一个 ACK 包,确认收到 FIN 包。
-
第三次挥手(FIN):接收方(服务器)在完成数据发送后,也发送一个 FIN 包到发送方(客户端),表示请求关闭连接。
-
第四次挥手(ACK):发送方(客户端)收到服务器的 FIN 包后,回复一个 ACK 包,确认收到 FIN 包。此时,连接关闭完成。
TCP 状态
在三次握手和四次挥手的过程中,TCP 连接会经历多种状态,这些状态在不同的操作系统中可能有所差异,但通常包括以下几种:
-
CLOSED:连接没有建立,处于关闭状态。
-
LISTEN:服务器端处于监听状态,等待客户端连接请求。
-
SYN-SENT:客户端已经发送了 SYN 包,等待服务器的 SYN-ACK 包。
-
SYN-RECEIVED:服务器收到客户端的 SYN 包,已发送 SYN-ACK 包,等待客户端的 ACK 包。
-
ESTABLISHED:连接已经建立,可以进行数据传输。
-
FIN-WAIT-1:客户端已经发送了 FIN 包,等待服务器的 ACK 包。
-
FIN-WAIT-2:客户端已经发送了 FIN 包,并收到了服务器的 ACK 包,等待服务器的 FIN 包。
-
TIME-WAIT:客户端已经发送了 ACK 包响应服务器的 FIN 包,处于时间等待状态,等待可能丢失的服务器 FIN 包的重传。
-
CLOSE-WAIT:服务器收到客户端的 FIN 包,已经发送了 ACK 包,等待应用程序关闭连接。
-
LAST-ACK:服务器已经发送了 FIN 包,等待客户端的 ACK 包。
-
CLOSING:双方同时尝试关闭连接,收到对方的 FIN 包并发送自己的 FIN 包。
这些状态确保了 TCP 连接的可靠性和数据传输的完整性。通过这些状态的转换,TCP 协议能够管理连接的建立、数据传输和关闭过程。
select,poll,epoll区别?
select
、poll
和 epoll
都是 Linux 系统中用于实现 I/O 多路复用(I/O multiplexing)的系统调用,它们允许程序同时监视多个文件描述符(file descriptors),并在这些文件描述符上有 I/O 事件发生时通知程序,从而避免在等待 I/O 时阻塞整个程序。尽管它们的目的相同,但它们在实现方式和性能特征上有很大的区别。
1. select
-
基本原理:
select
是最早引入的 I/O 多路复用机制。它使用一个fd_set
结构体来表示一组文件描述符,并使用三个fd_set
来分别监视读、写和异常(错误)事件。select
会在这些文件描述符上等待,直到至少有一个文件描述符就绪(即有 I/O 事件发生)。 - 局限性:
-
fd_set
的大小是固定的(通常为 1024 个文件描述符),因此select
最多只能监视 1024 个文件描述符。 -
每次调用
select
时,需要将fd_set
从用户空间复制到内核空间,并且每次返回时,内核会将fd_set
复制回用户空间。这个复制操作在文件描述符数量较多时会带来较大的开销。 -
select
返回后,程序需要遍历所有的文件描述符来检查哪些文件描述符就绪,这在文件描述符数量较多时效率较低。
-
-
优点:
select
兼容性好,几乎所有操作系统都支持。
2. poll
-
基本原理:
poll
是对select
的改进。它使用一个pollfd
结构体数组来表示一组文件描述符,并指定每个文件描述符感兴趣的事件(读、写、异常)。poll
会在这些文件描述符上等待,直到至少有一个文件描述符就绪。 - 改进点:
-
poll
没有文件描述符数量的限制(虽然实际使用中可能会受到系统资源限制)。 -
不需要像
select
那样每次调用时都将fd_set
复制到内核空间,减少了数据复制的开销。 -
poll
返回后,程序只需要遍历就绪的文件描述符,而不是所有的文件描述符,提高了效率。
-
- 局限性:
-
虽然
poll
减少了数据复制的开销,但在文件描述符数量较多时,遍历所有文件描述符的开销仍然存在。 -
poll
在高并发情况下,性能可能会下降。
-
3. epoll
-
基本原理:
epoll
是 Linux 特有的 I/O 多路复用机制,专门为高性能网络服务器设计。它使用事件驱动的方式来监视文件描述符,通过epoll_create
创建一个epoll
实例,通过epoll_ctl
添加、修改或删除文件描述符,最后通过epoll_wait
等待事件的发生。 - 优点:
-
边缘触发(Edge Triggered, ET)和水平触发(Level Triggered, LT):
epoll
支持两种事件触发模式。水平触发(LT)模式下,epoll
会在文件描述符就绪后持续通知程序,直到事件被处理;边缘触发(ET)模式下,epoll
只会在文件描述符状态发生变化时通知程序一次。ET 模式下可以减少不必要的系统调用,提高性能。 -
内核直接管理文件描述符:
epoll
在内核中维护一个文件描述符的事件列表,避免了每次调用时从用户空间复制数据到内核空间的开销。 -
事件驱动:
epoll_wait
返回后,程序只需要处理就绪的文件描述符,避免了不必要的遍历操作。
-
- 局限性:
-
epoll
是 Linux 特有的,不具有跨平台性。 -
使用
epoll
需要更多的编程复杂性,特别是在处理边缘触发模式时。
-
总结
-
select
:适用于简单的场景,尤其是需要跨平台的情况,但性能较差,文件描述符数量有限。 -
poll
:改进了select
的一些限制,但仍然存在性能问题,尤其是在高并发情况下。 -
epoll
:专为高性能网络服务器设计,性能优异,但仅适用于 Linux 系统。
选择哪种机制取决于具体的应用场景和需求。在大多数情况下,epoll
是首选,因为它在高并发和高性能方面表现出色。然而,如果你需要跨平台编程或者文件描述符数量不多,select
或 poll
可能是更合适的选择。
场景题:上传文件使用哪一个?select/poll/epoll?
在上传文件时,选择使用 select
、poll
还是 epoll
主要取决于应用程序的具体需求和场景。这些 I/O 多路复用机制主要用于处理多个文件描述符的 I/O 事件,例如在网络服务器中监听多个套接字的事件。然而,上传文件通常涉及到的是简单的客户端与服务器之间的交互,而不是高并发的网络服务器。
上传文件的典型流程
上传文件通常涉及以下步骤:
-
客户端向服务器发起 HTTP
POST
请求。 -
客户端将文件数据封装在请求体中,发送到服务器。
-
服务器接收文件数据并存储。
在这个过程中,客户端和服务器之间通常是单个连接,而不是多个并发连接。因此,select
、poll
和 epoll
在这类场景中的使用并不是必须的,因为它们更多地用于处理多个并发连接或 I/O 操作。
选择 I/O 多路复用机制的考虑
然而,如果你需要在服务器端处理多个并发上传请求,并且希望高效地管理这些连接,那么选择合适的 I/O 多路复用机制就变得重要了。
-
select
:适用于简单场景,尤其是在需要跨平台的情况下。然而,select
在处理大量文件描述符时的性能较差。 -
poll
:改进了select
的一些限制,但在高并发情况下,性能仍然有限。 -
epoll
:专为高性能网络服务器设计,适用于高并发、高吞吐量的场景。epoll
在内核中维护文件描述符的状态,避免了数据复制的开销,并且支持边缘触发模式,适合处理大量并发连接。
使用建议
-
单个文件上传:如果只是单个文件上传,通常不需要使用
select
、poll
或epoll
,直接使用 HTTPPOST
请求即可。 -
多个并发文件上传:如果你需要处理多个并发文件上传,并且服务器在高并发环境下运行,那么
epoll
是最佳选择。epoll
能够高效地管理大量并发连接,适用于高吞吐量的文件上传场景。
示例
以下是一个简单的文件上传示例,使用 HTTP POST
请求:
客户端代码(Python):
import requestsurl = 'http://example.com/upload'
files = {'file': open('example.txt', 'rb')}
response = requests.post(url, files=files)
print(response.text)
服务器端代码(Python,使用 Flask):
from flask import Flask, requestapp = Flask(__name__)@app.route('/upload', methods=['POST'])
def upload_file():file = request.files['file']file.save('/path/to/save/file.txt')return 'File uploaded successfully'if __name__ == '__main__':app.run()
在这个示例中,客户端将文件上传到服务器,服务器接收并保存文件,整个过程没有使用 select
、poll
或 epoll
。
总结
在文件上传的典型场景中,直接使用 HTTP POST
请求通常是最简单和最有效的方式。只有在需要处理大量并发文件上传的场景中,才需要考虑使用 epoll
等 I/O 多路复用机制来优化性能。
处女面给了字节,第一次面试直接地狱难度,大厂的强度还是很大的,上来先做一道动态规划,八股虽然就问了两个,但还是很细的。 patience is key...