Skip to content

Commit 3b941c0

Browse files
committed
modify net work
1 parent de35d10 commit 3b941c0

12 files changed

+678
-897
lines changed

docs/java/network-programming/Java网络与NIO总结.md

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
# Table of Contents
2-
1+
# 目录
32
* [Java IO](#java-io)
43
* [Socket编程](#socket编程)
54
* [客户端,服务端的线程模型](#客户端,服务端的线程模型)
@@ -14,16 +13,6 @@
1413
* [个人公众号:黄小斜](#个人公众号:黄小斜)
1514

1615

17-
---
18-
title: Java网络编程与NIO学习总结
19-
date: 2018-07-08 22:08:22
20-
tags:
21-
- Java网络编程
22-
- NIO
23-
categories:
24-
- 后端
25-
- 技术总结
26-
---
2716
这篇总结主要是基于我之前Java网络编程与NIO系列文章而形成的的。主要是把重要的知识点用自己的话说了一遍,可能会有一些错误,还望见谅和指点。谢谢
2817

2918
更多详细内容可以查看我的专栏文章:Java网络编程与NIO
@@ -161,6 +150,7 @@ container是一个多级容器,最外层到最内层依次是engine,host,c
161150

162151
下面是个server.xml文件实例,Tomcat根据该文件进行部署
163152

153+
````
164154
<Server> //顶层类元素,可以包括多个Service
165155
<Service> //顶层类元素,可包含一个Engine,多个Connecter
166156
<Connector> //连接器类元素,代表通信接口
@@ -173,7 +163,7 @@ container是一个多级容器,最外层到最内层依次是engine,host,c
173163
</Connector>
174164
</Service>
175165
</Server>
176-
166+
````
177167

178168
根据配置文件初始化容器信息,当请求到达时进行容器间的请求传递,事实上整个链条被称作pipeline,pipeline连接了各个容器的入口,由于每个容器和组件都实现了lifecycle接口。
179169

docs/java/network-programming/Java网络编程与NIO详解10:深度解读Tomcat中的NIO模型.md

Lines changed: 21 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
# Table of Contents
2-
1+
# 目录
32
* [一、I/O复用模型解读](#一、io复用模型解读)
43
* [二、TOMCAT对IO模型的支持](#二、tomcat对io模型的支持)
54
* [三、TOMCAT中NIO的配置与使用](#三、tomcat中nio的配置与使用)
@@ -9,7 +8,6 @@
98
* [七、关于性能](#七、关于性能)
109
* [八、总结](#八、总结)
1110

12-
1311
本文转自:http://www.sohu.com/a/203838233_827544
1412

1513
本系列文章将整理到我在GitHub上的《Java面试指南》仓库,更多精彩内容请到我的仓库里查看
@@ -37,8 +35,7 @@
3735

3836
Tomcat的NIO是基于I/O复用来实现的。对这点一定要清楚,不然我们的讨论就不在一个逻辑线上。下面这张图学习过I/O模型知识的一般都见过,出自《UNIX网络编程》,I/O模型一共有阻塞式I/O,非阻塞式I/O,I/O复用(select/poll/epoll),信号驱动式I/O和异步I/O。这篇文章讲的是I/O复用。
3937

40-
![](http://5b0988e595225.cdn.sohucs.com/images/20171112/0ab0b70be5a64b0e9014de867235da73.png)
41-
38+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405104442.png)
4239
IO复用.png
4340

4441
这里先来说下用户态和内核态,直白来讲,如果线程执行的是用户代码,当前线程处在用户态,如果线程执行的是内核里面的代码,当前线程处在内核态。更深层来讲,操作系统为代码所处的特权级别分了4个级别。
@@ -51,13 +48,12 @@ IO复用.png
5148

5249
上面提到的网络事件有连接就绪,接收就绪,读就绪,写就绪四个网络事件。I/O复用主要是通过Selector复用器来实现的,可以结合下面这个图理解上面的叙述。
5350

54-
![](http://5b0988e595225.cdn.sohucs.com/images/20171112/3a18d371c9c848479ca39d8eb4e57ce4.jpeg)
55-
51+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405104504.png)
5652
Selector图解.png
5753

5854
## 二、TOMCAT对IO模型的支持
5955

60-
![](http://5b0988e595225.cdn.sohucs.com/images/20171112/983c122c2a924e01b9f898279e2bd0b5.jpeg)
56+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405104517.png)
6157

6258
tomcat支持IO类型图.png
6359

@@ -71,7 +67,7 @@ tomcat从6以后开始支持NIO模型,实现是基于JDK的java.nio包。这
7167

7268
## 四、NioEndpoint组件关系图解读
7369

74-
![](http://5b0988e595225.cdn.sohucs.com/images/20171112/4e1a924a60974c76ab5115007562e563.jpeg)
70+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405104543.png)
7571

7672
tomcatnio组成.png
7773

@@ -87,8 +83,7 @@ LimitLatch是连接控制器,它负责维护连接数的计算,nio模式下
8783

8884
## 五、NioEndpoint执行序列图
8985

90-
![](http://5b0988e595225.cdn.sohucs.com/images/20171112/d69c2ef5110d4706aa7284c616d62927.jpeg)
91-
86+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405104621.png)
9287
tomcatnio序列图.png
9388

9489
在下一小节NioEndpoint源码解读中我们将对步骤1-步骤11依次找到对应的代码来说明。
@@ -99,8 +94,7 @@ tomcatnio序列图.png
9994

10095
无论是BIO还是NIO,开始都会初始化连接限制,不可能无限增大,NIO模式下默认是10000。
10196

102-
![](http://5b0988e595225.cdn.sohucs.com/images/20171112/332338f6d2c8488e9b35cd4fd76f078a.png)
103-
97+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405104637.png)
10498
**6.2、步骤解读**
10599

106100
下面我们着重叙述跟NIO相关的流程,共分为11个步骤,分别对应上面序列图中的步骤。
@@ -111,46 +105,41 @@ tomcatnio序列图.png
111105

112106
Socket,NIO下这里返回的是SocketChannel。
113107

114-
![](http://5b0988e595225.cdn.sohucs.com/images/20171112/ca01395590944a35b4717b15c984849e.png)
115-
108+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405104855.png)
116109
**步骤2**:启动接收线程
117110

118-
![](http://5b0988e595225.cdn.sohucs.com/images/20171112/e7c40dc6f84b48f4915ca854c2a3b2cc.png)
119111

120112
**步骤3**:ServerSocketChannel.accept()接收新连接
121113

122-
![](http://5b0988e595225.cdn.sohucs.com/images/20171112/50ddbfe042514ba29a8bcd606b125f76.jpeg)
114+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405104937.png)
123115

124116
**步骤4**:将接收到的链接通道设置为非阻塞
125117

126118
**步骤5**:构造NioChannel对象
127119

128120
**步骤6**:register注册到轮询线程
129121

130-
![](http://5b0988e595225.cdn.sohucs.com/images/20171112/f3f5130bcf7a4348bc9752bd009c1b72.png)
122+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405104957.png)
131123

132124
**步骤7**:构造PollerEvent,并添加到事件队列
133125

134-
![](http://5b0988e595225.cdn.sohucs.com/images/20171112/65de3c5cebea42c59ff19e8a168cf406.png)
126+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105014.png)
135127

136128
**步骤8**:启动轮询线程
137129

138-
![](http://5b0988e595225.cdn.sohucs.com/images/20171112/5c097feaa2324a2da082a81287f9e862.png)
130+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105027.png)
139131

140132
**步骤9**:取出队列中新增的PollerEvent并注册到Selector
141133

142-
![](http://5b0988e595225.cdn.sohucs.com/images/20171112/8ac5e80b9aa84f57b4c3f7ecd4e2ea1d.png)
134+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105043.png)
143135

144136
**步骤10**:Selector.select()
145137

146-
![](http://5b0988e595225.cdn.sohucs.com/images/20171112/82d20de2a4614eab9fe14e58234db552.jpeg)
147-
148-
![](http://5b0988e595225.cdn.sohucs.com/images/20171112/75641a4b8d444f8ab7a033bee9497c2b.png)
138+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105057.png)
149139

140+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105110.png)
150141
**步骤11**:根据选择的SelectionKey构造SocketProcessor提交到请求处理线程
151-
152-
![](http://5b0988e595225.cdn.sohucs.com/images/20171112/b8ec9bbcbdcd4088841741acb20152f8.png)
153-
142+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105154.png)
154143
**6.3、NioBlockingSelector和BlockPoller介绍**
155144

156145
上面的序列图有个地方我没有描述,就是NioSelectorPool这个内部类,是因为在整体理解tomcat的nio上面在序列图里面不包括它更好理解。
@@ -159,13 +148,13 @@ Socket,NIO下这里返回的是SocketChannel。
159148

160149
以执行servlet后,得到response,往socket中写数据为例,最终写的过程调用NioBlockingSelector的write方法。代码如下:
161150

162-
![](http://5b0988e595225.cdn.sohucs.com/images/20171112/4ac6ac8fdb0a4b24bdcb646a09cbb13e.jpeg)
151+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105223.png)
163152

164-
![](http://5b0988e595225.cdn.sohucs.com/images/20171112/549283c8059b4e3c8798077b654dc3d1.png)
153+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105237.png)
165154

166155
也就是说当socket.write()返回0时,说明网络状态不稳定,这时将socket注册OP_WRITE事件到辅Selector,由BlockPoller线程不断轮询这个辅Selector,直到发现这个socket的写状态恢复了,通过那个倒数计数器,通知Worker线程继续写socket动作。看一下BlockSelector线程的代码逻辑:
167156

168-
![](http://5b0988e595225.cdn.sohucs.com/images/20171112/626817ca25fc4a439dbf5d23c2c08d0e.jpeg)
157+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105251.png)
169158

170159
使用这个辅Selector主要是减少线程间的切换,同时还可减轻主Selector的负担。
171160

@@ -175,13 +164,9 @@ Socket,NIO下这里返回的是SocketChannel。
175164

176165
NIO的优势更在于用少量的线程hold住大量的连接。还有一点,我们在压测的过程中,遇到在NIO模式下刚开始的一小段时间内容,会有错误,这是因为一般的压测工具是基于一种长连接,也就是说比如模拟1000并发,那么同时建立1000个连接,下一时刻再发送请求就是基于先前的这1000个连接来发送,还有TOMCAT的NIO处理是有POLLER线程来接管的,它的线程数一般等于CPU的核数,如果一瞬间有大量并发过来,POLLER也会顿时处理不过来。
177166

178-
![](http://5b0988e595225.cdn.sohucs.com/images/20171112/7d62f8792e1c41f090d945c755bba6c7.jpeg)
179-
180-
压测1.jpeg
181-
182-
![](http://5b0988e595225.cdn.sohucs.com/images/20171112/e8ec450685d64e5785db44a0bb60660c.jpeg)
167+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105304.png)
183168

184-
压测2.jpeg
169+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105318.png)
185170

186171
## 八、总结
187172

docs/java/network-programming/Java网络编程与NIO详解11:Tomcat中的Connector源码分析(NIO).md

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
# Table of Contents
2-
1+
# 目录
32
* [前言](#前言)
43
* [源码环境准备](#源码环境准备)
54
* [endpoint](#endpoint)
@@ -43,7 +42,7 @@
4342

4443
先简单画一张图示意一下本文的主要内容:
4544

46-
![0](https://www.javadoop.com/blogimages/tomcat-nio/0.png)
45+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105401.png)
4746

4847
**目录**
4948

@@ -87,7 +86,7 @@ public static void main(String[] args) throws LifecycleException {
8786
8887
这里,介绍第一个重要的概念:**Connector**。在 Tomcat 中,使用 Connector 来处理连接,一个 Tomcat 可以配置多个 Connector,分别用于监听不同端口,或处理不同协议。
8988

90-
在 Connector 的构造方法中,我们可以传 `HTTP/1.1` 或 `AJP/1.3` 用于指定协议,也可以传入相应的协议处理类,毕竟协议不是重点,将不同端口进来的连接对应不同处理类才是正道。典型地,我们可以指定以下几个协议处理类:
89+
在 Connector 的构造方法中,我们可以传`HTTP/1.1``AJP/1.3`用于指定协议,也可以传入相应的协议处理类,毕竟协议不是重点,将不同端口进来的连接对应不同处理类才是正道。典型地,我们可以指定以下几个协议处理类:
9190

9291
* org.apache.coyote.http11.Http11NioProtocol:对应非阻塞 IO
9392
* org.apache.coyote.http11.Http11Nio2Protocol:对应异步 IO
@@ -119,9 +118,9 @@ public static void main(String[] args) throws LifecycleException {
119118
120119
## endpoint
121120
122-
前面我们说过一个 Connector 对应一个协议,当然这描述也不太对,NIO 和 NIO2 就都是处理 HTTP/1.1 的,只不过一个使用非阻塞,一个使用异步。进到指定 protocol 代码,我们就会发现,它们的代码及其简单,只不过是指定了特定的 **endpoint**。
121+
前面我们说过一个 Connector 对应一个协议,当然这描述也不太对,NIO 和 NIO2 就都是处理 HTTP/1.1 的,只不过一个使用非阻塞,一个使用异步。进到指定 protocol 代码,我们就会发现,它们的代码及其简单,只不过是指定了特定的**endpoint**。
123122
124-
打开 `Http11NioProtocol` 和 `Http11Nio2Protocol`源码,我们可以看到,在构造方法中,它们分别指定了 NioEndpoint 和 Nio2Endpoint。
123+
打开`Http11NioProtocol``Http11Nio2Protocol`源码,我们可以看到,在构造方法中,它们分别指定了 NioEndpoint 和 Nio2Endpoint。
125124
126125
```
127126
// 非阻塞模式
@@ -143,15 +142,15 @@ public class Http11Nio2Protocol extends AbstractHttp11JsseProtocol<Nio2Channel>
143142
}
144143
```
145144
146-
这里介绍第二个重要的概念:**endpoint**。Tomcat 使用不同的 endpoint 来处理不同的协议请求,今天我们的重点是 **NioEndpoint**,其使用**非阻塞 IO** 来进行处理 HTTP/1.1 协议的请求。
145+
这里介绍第二个重要的概念:**endpoint**。Tomcat 使用不同的 endpoint 来处理不同的协议请求,今天我们的重点是**NioEndpoint**,其使用**非阻塞 IO**来进行处理 HTTP/1.1 协议的请求。
147146
148-
**NioEndpoint** 继承 => **AbstractJsseEndpoint** 继承 => **AbstractEndpoint**。中间的 AbstractJsseEndpoint 主要是提供了一些关于 `HTTPS` 的方法,这块我们暂时忽略它,后面所有关于 HTTPS 的我们都直接忽略,感兴趣的读者请自行分析。
147+
**NioEndpoint**继承 =>**AbstractJsseEndpoint**继承 =>**AbstractEndpoint**。中间的 AbstractJsseEndpoint 主要是提供了一些关于`HTTPS`的方法,这块我们暂时忽略它,后面所有关于 HTTPS 的我们都直接忽略,感兴趣的读者请自行分析。
149148
150149
## init 过程分析
151150
152151
下面,我们看看从 tomcat.start() 一直到 NioEndpoint 的过程。
153152
154-
**1\. AbstractProtocol** # **init**
153+
**1\. AbstractProtocol**#**init**
155154
156155
```
157156
@Override
@@ -165,7 +164,7 @@ public void init() throws Exception {
165164
}
166165
```
167166
168-
**2\. AbstractEndpoint** # **init**
167+
**2\. AbstractEndpoint**#**init**
169168
170169
```
171170
public final void init() throws Exception {
@@ -177,7 +176,7 @@ public final void init() throws Exception {
177176
}
178177
```
179178
180-
**3\. NioEndpoint** # **bind**
179+
**3\. NioEndpoint**#**bind**
181180
182181
这里就到我们的 NioEndpoint 了,要使用到我们之前学习的 NIO 的知识了。
183182
@@ -321,9 +320,9 @@ public void startInternal() throws Exception {
321320
}
322321
```
323322
324-
到这里,我们启动了**工作线程池**、 **poller 线程组**、**acceptor 线程组**。同时,工作线程池初始就已经启动了 10 个线程。我们用 **jconsole** 来看看此时的线程,请看下图:
323+
到这里,我们启动了**工作线程池**、**poller 线程组**、**acceptor 线程组**。同时,工作线程池初始就已经启动了 10 个线程。我们用**jconsole**来看看此时的线程,请看下图:
325324
326-
![1](https://www.javadoop.com/blogimages/tomcat-nio/1.png)
325+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105500.png)
327326
328327
从 jconsole 中,我们可以看到,此时启动了 BlockPoller、worker、poller、acceptor、AsyncTimeout,大家应该都已经清楚了每个线程是哪里启动的吧。
329328
@@ -345,7 +344,7 @@ public Acceptor(AbstractEndpoint<?,U> endpoint) {
345344
}
346345
```
347346
348-
**threadName** 就是一个线程名字而已,Acceptor 的状态 **state** 主要是随着 endpoint 来的。
347+
**threadName**就是一个线程名字而已,Acceptor 的状态**state**主要是随着 endpoint 来的。
349348
350349
```
351350
public enum AcceptorState {
@@ -498,7 +497,7 @@ protected boolean setSocketOptions(SocketChannel socket) {
498497
}
499498
```
500499
501-
我们看到,这里又没有进行实际的处理,而是将这个 SocketChannel **注册**到了其中一个 poller 上。因为我们知道,acceptor 应该尽可能的简单,只做 accept 的工作,简单处理下就往后面扔。acceptor 还得回到之前的循环去 accept 新的连接呢。
500+
我们看到,这里又没有进行实际的处理,而是将这个 SocketChannel**注册**到了其中一个 poller 上。因为我们知道,acceptor 应该尽可能的简单,只做 accept 的工作,简单处理下就往后面扔。acceptor 还得回到之前的循环去 accept 新的连接呢。
502501
503502
我们只需要明白,此时,往 poller 中注册了一个 NioChannel 实例,此实例包含客户端过来的 SocketChannel 和一个 SocketBufferHandler 实例。
504503
@@ -820,7 +819,7 @@ protected SocketProcessorBase<NioChannel> createSocketProcessor(
820819
821820
最后,再祭出文章开始的那张图来总结一下:
822821
823-
![0](https://www.javadoop.com/blogimages/tomcat-nio/0.png)
822+
![](https://java-tutorial.oss-cn-shanghai.aliyuncs.com/20230405105530.png)
824823
825824
这里简单梳理下前面我们说的流程,帮大家回忆一下:
826825

0 commit comments

Comments
 (0)