Skip to content

format and add detail #243

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions network/3_tcp/challenge_ack.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ tcp_send_challenge_ack 函数里就会调用 tcp_send_ack 函数来回复一个
在 Linux 上有个叫 killcx 的工具,就是基于上面这样的方式实现的,它会主动发送 SYN 包获取 SEQ/ACK 号,然后利用 SEQ/ACK 号伪造两个 RST 报文分别发给客户端和服务端,这样双方的 TCP 连接都会被释放,这种方式活跃和非活跃的 TCP 连接都可以杀掉。


killcx 的工具使用方式也很简单,如果在服务端执行 killcx 工具,只需指明客户端的 IP 和端口号,如果在客户端执行 killcx 工具,则就指明服务端的 IP 和端口号。
killcx 的工具使用方式也很简单,如果在服务端执行 killcx 工具,只需指明客户端的 IP 和端口号,如果在客户端执行 killcx 工具,则就指明服务端的 IP 和端口号。

```csharp
./killcx <IP地址>:<端口号>
Expand Down Expand Up @@ -222,7 +222,7 @@ killcx 工具则是属于主动获取,它是主动发送一个 SYN 报文,
这两种工具都是通过伪造 RST 报文来关闭 TCP 连接的,但是它们获取「对方下一次期望收到的序列号的方式是不同的,也正因此,造就了这两个工具的应用场景有区别。

- tcpkill 工具只能用来关闭活跃的 TCP 连接,无法关闭非活跃的 TCP 连接,因为 tcpkill 工具是等双方进行 TCP 通信后,才去获取正确的序列号,如果这条 TCP 连接一直没有任何数据传输,则就永远获取不到正确的序列号。
- killcx 工具可以用来关闭活跃和非活跃的 TCP 连接,因为 killcx 工具是主动发送 SYN 报文,这时对方就会回复 Challenge ACK,然后 killcx 工具就能从这个 ACK 获取到正确的序列号。
- killcx 工具可以用来关闭活跃和非活跃的 TCP 连接,因为 killcx 工具是主动发送 SYN 报文,这时对方就会回复 Challenge ACK,然后 killcx 工具就能从这个 ACK 获取到正确的序列号。

完!

Expand Down
6 changes: 3 additions & 3 deletions os/8_network_system/reactor.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

但是引入了线程池,那么一个线程要处理多个连接的业务,线程在处理某个连接的 `read` 操作时,如果遇到没有数据可读,就会发生阻塞,那么线程就没办法继续处理其他连接的业务。

要解决这一个问题,最简单的方式就是将 socket 改成非阻塞,然后线程不断地轮询调用 `read` 操作来判断是否有数据,这种方式虽然该能够解决阻塞的问题,但是解决的方式比较粗暴,因为轮询是要消耗 CPU 的,而且随着一个 线程处理的连接越多,轮询的效率就会越低。
要解决这一个问题,最简单的方式就是将 socket 改成非阻塞,然后线程不断地轮询调用 `read` 操作来判断是否有数据,这种方式虽然该能够解决阻塞的问题,但是解决的方式比较粗暴,因为轮询是要消耗 CPU 的,而且随着一个线程处理的连接越多,轮询的效率就会越低。

上面的问题在于,线程并不知道当前连接是否有数据可读,从而需要每次通过 `read` 去试探。

Expand Down Expand Up @@ -163,7 +163,7 @@ Redis 是由 C 语言实现的,它采用的正是「单 Reactor 单进程」
- Handler 对象不再负责业务处理,只负责数据的接收和发送,Handler 对象通过 read 读取到数据后,会将数据发给子线程里的 Processor 对象进行业务处理;
- 子线程里的 Processor 对象就进行业务处理,处理完后,将结果发给主线程中的 Handler 对象,接着由 Handler 通过 send 方法将响应结果发送给 client;

单 Reator 多线程的方案优势在于**能够充分利用多核 CPU 的能**,那既然引入多线程,那么自然就带来了多线程竞争资源的问题。
单 Reator 多线程的方案优势在于**能够充分利用多核 CPU 的性能**,那既然引入多线程,那么自然就带来了多线程竞争资源的问题。

例如,子线程完成业务处理后,要把结果传递给主线程的 Reactor 进行发送,这里涉及共享数据的竞争。

Expand All @@ -187,7 +187,7 @@ Redis 是由 C 语言实现的,它采用的正是「单 Reactor 单进程」

方案详细说明如下:

- 主线程中的 MainReactor 对象通过 select 监控连接建立事件,收到事件后通过 Acceptor 对象中的 accept 获取连接,将新的连接分配给某个子线程;
- 主线程中的 MainReactor 对象通过 select 监控连接建立事件,收到事件后通过 Acceptor 对象中的 accept 获取连接,将新的连接分配给某个子线程;
- 子线程中的 SubReactor 对象将 MainReactor 对象分配的连接加入 select 继续进行监听,并创建一个 Handler 用于处理连接的响应事件。
- 如果有新的事件发生时,SubReactor 对象会调用当前连接对应的 Handler 对象来进行响应。
- Handler 对象通过 read -> 业务处理 -> send 的流程来完成完整的业务流程。
Expand Down
2 changes: 1 addition & 1 deletion os/8_network_system/zero_copy.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ write(socket, tmp_buf, len);

![](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)

首先,期间共**发生了 4 次用户态与内核态的上下文切换**,因为发生了两次系统调用,一次是 `read()` ,一次是 `write()`,每次系统调用都得先从用户态切换到内核态,等内核完成任务后,再从内核态切换回用户态。
首先,期间共**发生了 4 次用户态与内核态的上下文切换**,因为发生了两次系统调用,一次是 `read()` ,一次是 `write()`,每次系统调用都得先从用户态切换到内核态,等内核完成任务后,再从内核态切换回用户态。

上下文切换的成本并不小,一次切换需要耗时几十纳秒到几微秒,虽然时间看上去很短,但是在高并发的场景下,这类时间容易被累积和放大,从而影响系统的性能。

Expand Down