微信公众号:码上就有 公众号的文章名称:JAVA中的I/O模型-多路复用
在日常的IO模型中,我们应该听过
BIO
、NIO
以及AIO
。对于BIO
和NIO
想必许多开发接触过,至于后面的AIO
可能大部分都是没有使用过(可能停留在Demo
上)。但是对于其中的原理真的都了解了吗?知道其中的是如何完成任务的嘛?跟着我走,带你认真的学习下相关知识!!!!
环境相关介绍:
1.8 - JDK (1.6前后有版本变化)
CentOS Linux release 7.8.2003 (Core)
BIO
是一个阻塞式,那接下来就看看为什么是阻塞式的?哪里阻塞了?
以下代码防止云主机上进行运行(序号是直接
set nu
,没考虑从0
开始)
12 public class BIO {
13
14 public static void main(String[] args) throws IOException {
15 ServerSocket serverSocket = new ServerSocket(8888);
16 System.out.println("step1 : new ServerSocket(8888) ");
17 while (true) {
18 Socket accept = serverSocket.accept();
19 System.out.println("step2 accept client: " + accept.getPort());
20 new Thread(new Runnable() {
21 Socket ss;
22 public Runnable setSS(Socket s) {
23 ss = s;
24 return this;
25 }
26 public void run() {
27 try {
28 InputStream inputStream = ss.getInputStream();
29 BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
30 while (true) {
31 System.out.println(reader.readLine());
32 }
33 } catch (IOException e) {
34 e.printStackTrace();
35 }
36 }
37 }.setSS(accept)).start();
38 }
39 }
40 }
当我们通过编译之后运行起来,通过命令找到对应指令文件查看(这里区分下主进程以及当
socket
创建完成之后子进程的文件,我会把主要部分摘取出来)。
主进程文件:
2819 socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 7
2820 fcntl(7, F_GETFL) = 0x2 (flags O_RDWR)
2821 fcntl(7, F_SETFL, O_RDWR|O_NONBLOCK) = 0
2822 setsockopt(7, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
2823 lseek(3, 65120315, SEEK_SET) = 65120315
2824 read(3, "PK\3\4\n\0\0\10\0\0X\203\6Q~\244\245j\301\3\0\0\301\3\0\0\26\0\0\0", 30) = 30
2825 lseek(3, 65120367, SEEK_SET) = 65120367
2826 read(3, "\312\376\272\276\0\0\0004\0.\n\0\10\0#\t\0\7\0$\n\0\t\0%\n\0\t\0&\7\0"..., 961) = 961
2827 lseek(3, 65112663, SEEK_SET) = 65112663
2828 read(3, "PK\3\4\n\0\0\10\0\0X\203\6Q\17umL\251\35\0\0\251\35\0\0\35\0\0\0", 30) = 30
2829 lseek(3, 65112722, SEEK_SET) = 65112722
2830 read(3, "\312\376\272\276\0\0\0004\1^\n\0Y\0\275\7\0\276\10\0\277\n\0\2\0\300\n\0\301\0\302\7"..., 7593) = 7593
2831 lseek(3, 65112085, SEEK_SET) = 65112085
2832 read(3, "PK\3\4\n\0\0\10\0\0X\203\6Q\247F\361\221\5\2\0\0\5\2\0\0\37\0\0\0", 30) = 30
2833 lseek(3, 65112146, SEEK_SET) = 65112146
2834 read(3, "\312\376\272\276\0\0\0004\0\32\n\0\3\0\24\7\0\26\7\0\27\1\0\6<init>\1\0"..., 517) = 517
2835 bind(7, {sa_family=AF_INET, sin_port=htons(8888), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
2836 listen(7, 50) = 0
2837 futex(0x7fd378110954, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x7fd378110950, FUTEX_OP_SET<<28|0<<12|FUTEX_OP_CMP_GT<<24|0x1) = 1
2838 write(1, "step1 : new ServerSocket(8888) ", 31) = 31
2839 write(1, "\n", 1) = 1
2840 lseek(3, 65254201, SEEK_SET) = 65254201
2841 read(3, "PK\3\4\n\0\0\10\0\0N\203\6Q\311g\27\21\315A\0\0\315A\0\0\25\0\0\0", 30) = 30
2842 lseek(3, 65254252, SEEK_SET) = 65254252
2843 read(3, "\312\376\272\276\0\0\0004\2#\n\0\6\1S\t\0\236\1T\t\0\236\1U\t\0\236\1V\t\0"..., 16845) = 16845
2844 poll([{fd=7, events=POLLIN|POLLERR}], 1, -1
上面的主文件我们只需要关注2819
、2835
、2836
以及2844
四行,前三行分别对应的是socket
的创建
,以及绑定端口
和监听事件
。而后面的poll
则是一个等待事件函数,我们接下来看看方法描述。
再看下方法返回值:
可看出poll
是等待客户端连接的一个函数,如果当前没有客户端连接,则会一直等待。
接下来我这边就触发一个客户端的连接,让程序进行下去。
主进程文件:
2844 poll([{fd=7, events=POLLIN|POLLERR}], 1, -1) = 1 ([{fd=7, revents=POLLIN}])
2845 accept(7, {sa_family=AF_INET, sin_port=htons(48408), sin_addr=inet_addr("127.0.0.1")}, [16]) = 8
2846 fcntl(8, F_GETFL) = 0x2 (flags O_RDWR)
2847 fcntl(8, F_SETFL, O_RDWR) = 0
2848 write(1, "step2 accept client: 48408", 27) = 27
2849 write(1, "\n", 1) = 1
2850 stat("/root/straceDir/bio/BIO$1.class", {st_mode=S_IFREG|0644, st_size=1080, ...}) = 0
2851 open("/root/straceDir/bio/BIO$1.class", O_RDONLY) = 9
2852 fstat(9, {st_mode=S_IFREG|0644, st_size=1080, ...}) = 0
2853 futex(0x7f2e4811dc54, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x7f2e4811dc50, FUTEX_OP_SET<<28|0<<12|FUTEX_OP_CMP_GT<<24|0x1) = 1
2854 futex(0x7f2e4811dc28, FUTEX_WAKE_PRIVATE, 1) = 0
2855 stat("/root/straceDir/bio/BIO$1.class", {st_mode=S_IFREG|0644, st_size=1080, ...}) = 0
2856 read(9, "\312\376\272\276\0\0\0004\0H\n\0\16\0#\t\0\r\0$\n\0%\0&\7\0'\7\0(\n"..., 1024) = 1024
2857 read(9, "\30\7\0\32\7\0\33\377\0\f\0\1\7\0\34\0\1\7\0\35\0\3\0\36\0\0\0\2\0\37\0 "..., 56) = 56
2858 close(9) = 0
2859 lseek(3, 69644382, SEEK_SET) = 69644382
2860 read(3, "PK\3\4\n\0\0\10\0\0J\203\6QS\211A\367?\10\0\0?\10\0\0;\0\0\0", 30) = 30
2861 lseek(3, 69644471, SEEK_SET) = 69644471
2862 read(3, "\312\376\272\276\0\0\0004\0H\7\0003\n\0\v\0004\t\0\10\0005\n\0\1\0006\t\0\v\0"..., 2111) = 2111
2863 mmap(NULL, 1052672, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7f2e34543000
2864 clone(child_stack=0x7f2e34642fb0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0x7f2 e346439d0, tls=0x7f2e34643700, child_tidptr=0x7f2e346439d0) = 27730
上面我们主要看2844
、2845
以及2864
返回的fd
(这里我们是通过代码方式发派到子线程中去接受数据读取)。
上面主要做了两件事:
accept
(fd
还是上面创建的socket
)。clone
)。我们继续跟到对应子进程中的文件中:
1 set_robust_list(0x7f2e346439e0, 24) = 0
2 gettid() = 27730
3 rt_sigprocmask(SIG_BLOCK, NULL, [QUIT], 8) = 0
4 rt_sigprocmask(SIG_UNBLOCK, [HUP INT ILL BUS FPE SEGV USR2 TERM], NULL, 8) = 0
5 rt_sigprocmask(SIG_BLOCK, [QUIT], NULL, 8) = 0
6 futex(0x7f2e4804c454, FUTEX_WAKE_OP_PRIVATE, 1, 1, 0x7f2e4804c450, FUTEX_OP_SET<<28|0<<12|FUTEX_OP_CMP_GT<<24|0x1) = 1
7 futex(0x7f2e4815da54, FUTEX_WAIT_PRIVATE, 1, NULL) = 0
8 futex(0x7f2e4815da28, FUTEX_WAKE_PRIVATE, 1) = 0
9 mmap(NULL, 134217728, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x7f2dfc000000
10 munmap(0x7f2e00000000, 67108864) = 0
11 mprotect(0x7f2dfc000000, 135168, PROT_READ|PROT_WRITE) = 0
12 sched_getaffinity(27730, 32, [0, 1]) = 32
13 sched_getaffinity(27730, 32, [0, 1]) = 32
14 mmap(0x7f2e34543000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f2e34543000
15 mprotect(0x7f2e34543000, 12288, PROT_NONE) = 0
16 prctl(PR_SET_NAME, "Thread-0") = 0
17 lseek(3, 31619740, SEEK_SET) = 31619740
18 read(3, "PK\3\4\n\0\0\10\0\0N\203\6QJg1\367\267\3\0\0\267\3\0\0\27\0\0\0", 30) = 30
19 lseek(3, 31619793, SEEK_SET) = 31619793
20 read(3, "\312\376\272\276\0\0\0004\0000\t\0\6\0 \n\0\7\0!\t\0\36\0\"\n\0#\0\37\n\0"..., 951) = 951
21 lseek(3, 31621751, SEEK_SET) = 31621751
22 read(3, "PK\3\4\n\0\0\10\0\0O\203\6Q\316\311\205\235s\20\0\0s\20\0\0 \0\0\0", 30) = 30
23 lseek(3, 31621813, SEEK_SET) = 31621813
24 read(3, "\312\376\272\276\0\0\0004\0\266\n\0m\0n\n\0-\0o\t\0,\0p\t\0,\0q\t\0"..., 4211) = 4211
25 recvfrom(8,
建立连接之后,我们可以看到25
行这里,在fd = 8
(上面accept
事件后产生对应fd=8
)这里等待接收数据。
从上面的方法描述中,我们可以看出该方法是一个阻塞式的,也就意味着,如果当前没有读取消息,则这个子进程就会一直hang
住,这也就以为这我们为什么需要开辟一个子进程去完成对应的读取消息的事情。
通过上面的现象,我们可以看到对应的
BIO
执行过程,了解到为什么会有同步阻塞的事情发生。
如果没有开辟子进程,那么demo
中的18
以及31
行都会发生阻塞事件,而当我们开辟了子进程,那么18
行依旧会发生对应的阻塞,同时也浪费了资源(一万个连接则创建了一万个子进程)。
当我选择
BIO
去做业务的时候,则需要考虑他能带来什么样的好处以及弊端,有利于帮助我们选择合适的一个网络IO模型。那么他的优势以及弊端各是什么呢?
优势:
代码编写简单
弊端:
recvfrom
为用户态程序调用内核系统进行等待数据接收)下一节我们再讲解接下来的几种IO模型,让大家能够很好的体会到为什么需要不断的进行迭代升级。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。