Netty学习-java BIO编程

Netty学习-java BIO编程

二月 08, 2020

一. Java BIO 基本介绍

  • Java bio 就是传统java io

  • 同步且阻塞, 服务端实现为一个连接一个线程, 每当客户端有连接请求时就启动一个线程, 如果这个连接不做任何事就会造成线程资源浪费, 可以通过线程池机制改善.

  • bio方式适用于连接数目较小且固定的架构, 对服务器资源要求高, 并发局限于Application, JDK1.4以前的唯一选择.

二. Java BIO 工作流程

解读:

  1. 首先, 服务端启动一个ServerSocket
  2. 然后, 客户端启动Socket对服务器进行通信, 默认情况服务端每有一个客户端就启动一个线程与之通信
  3. 客户端发出请求后, 服务端会先查看有没有可用的线程, 若没有则让客户端等等, 或者直接拒绝连接; 如果有线程可用, 客户端会在连接请求结束后在继续执行.

三. Java BIO应用实例

实例说明:

  1. 使用BIO模型编写一个服务端, 单客户端有连接时, 就启动线程通信
  2. 使用线程池机制改善连接
  3. 服务端可以接收客户端发送的数据

代码:

1
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import io.netty.util.concurrent.DefaultThreadFactory;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;

public class BioServer {

/**
* 客户端消息处理
* @param socket socket
*/
public static void handler(Socket socket){

try {

System.out.println("线程id = " + Thread.currentThread().getId() +
" 名字 = " + Thread.currentThread().getName());

byte[] bytes = new byte[1024];

InputStream inputStream = socket.getInputStream();

while (true) {
System.out.println("线程id = " + Thread.currentThread().getId() +
" 名字 = " + Thread.currentThread().getName());

System.out.println(">>> read ....");

int read = inputStream.read(bytes);

if (read != -1) {
System.out.println(new String(bytes, 0, read));
}else {
break;
}
}

}catch (Exception e){
e.printStackTrace();
}finally {
System.out.println("关闭client的连接");

try{
socket.close();
}catch (Exception e){
e.printStackTrace();
}
}

}

public static void main(String[] args) throws IOException {


// 创建线程池
ExecutorService newCachedThreadPool = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<>(),
new DefaultThreadFactory("newCachedThreadPool"));


// 创建SeverSocket
ServerSocket serverSocket = new ServerSocket(8888);

System.out.println(">>> Server starting ...... ");

while (true){
System.out.println(
"线程id = " + Thread.currentThread().getId() +
" 名字 = " + Thread.currentThread().getName());

System.out.println(">>> waiting to connect .....");

/*
监听客户端连接, 没有连接时,
serverSocket.accept()方法会 -> 阻塞 <- 在此,
直到有客户端连接
*/
final Socket socket = serverSocket.accept();

System.out.println("连接到一个客户端");

// 连接到一个客户端就从线程池里取线程执行
newCachedThreadPool.execute(() -> handler(socket));


}

}
}

结果

1
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
连接到一个客户端
线程id = 1 名字 = main
>>> waiting to connect .....
连接到一个客户端
线程id = 1 名字 = main
>>> waiting to connect .....
线程id = 13 名字 = newCachedThreadPool-1-3
线程id = 13 名字 = newCachedThreadPool-1-3
>>> read ....
线程id = 14 名字 = newCachedThreadPool-1-4
线程id = 14 名字 = newCachedThreadPool-1-4
>>> read ....
GET / HTTP/1.1
Host: 127.0.0.1:8888
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.9 Safari/537.36
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Purpose: prefetch
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9


线程id = 14 名字 = newCachedThreadPool-1-4
>>> read ....
关闭client的连接
GET / HTTP/1.1
Host: 127.0.0.1:8888
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.9 Safari/537.36
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9


线程id = 13 名字 = newCachedThreadPool-1-3
>>> read ....
关闭client的连接

从结果看, 发生了两次请求, 因为Google浏览器当你发送请求时会有一次请求 网站图标(/favicon.ico). 从线程信息打印的结果看, 客户端连接过程中并不是只使用一个线程, 说明线程池起到了作用