這里主要講一下Tomcat使用NIO啟動和進行請求處理的大致流程,使用的源碼版本是7.0.5,對于其他處理等流程就不寫了,我在別的文章里已經大致寫過了,不過是用的6.0版本:http://zddava.javaeye.com/category/53603。
當Tomcat配置成使用NIO時,啟動過程其實和過去差不多,也是Connector#startInternal -> Protocol(Http11NioProtocol)#start() -> Endpoint(NioEndPoint)#start()的過程,這里主要看一下NioEndPoint:
1
public void start() throws Exception {
2
// 初始化
3
if (!initialized) {
4
init();
5
}
6
if (!running) {
7
running = true;
8
paused = false;
9
10
// 創建一個ThreadPoolExecutor對象,和JDK里的功能一樣,只不過進行了一些擴展
11
if (getExecutor() == null) {
12
createExecutor();
13
}
14
15
// 開啟poll的線程
16
pollers = new Poller[getPollerThreadCount()];
17
for (int i = 0; i < pollers.length; i++) {
18
pollers[i] = new Poller();
19
Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-" + i);
20
pollerThread.setPriority(threadPriority);
21
pollerThread.setDaemon(true);
22
pollerThread.start();
23
}
24
25
// 開啟Acceptor的線程
26
for (int i = 0; i < acceptorThreadCount; i++) {
27
Thread acceptorThread = new Thread(new Acceptor(), getName() + "-Acceptor-" + i);
28
acceptorThread.setPriority(threadPriority);
29
acceptorThread.setDaemon(getDaemon());
30
acceptorThread.start();
31
}
32
}
33
}

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

這里先看一下init()方法,沒有全列出來,最主要的一點就是初始化ServerSocketChannel:
1
public void init() throws Exception {
2
3
if (initialized)
4
return;
5
6
// 初始化ServerSocketChannel,這里用的是阻塞的方式,沒有用Selector
7
serverSock = ServerSocketChannel.open();
8
socketProperties.setProperties(serverSock.socket());
9
InetSocketAddress addr = (getAddress() != null ? new InetSocketAddress(getAddress(), getPort())
10
: new InetSocketAddress(getPort()));
11
serverSock.socket().bind(addr, getBacklog());
12
serverSock.configureBlocking(true); // mimic APR behavior
13
serverSock.socket().setSoTimeout(getSocketProperties().getSoTimeout());
14
15
......
16
17
}

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

Tomcat每種Endpoint的Acceptor線程其實作用都一樣,對來訪的請求進行最初的處理之用,NioEndpoint的Acceptor也不例外,它內部也只定義一個繼承自Runnable的方法
1
public void run() {
2
while (running) {
3
4
while (paused && running) {
5
try {
6
Thread.sleep(1000);
7
} catch (InterruptedException e) {
8
// Ignore
9
}
10
}
11
12
if (!running) {
13
break;
14
}
15
try {
16
// 接受請求
17
SocketChannel socket = serverSock.accept();
18
if ( running && (!paused) && socket != null ) {
19
// 將SocketChannel給pollor處理
20
if (!setSocketOptions(socket)) {
21
try {
22
socket.socket().close();
23
socket.close();
24
} catch (IOException ix) {
25
if (log.isDebugEnabled())
26
log.debug("", ix);
27
}
28
}
29
}
30
} catch (SocketTimeoutException sx) {
31
//normal condition
32
} catch (IOException x) {
33
if (running) {
34
log.error(sm.getString("endpoint.accept.fail"), x);
35
}
36
} catch (OutOfMemoryError oom) {
37
try {
38
oomParachuteData = null;
39
releaseCaches();
40
log.error("", oom);
41
}catch ( Throwable oomt ) {
42
try {
43
try {
44
System.err.println(oomParachuteMsg);
45
oomt.printStackTrace();
46
}catch (Throwable letsHopeWeDontGetHere){
47
ExceptionUtils.handleThrowable(letsHopeWeDontGetHere);
48
}
49
}catch (Throwable letsHopeWeDontGetHere){
50
ExceptionUtils.handleThrowable(letsHopeWeDontGetHere);
51
}
52
}
53
} catch (Throwable t) {
54
ExceptionUtils.handleThrowable(t);
55
log.error(sm.getString("endpoint.accept.fail"), t);
56
}
57
}
58
}
59
}

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

方法其實挺容易理解,就是得到請求用的SocketChannel后交給Poller處理,這里poll是一個UNIX的系統調用名字,Java開發者可以google下,我也是才準備開始啃《UNIX網絡編程》,閑言少敘,看一下#setSocketOptions()方法吧:
1
protected boolean setSocketOptions(SocketChannel socket) {
2
// Process the connection
3
try {
4
// disable blocking, APR style, we are gonna be polling it
5
// 這里終于看到了印象中的NIO的影子了
6
socket.configureBlocking(false);
7
Socket sock = socket.socket();
8
socketProperties.setProperties(sock);
9
10
// NioChannel是ByteChannel的子類
11
// 從隊列里取出第一個可用的Channel,這樣的話NioChannel應該是設計成非GC的
12
// 感覺其目的主要是對SocketChannel進行下封裝
13
NioChannel channel = nioChannels.poll();
14
if (channel == null) {
15
// 不過這里如果沒有可用的就初始化一個的話請求數陡然增高再慢慢回落的時候不就浪費了內存了嗎?
16
// NioBufferHandler里分別分配了讀緩沖區和寫緩沖區
17
// SSL setup
18
if (sslContext != null) {
19
SSLEngine engine = createSSLEngine();
20
int appbufsize = engine.getSession().getApplicationBufferSize();
21
NioBufferHandler bufhandler = new NioBufferHandler(Math.max(appbufsize,
22
socketProperties.getAppReadBufSize()), Math.max(appbufsize,
23
socketProperties.getAppWriteBufSize()), socketProperties.getDirectBuffer());
24
channel = new SecureNioChannel(socket, engine, bufhandler, selectorPool);
25
} else {
26
// normal tcp setup
27
NioBufferHandler bufhandler = new NioBufferHandler(socketProperties.getAppReadBufSize(),
28
socketProperties.getAppWriteBufSize(), socketProperties.getDirectBuffer());
29
30
channel = new NioChannel(socket, bufhandler);
31
}
32
} else {
33
// 這里就是對Channel的重用了
34
channel.setIOChannel(socket);
35
if (channel instanceof SecureNioChannel) {
36
SSLEngine engine = createSSLEngine();
37
((SecureNioChannel) channel).reset(engine);
38
} else {
39
channel.reset();
40
}
41
}
42
// 這里就是將SocketChannel注冊到Poller了。
43
// getPoller0用的循環的方式來返回Poller,即Poller 1, 2, 3 ... n 然后再回到1, 2, 3.
44
getPoller0().register(channel);
45
} catch (Throwable t) {
46
ExceptionUtils.handleThrowable(t);
47
try {
48
log.error("", t);
49
} catch (Throwable tt) {
50
ExceptionUtils.handleThrowable(t);
51
}
52
// Tell to close the socket
53
return false;
54
}
55
return true;
56
}

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

好了,終于到了Poller了,下一篇開始Poller。