NIO 看破也说破(二)—— Java 中的两种BIO

{"type":"doc","content":[{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/0e36ad9712c8d9ad8f7a7c570","title":""},"content":[{"type":"text","text":"上一篇"}]},{"type":"text","text":"我们得出结论,提供网络能力的不是Java是Linux操作系统。本文我们通过分析系统函数调用,观察不同jdk版本中BIO的实现差别。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"核心结论:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不同版本jdk实现方式不一致"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果不给socket设置nonblocking,accept会阻塞直到数据到达"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"poll的调用是阻塞的,直到注册的event发生后,返回发生事件的fd"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/16/1617bdda2c73b1b916bac7628e2b522c.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"boxShadow"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"环境准备"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"centOS 7"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"jdk1.5.0-jdk1.8.0"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"strace"}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"测试代码"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"BIOServer.java"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"import java.io.IOException;\nimport java.net.ServerSocket;\n\npublic class BIOServer {\n public static void main(String[] args) throws IOException {\n ServerSocket server = new ServerSocket(8080);\n while (true) {\n server.accept();\n System.out.println(\"===========\");\n }\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"测试步骤"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"1、编译BIOServer.java后,命令行启动,监听8080端口"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"2、模拟client端telnet localhost 8080,连通后立马断开"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"3、strace监听Java进程的函数调用"}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"Java5"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"strace调用栈"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"//开启3号fd\n3188 socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3\n3189 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0\n//对fd3 绑定8080端口,返回成功\n3190 bind(3, {sa_family=AF_INET, sin_port=htons(8080), sin_addr=inet_addr(\"0.0.0.0\")}, 16) = 0\n//监听fd3 \n3191 listen(3, 50) = 0\n3197 gettimeofday({tv_sec=1588789268, tv_usec=224692}, NULL) = 0\n3198 gettimeofday({tv_sec=1588789268, tv_usec=224993}, NULL) = 0\n3199 gettimeofday({tv_sec=1588789268, tv_usec=225263}, NULL) = 0\n//从fd3中接受到新的连接,放到fd5中\n3200 accept(3, {sa_family=AF_INET, sin_port=htons(40862), sin_addr=inet_addr(\"127.0.0.1\")}, [16]) = 5\n3199 gettimeofday({tv_sec=1588789268, tv_usec=225263}, NULL) = 0\n3201 gettimeofday({tv_sec=1588789270, tv_usec=619848}, NULL) = 0\n3208 gettimeofday({tv_sec=1588789270, tv_usec=623749}, NULL) = 0\n3209 write(1, \"===========\", 11) = 11\n3210 write(1, \"\\n\", 1) = 1\n3211 accept(3, 0xffe9a4ec, [16]) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)\n3212 --- SIGINT {si_signo=SIGINT, si_code=SI_KERNEL} ---\n3213 futex(0xf79429c0, FUTEX_WAKE_PRIVATE, 1) = 1\n3214 rt_sigreturn({mask=[QUIT]}) = 102\n3215 accept(3, ) = ?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"查看man手册"}]},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"If no pending connections are present on the queue, and the socket is not marked as nonblocking, accept() blocks the caller until a\n connection is present. If the socket is marked nonblocking and no pending connections are present on the queue, accept() fails with\n the error EAGAIN or EWOULDBLOCK."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}},{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"如果没有对socket设置nonblocking,accept会一直阻塞直到一个链接出现"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"结论"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"java5中的bio是通过accept阻塞实现"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"Java6"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"strace调用栈"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"// 打开6号fd\n13614 socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 6\n13615 fcntl(6, F_GETFL) = 0x2 (flags O_RDWR)\n13616 fcntl(6, F_SETFL, O_RDWR|O_NONBLOCK) = 0\n13617 setsockopt(6, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0\n13618 gettimeofday({tv_sec=1588790897, tv_usec=258322}, NULL) = 0\n13619 gettimeofday({tv_sec=1588790897, tv_usec=277413}, NULL) = 0\n13620 gettimeofday({tv_sec=1588790897, tv_usec=277603}, NULL) = 0\n //对fd6绑定8080\n13621 bind(6, {sa_family=AF_INET, sin_port=htons(8080), sin_addr=inet_addr(\"0.0.0.0\")}, 16) = 0\n//监听\n13622 listen(6, 50) = 0\n13623 gettimeofday({tv_sec=1588790897, tv_usec=278363}, NULL) = 0\n13624 gettimeofday({tv_sec=1588790897, tv_usec=287641}, NULL) = 0\n//先把6号fd放入poll中监听,返回1个POLLIN的fd\n13625 poll([{fd=6, events=POLLIN|POLLERR}], 1, -1) = 1 ([{fd=6, revents=POLLIN}])\n//调用accept把fd6中的内容读出来\n13626 accept(6, {sa_family=AF_INET, sin_port=htons(40868), sin_addr=inet_addr(\"127.0.0.1\")}, [16]) = 8\n13627 fcntl(8, F_GETFL) = 0x2 (flags O_RDWR)\n13628 fcntl(8, F_SETFL, O_RDWR) = 0\n13629 gettimeofday({tv_sec=1588790899, tv_usec=835776}, NULL) = 0\n13630 gettimeofday({tv_sec=1588790899, tv_usec=837031}, NULL) = 0\n13631 gettimeofday({tv_sec=1588790899, tv_usec=837294}, NULL) = 0\n13632 gettimeofday({tv_sec=1588790899, tv_usec=837659}, NULL) = 0\n13633 gettimeofday({tv_sec=1588790899, tv_usec=838010}, NULL) = 0\n13634 write(1, \"===========\", 11) = 11\n13635 write(1, \"\\n\", 1) = 1\n// 读取完数据后,再次吧6号fd放入到poll中等待数据\n13636 poll([{fd=6, events=POLLIN|POLLERR}], 1, -1 ) = ?\n13637 +++ exited with 130 +++"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"listen之后这里没有立即调用 accept,而是先调用poll把 server_sockfd 与pollfdArray[0]关联起来,然后再把pollfdArray放到poll里去,这里只有一个文件描述符。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"调用poll会使得线程阻塞,当有客户端连接进来的时候,poll函数就会返回一个整数,代表了数组中有多少个socket上有数据到达。对于第一次连接这种情况,返回值就是1。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"接着,先判断pollfdArray[0]上是不是有数据,如果有的话,再去调用accept去接受新的连接,新的连接创建以后,我们会把新的socket放到pollfdArray中去,继续这个循环,然后在poll中再次休眠。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"先看man手册中对于poll的定义:"}]},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"NAME\n poll, ppoll - wait for some event on a file descriptor\n\nSYNOPSIS\n #include \n\n int poll(struct pollfd *fds, nfds_t nfds, int timeout);\nDESCRIPTION\n poll() performs a similar task to select(2): it waits for one of a set of file descriptors to become ready to perform I/O.\n \t\t…………\n …………\n …………\n If none of the events requested (and no error) has occurred for any of the file descriptors, then poll() blocks until one of the\n events occurs.\n\n The timeout argument specifies the minimum number of milliseconds that poll() will block. (This interval will be rounded up to the\n system clock granularity, and kernel scheduling delays mean that the blocking interval may overrun by a small amount.) Specifying a\n negative value in timeout means an infinite timeout. Specifying a timeout of zero causes poll() to return immediately, even if no\n file descriptors are ready."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}},{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"man手册可以得到如下结论:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}},{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"1、poll 是和 select类似的方法"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}},{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"2、当没有任何event到来时,poll会阻塞,直到一个event发生"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}},{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"3、timeout参数明确了poll在指定的毫秒内阻塞,指定一个负数表示无限超时"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"验证"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"猜想server启动后,没有客户端建立连接,系统调用应该阻塞在poll方法上"}]},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"[root@f00e68119764 tmp]# tail -f out.3257\nclock_gettime(CLOCK_MONOTONIC, {tv_sec=49698, tv_nsec=95678478}) = 0\nclock_gettime(CLOCK_MONOTONIC, {tv_sec=49698, tv_nsec=95961778}) = 0\nclock_gettime(CLOCK_MONOTONIC, {tv_sec=49698, tv_nsec=96243778}) = 0\nclock_gettime(CLOCK_MONOTONIC, {tv_sec=49698, tv_nsec=96531678}) = 0\nclock_gettime(CLOCK_MONOTONIC, {tv_sec=49698, tv_nsec=96818478}) = 0\nclock_gettime(CLOCK_MONOTONIC, {tv_sec=49698, tv_nsec=97112578}) = 0\nclock_gettime(CLOCK_MONOTONIC, {tv_sec=49698, tv_nsec=97404578}) = 0\nclock_gettime(CLOCK_MONOTONIC, {tv_sec=49698, tv_nsec=97692278}) = 0\nclock_gettime(CLOCK_MONOTONIC, {tv_sec=49698, tv_nsec=98007178}) = 0\npoll([{fd=5, events=POLLIN|POLLERR}], 1, -1"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"当有client与8080建立连接时,日志滚动,出现accept调用"}]},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":" 18228 accept(5, {sa_family=AF_INET, sin_port=htons(40884), sin_addr=inet_addr(\"127.0.0.1\")}, [16]) = 6\n 18229 fcntl(6, F_GETFL) = 0x2 (flags O_RDWR)\n 18230 fcntl(6, F_SETFL, O_RDWR) = 0\n 18231 clock_gettime(CLOCK_MONOTONIC, {tv_sec=50008, tv_nsec=62405578}) = 0\n 18232 clock_gettime(CLOCK_MONOTONIC, {tv_sec=50008, tv_nsec=62713278}) = 0\n 18252 clock_gettime(CLOCK_MONOTONIC, {tv_sec=50008, tv_nsec=67718778}) = 0\n 18253 write(1, \"===========\", 11) = 11\n 18254 clock_gettime(CLOCK_MONOTONIC, {tv_sec=50008, tv_nsec=68673978}) = 0\n 18255 write(1, \"\\n\", 1) = 1\n 18256 poll([{fd=5, events=POLLIN|POLLERR}], 1, -1"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"日志继续停留在poll方法,验证猜想是正确的"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"结论"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"1、jdk6中,bio通过 poll 和 accept 的方式实现"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"2、poll方法是阻塞的"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"Java7/8"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"strace调用栈"}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"18774 socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 6\n18775 fcntl(6, F_GETFL) = 0x2 (flags O_RDWR)\n18776 fcntl(6, F_SETFL, O_RDWR|O_NONBLOCK) = 0\n18777 setsockopt(6, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0\n18778 gettimeofday({tv_sec=1588793691, tv_usec=279644}, NULL) = 0\n18779 gettimeofday({tv_sec=1588793691, tv_usec=279906}, NULL) = 0\n18780 gettimeofday({tv_sec=1588793691, tv_usec=280172}, NULL) = 0\n18781 bind(6, {sa_family=AF_INET, sin_port=htons(8080), sin_addr=inet_addr(\"0.0.0.0\")}, 16) = 0\n18782 listen(6, 50) = 0\n18783 gettimeofday({tv_sec=1588793691, tv_usec=281027}, NULL) = 0\n18784 gettimeofday({tv_sec=1588793691, tv_usec=281295}, NULL) = 0\n18785 gettimeofday({tv_sec=1588793691, tv_usec=291874}, NULL) = 0\n18786 poll([{fd=6, events=POLLIN|POLLERR}], 1, -1) = 1 ([{fd=6, revents=POLLIN}])\n18787 accept(6, {sa_family=AF_INET, sin_port=htons(40874), sin_addr=inet_addr(\"127.0.0.1\")}, [16]) = 7\n18788 fcntl(7, F_GETFL) = 0x2 (flags O_RDWR)\n18789 fcntl(7, F_SETFL, O_RDWR) = 0\n18790 gettimeofday({tv_sec=1588793691, tv_usec=912271}, NULL) = 0\n18791 gettimeofday({tv_sec=1588793691, tv_usec=912551}, NULL) = 0\n18792 write(1, \"===========\", 11) = 11\n18793 gettimeofday({tv_sec=1588793691, tv_usec=913462}, NULL) = 0\n18794 write(1, \"\\n\", 1) = 1\n18795 poll([{fd=6, events=POLLIN|POLLERR}], 1, -1 ) = ?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}}],"text":"可以看出jdk7和jdk8跟jdk6中的实现方式一致"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"备忘录"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}},{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"不同版本jdk实现方式不一致"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}},{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"如果不给socket设置nonblocking,accept会阻塞直到数据到达"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"size","attrs":{"size":12}},{"type":"color","attrs":{"color":"#F5222D","name":"red"}},{"type":"strong"}],"text":"poll的调用是阻塞的,直到注册的event发生后,返回发生事件的fd"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"欢迎交流"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/08/089c76b0c78251669ee0db322c88a906.jpeg","alt":null,"title":"反范式","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"boxShadow"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上一篇 "},{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/0e36ad9712c8d9ad8f7a7c570","title":""},"content":[{"type":"text","text":"NIO看破也说破(一)- Linux/IO基础"}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章