核心结论:
不同版本jdk实现方式不一致
如果不给socket设置nonblocking,accept会阻塞直到数据到达
poll的调用是阻塞的,直到注册的event发生后,返回发生事件的fd
环境准备
centOS 7
jdk1.5.0-jdk1.8.0
strace
测试代码
BIOServer.java
测试步骤
1、编译BIOServer.java后,命令行启动,监听8080端口
2、模拟client端telnet localhost 8080,连通后立马断开
3、strace监听Java进程的函数调用
Java5
strace调用栈
查看man手册
如果没有对socket设置nonblocking,accept会一直阻塞直到一个链接出现
结论
java5中的bio是通过accept阻塞实现
Java6
strace调用栈
// 打开6号fd
13614 socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 6
13615 fcntl(6, F_GETFL) = 0x2 (flags O_RDWR)
13616 fcntl(6, F_SETFL, O_RDWR|O_NONBLOCK) = 0
13617 setsockopt(6, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
13618 gettimeofday({tv_sec=1588790897, tv_usec=258322}, NULL) = 0
13619 gettimeofday({tv_sec=1588790897, tv_usec=277413}, NULL) = 0
13620 gettimeofday({tv_sec=1588790897, tv_usec=277603}, NULL) = 0
//对fd6绑定8080
13621 bind(6, {sa_family=AF_INET, sin_port=htons(8080), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
//监听
13622 listen(6, 50) = 0
13623 gettimeofday({tv_sec=1588790897, tv_usec=278363}, NULL) = 0
13624 gettimeofday({tv_sec=1588790897, tv_usec=287641}, NULL) = 0
//先把6号fd放入poll中监听,返回1个POLLIN的fd
13625 poll([{fd=6, events=POLLIN|POLLERR}], 1, -1) = 1 ([{fd=6, revents=POLLIN}])
//调用accept把fd6中的内容读出来
13626 accept(6, {sa_family=AF_INET, sin_port=htons(40868), sin_addr=inet_addr("127.0.0.1")}, [16]) = 8
13627 fcntl(8, F_GETFL) = 0x2 (flags O_RDWR)
13628 fcntl(8, F_SETFL, O_RDWR) = 0
13629 gettimeofday({tv_sec=1588790899, tv_usec=835776}, NULL) = 0
13630 gettimeofday({tv_sec=1588790899, tv_usec=837031}, NULL) = 0
13631 gettimeofday({tv_sec=1588790899, tv_usec=837294}, NULL) = 0
13632 gettimeofday({tv_sec=1588790899, tv_usec=837659}, NULL) = 0
13633 gettimeofday({tv_sec=1588790899, tv_usec=838010}, NULL) = 0
13634 write(1, "===========", 11) = 11
13635 write(1, "\n", 1) = 1
// 读取完数据后,再次吧6号fd放入到poll中等待数据
13636 poll([{fd=6, events=POLLIN|POLLERR}], 1, -1 <unfinished ...>) = ?
13637 +++ exited with 130 +++
- 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.
listen之后这里没有立即调用 accept,而是先调用poll把 server_sockfd 与pollfdArray[0]关联起来,然后再把pollfdArray放到poll里去,这里只有一个文件描述符。
调用poll会使得线程阻塞,当有客户端连接进来的时候,poll函数就会返回一个整数,代表了数组中有多少个socket上有数据到达。对于第一次连接这种情况,返回值就是1。
接着,先判断pollfdArray[0]上是不是有数据,如果有的话,再去调用accept去接受新的连接,新的连接创建以后,我们会把新的socket放到pollfdArray中去,继续这个循环,然后在poll中再次休眠。
先看man手册中对于poll的定义:
man手册可以得到如下结论:
1、poll 是和 select类似的方法
2、当没有任何event到来时,poll会阻塞,直到一个event发生
3、timeout参数明确了poll在指定的毫秒内阻塞,指定一个负数表示无限超时
验证
猜想server启动后,没有客户端建立连接,系统调用应该阻塞在poll方法上
当有client与8080建立连接时,日志滚动,出现accept调用
日志继续停留在poll方法,验证猜想是正确的
结论
1、jdk6中,bio通过 poll 和 accept 的方式实现
2、poll方法是阻塞的
Java7/8
strace调用栈
18774 socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 6
18775 fcntl(6, F_GETFL) = 0x2 (flags O_RDWR)
18776 fcntl(6, F_SETFL, O_RDWR|O_NONBLOCK) = 0
18777 setsockopt(6, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
18778 gettimeofday({tv_sec=1588793691, tv_usec=279644}, NULL) = 0
18779 gettimeofday({tv_sec=1588793691, tv_usec=279906}, NULL) = 0
18780 gettimeofday({tv_sec=1588793691, tv_usec=280172}, NULL) = 0
18781 bind(6, {sa_family=AF_INET, sin_port=htons(8080), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
18782 listen(6, 50) = 0
18783 gettimeofday({tv_sec=1588793691, tv_usec=281027}, NULL) = 0
18784 gettimeofday({tv_sec=1588793691, tv_usec=281295}, NULL) = 0
18785 gettimeofday({tv_sec=1588793691, tv_usec=291874}, NULL) = 0
18786 poll([{fd=6, events=POLLIN|POLLERR}], 1, -1) = 1 ([{fd=6, revents=POLLIN}])
18787 accept(6, {sa_family=AF_INET, sin_port=htons(40874), sin_addr=inet_addr("127.0.0.1")}, [16]) = 7
18788 fcntl(7, F_GETFL) = 0x2 (flags O_RDWR)
18789 fcntl(7, F_SETFL, O_RDWR) = 0
18790 gettimeofday({tv_sec=1588793691, tv_usec=912271}, NULL) = 0
18791 gettimeofday({tv_sec=1588793691, tv_usec=912551}, NULL) = 0
18792 write(1, "===========", 11) = 11
18793 gettimeofday({tv_sec=1588793691, tv_usec=913462}, NULL) = 0
18794 write(1, "\n", 1) = 1
18795 poll([{fd=6, events=POLLIN|POLLERR}], 1, -1 <unfinished ...>) = ?
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
可以看出jdk7和jdk8跟jdk6中的实现方式一致
备忘录
不同版本jdk实现方式不一致
如果不给socket设置nonblocking,accept会阻塞直到数据到达
poll的调用是阻塞的,直到注册的event发生后,返回发生事件的fd
来源:InfoQ