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基礎"}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章