网络IO模型演进01——BIO模型示例

1. 简介

BIO 全称 Blocking IO , 也就是阻塞IO,这里的阻塞体现在两个方面,
- accept内核分配的Socket资源后,阻塞的等待客户端的连接
- 读取客户端发送的数据时是阻塞的

2. 笔者环境
  • Ubuntu 18.04
  • JDK1.4
3. 安装JDK1.4

注意这里要演示原始的BIO模型,需要使用JDK1.4来编译;如果使用JDK8编译,会发现在系统调用层面使用的是poll模型

  • https://www.oracle.com/java/technologies/java-archive-javase-v14-downloads.html
  • chmod a+x j2sdk-1_4_2_19-linux-i586.bin
  • ./j2sdk-1_4_2_19-linux-i586.bin
  • 如果报 ./install.sfx.5628: not found 这个错误
  • 需要执行sudo apt-get install g+±multilib
4. 示例代码
  • vim SocketBIO.java
  • j2sdk1.4.2_19/bin/javac SocketBIO.java
  • strace -ff -o out j2sdk1.4.2_19/bin/java SocketBIO
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class SocketBIO {


    public static void main(String[] args) throws Exception {
        ServerSocket server = new ServerSocket(9090,20);

        System.out.println("create ServerSocket");

        while (true) {
            final  Socket client = server.accept();  //阻塞1

            System.out.println("client " + client.getPort());

            new Thread(new Runnable(){

                public void run() {
                    InputStream in = null;
                    try {
                        in = client.getInputStream();
                        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
                        while(true){

                            String dataline = reader.readLine(); //阻塞2

                            if(null != dataline){
                                System.out.println(dataline);
                            }else{
                                client.close();
                                break;
                            }
                        }
                        System.out.println("client close");

                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                }



            }).start();

        }
    }

}

  • 这里strace 是 查看系统调用命令,会将结果输出到out为前缀的文件中,后面的id是线程id
  • 可以看到此时控制台输出
$ strace -ff -o out j2sdk1.4.2_19/bin/java SocketBIO
create ServerSocket
5. 查看系统调用
  • 打开一个新的命令行,查看系统调用日志,查找输出 create ServerSocket 的文件
$ grep ServerSocket out.*
out.2350:write(1, "create ServerSocket", 19)     = 19
  • 打开out.2350文件,查找上面 write(1, “create ServerSocket”, 19)这一行
  • vim out.2350
  • 在vim中输入 / 就可以查找,然后输入 ServerSocket就可以查到了
socket(AF_INET6, SOCK_STREAM, IPPROTO_IP) = 3 ##返回的文件描述符是3
setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
bind(3, {sa_family=AF_INET6, sin6_port=htons(9090), inet_pton(AF_INET6, "::", &sin6_addr), sin6_flowinfo=htonl(0)}, 24) = 0
listen(3, 20)                           = 0 
write(1, "create ServerSocket", 19)     = 19 ##返回的是字符长度
write(1, "\n", 1)                       = 1
accept(3,                                   ## 阻塞了,没有返回值
  • 可以发现Socket 的创建过程使用了四个关键的系统调用
    • socket
    • bind
    • listen
    • accept
6. 客户端连接
  • 新打开一个命令行,进行连接
  • nc localhost 9090
  • 继续查看系统调用 vim out.2350
accept(3, {sa_family=AF_INET6, sin6_port=htons(44028), inet_pton(AF_INET6, "::ffff:127.0.0.1", &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, [28]) = 5
write(1, "client 44028", 12)            = 12
write(1, "\n", 1)                       = 1
stat64("/opt/bak/io/SocketBIO$1.class", {st_mode=S_IFREG|0644, st_size=990, ...}) = 0
openat(AT_FDCWD, "/opt/bak/io/SocketBIO$1.class", O_RDONLY|O_LARGEFILE) = 6
fstat64(6, {st_mode=S_IFREG|0644, st_size=990, ...}) = 0
stat64("/opt/bak/io/SocketBIO$1.class", {st_mode=S_IFREG|0644, st_size=990, ...}) = 0
read(6, "\312\376\272\276\0\0\0.\0B\n\0\20\0\35\t\0\17\0\36\n\0\37\0 \7\0!\7\0\"\n"..., 990) = 990
close(6)                                = 0
mmap2(NULL, 528384, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0xe8f20000
mprotect(0xe8f21000, 524288, PROT_READ|PROT_WRITE|PROT_EXEC) = 0
clone(child_stack=0xe8fa0424, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tidptr=0xe8fa0ba8, tls={entry_number=12, base_addr=0xe8fa0b40, limit=0x0fffff, seg_32bit=1, contents=0, read_exec_only=0, limit_in_pages=1, seg_not_present=0, useable=1}, child_tidptr=0xe8fa0ba8) = 5526
futex(0x86cfd78, FUTEX_WAIT_PRIVATE, 0, NULL) = 0
futex(0x86a2cb4, FUTEX_WAIT_PRIVATE, 2, NULL) = 0
futex(0x86a2cb4, FUTEX_WAKE_PRIVATE, 1) = 0
sched_setscheduler(5526, SCHED_OTHER, [5]) = -1 EINVAL (Invalid argument)
accept(3,
  • 这里可以看到,在未进行客户端连接时阻塞的accept系统调用,已经完成返回值是文件描述符5
  • clone 系统调用开辟了一个新的线程,去处理客户端的数据
  • 然后继续accept阻塞
7. 客户端发送数据
  • 在上面的clone系统调用,返回值是5526
  • 我们来查看out.5526 文件
  • 最后一行系统调用 recv,传入文件描述符5,就是前面accept返回的文件描述符
recv(5,
  • 在nc 窗口下发送数据
$ nc localhost 9090
abc
  • 服务端收到数据
$ strace -ff -o out j2sdk1.4.2_19/bin/java SocketBIO
create ServerSocket
client 44028
abc
  • 再次查看out.5526 文件
recv(5, "abc\n", 8192, 0)               = 4
ioctl(5, FIONREAD, [0])                 = 0
write(1, "abc", 3)                      = 3
write(1, "\n", 1)                       = 1
recv(5,                                 ### 再次阻塞
8.总结
  1. 网络编程使用的系统调用
    • socket
    • bind
    • listen
    • accept
    • recv
  2. accept 和 recv都是阻塞的
  3. 由于accept 和 recv都是阻塞的,如果需要处理多个连接, 需要为每个客户端连接创建一个线程,造成大量的线程资源消耗
9 参考
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值