IO原理与JAVA RPC

Linux IO

文件分区

# 不同的文件系统挂载到不同的目录下,如下/dev挂载点,取消/dev的挂载后,如存在dev文件则仍可见dev
[root@VM-16-13-centos ~]# df
文件系统          1K-块    已用     可用 已用% 挂载点
devtmpfs        1896680       0  1896680    0% /dev
tmpfs           1912948      24  1912924    1% /dev/shm
tmpfs           1912948     484  1912464    1% /run
tmpfs           1912948       0  1912948    0% /sys/fs/cgroup
/dev/vda1      82503044 8552896 70494644   11% /
tmpfs            382588       0   382588    0% /run/user/0

# 查看不同挂载点分配的空间详情
[root@VM-16-13-centos /]# df -h
文件系统        容量  已用  可用 已用% 挂载点
devtmpfs        1.9G     0  1.9G    0% /dev
tmpfs           1.9G   24K  1.9G    1% /dev/shm
tmpfs           1.9G  492K  1.9G    1% /run
tmpfs           1.9G     0  1.9G    0% /sys/fs/cgroup
/dev/vda1        79G  8.2G   68G   11% /
tmpfs           374M     0  374M    0% /run/user/0

# 卸载挂载目录
umount [挂载点目录]

# 挂载目录
mount [被挂载的分区镜像的位置] [挂载点目录]

硬链接(如同一份副本)

# root 目录下有一文件 a.txt
# 将 a.txt 文件硬链接到 b.txt
ln /root/a.txt /root/b.txt

# stat 查看Inode节点都是同一份
[root@VM-16-13-centos ~]# stat a.txt 
  文件:a.txt
  大小:13        	块:8          IO 块:4096   普通文件
设备:fd01h/64769d	Inode:397618      硬链接:2
权限:(0644/-rw-r--r--)  Uid:(    0/    root)   Gid:(    0/    root)
最近访问:2022-04-19 21:04:52.485540981 +0800
最近更改:2022-04-19 21:04:50.707541495 +0800
最近改动:2022-04-19 21:05:07.032536783 +0800
创建时间:-
[root@VM-16-13-centos ~]# stat b.txt 
  文件:b.txt
  大小:13        	块:8          IO 块:4096   普通文件
设备:fd01h/64769d	Inode:397618      硬链接:2
权限:(0644/-rw-r--r--)  Uid:(    0/    root)   Gid:(    0/    root)
最近访问:2022-04-19 21:04:52.485540981 +0800
最近更改:2022-04-19 21:04:50.707541495 +0800
最近改动:2022-04-19 21:05:07.032536783 +0800
创建时间:-

# 硬链接了几份文件就是几,这里有两份a.txt与b.txt
[root@VM-16-13-centos ~]# ll
总用量 8
-rw-r--r-- 2 root root 13 4月  19 21:04 a.txt
-rw-r--r-- 2 root root 13 4月  19 21:04 b.txt

# 任意编辑其中一文件另外被被链接的文件均会被修改

# 当删除a.txt或b.txt另一文件会继续存在,并且链接number会-1
[root@VM-16-13-centos ~]# ll
总用量 8
-rw-r--r-- 2 root root 13 4月  19 21:04 a.txt
-rw-r--r-- 2 root root 13 4月  19 21:04 b.txt
[root@VM-16-13-centos ~]# rm -rf a.txt 
[root@VM-16-13-centos ~]# ll
总用量 4
-rw-r--r-- 1 root root 13 4月  19 21:04 b.txt

软链接(如同被链接文件的快捷方式)

# 将b.txt文件软链接到c.txt
ln -s /root/b.txt /root/c.txt
[root@VM-16-13-centos ~]# ln -s /root/b.txt /root/c.txt
[root@VM-16-13-centos ~]# ll
总用量 4
-rw-r--r-- 1 root root 13 4月  19 21:04 b.txt
lrwxrwxrwx 1 root root 11 4月  19 21:13 c.txt -> /root/b.txt

# 从上发现链接number不变都为1,查看器Inode节点不在同一结点
[root@VM-16-13-centos ~]# stat b.txt 
  文件:b.txt
  大小:13        	块:8          IO 块:4096   普通文件
设备:fd01h/64769d	Inode:397618      硬链接:1
权限:(0644/-rw-r--r--)  Uid:(    0/    root)   Gid:(    0/    root)
最近访问:2022-04-19 21:04:52.485540981 +0800
最近更改:2022-04-19 21:04:50.707541495 +0800
最近改动:2022-04-19 21:12:37.980406709 +0800
创建时间:-
[root@VM-16-13-centos ~]# stat c.txt 
  文件:c.txt -> /root/b.txt
  大小:11        	块:0          IO 块:4096   符号链接
设备:fd01h/64769d	Inode:398017      硬链接:1
权限:(0777/lrwxrwxrwx)  Uid:(    0/    root)   Gid:(    0/    root)
最近访问:2022-04-19 21:14:09.788380231 +0800
最近更改:2022-04-19 21:13:26.575392695 +0800
最近改动:2022-04-19 21:13:26.575392695 +0800
创建时间:-

# 编辑任意文件b.txt或c.txt其链接的文件都会改变

# 当删除被链接的文件b.txt则c.txt文件会显示异常无法操作
[root@VM-16-13-centos ~]# rm -rf b.txt 
[root@VM-16-13-centos ~]# ll
总用量 0
lrwxrwxrwx 1 root root 11 4月  19 21:13 c.txt -> /root/b.txt (此处会显示为红色并闪烁,代表该文件不存在)

创建虚拟分区挂载

  • dd:拷贝数据生成文件
  • if:输入文件(input file)
  • /dev/zero:无限大的空
  • of:输出文件(output file)
  • bs:文件块大小(block size)
  • count:块的数量
[root@VM-16-13-centos armin]# dd if=/dev/zero of=mydisk bs=1048576 count=10
记录了10+0 的读入
记录了10+0 的写出
10485760 bytes (10 MB, 10 MiB) copied, 0.00874465 s, 1.2 GB/s
[root@VM-16-13-centos armin]# ll
总用量 10240
-rw-r--r-- 1 root root 10485760 4月  19 22:16 mydisk
[root@VM-16-13-centos armin]# ll -h
总用量 10M
-rw-r--r-- 1 root root 10M 4月  19 22:16 mydisk

# 将 mydisk 挂载到 /dev/loop0 文件系统,并格式化为 ext2 格式
[root@VM-16-13-centos armin]# losetup /dev/loop0 mydisk 
[root@VM-16-13-centos armin]# mke2fs /dev/loop0
mke2fs 1.45.6 (20-Mar-2020)
丢弃设备块: 完成                            
创建含有 10240 个块(每块 1k)和 2560 个inode的文件系统
文件系统UUID:7c626cc6-3e03-427e-b709-170e207dcbd8
超级块的备份存储于下列块: 
	8193

正在分配组表: 完成                            
正在写入inode表: 完成                            
写入超级块和文件系统账户统计信息: 已完成

# 将生成的文件系统挂载到 /mnt/ooxx目录
[root@VM-16-13-centos ooxx]# mount -t ext2 /dev/loop0 /mnt/ooxx/
[root@VM-16-13-centos ooxx]# df
文件系统          1K-块    已用     可用 已用% 挂载点
devtmpfs        1896680       0  1896680    0% /dev
tmpfs           1912948      24  1912924    1% /dev/shm
tmpfs           1912948     512  1912436    1% /run
tmpfs           1912948       0  1912948    0% /sys/fs/cgroup
/dev/vda1      82503044 8555436 70492104   11% /
tmpfs            382588       0   382588    0% /run/user/0
/dev/loop0         9911      92     9307    1% /mnt/ooxx

# 找到 bash(linux输入字符解析工具) 程序
[root@VM-16-13-centos ooxx]# whereis bash
bash: /usr/bin/bash /usr/share/man/man1/bash.1.gz /usr/share/info/bash.info.gz

# 将 bash 程序 copy 到 /mnt/ooxx/bin
[root@VM-16-13-centos ooxx]# mkdir bin
[root@VM-16-13-centos ooxx]# cp /usr/bin/bash bin
[root@VM-16-13-centos ooxx]# cd bin/
[root@VM-16-13-centos bin]# ll
总用量 1130
-rwxr-xr-x 1 root root 1150584 4月  19 22:33 bash

# 分析 bash 程序依赖的动态链接库
[root@VM-16-13-centos bin]# ldd bash 
	linux-vdso.so.1 (0x00007ffc09544000)
	libtinfo.so.6 => /lib64/libtinfo.so.6 (0x00007f67444ec000)
	libdl.so.2 => /lib64/libdl.so.2 (0x00007f67442e8000)
	libc.so.6 => /lib64/libc.so.6 (0x00007f6743f23000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f6744a37000)
	
# 模拟 bash 程序copy依赖的动态链接库到 /mnt/ooxx 目录,{}当前目录中的多个文件可使用
[root@VM-16-13-centos ooxx]# mkdir lib64
[root@VM-16-13-centos ooxx]# cp /lib64/{libtinfo.so.6,libdl.so.2,libc.so.6,ld-linux-x86-64.so.2} ./lib64/
[root@VM-16-13-centos ooxx]# ll
总用量 1131
-rwxr-xr-x 1 root root 1150584 4月  19 22:33 bash
drwxr-xr-x 2 root root    1024 4月  19 22:37 lib64
[root@VM-16-13-centos ooxx]# cd lib64/
[root@VM-16-13-centos lib64]# ll
总用量 3598
-rwxr-xr-x 1 root root  278504 4月  19 22:37 ld-linux-x86-64.so.2
-rwxr-xr-x 1 root root 3167976 4月  19 22:37 libc.so.6
-rwxr-xr-x 1 root root   28816 4月  19 22:37 libdl.so.2
-rwxr-xr-x 1 root root  187496 4月  19 22:37 libtinfo.so.6

# 把根目录切换到当前目录,并启动当前目录 bash,获取当前进程id号
[root@VM-16-13-centos ooxx]# chroot ./
bash-4.4# echo $$
1318231

# 将111输出到虚拟文件系统根目录下a.txt文件
bash-4.4# echo 111 > /a.txt

# 父 bash 进程号
bash-4.4# exit
exit
[root@VM-16-13-centos ooxx]# echo $$
1314500

# 查看虚拟文件系统根目录是否存在 a.txt文件(存在)
-rw-r--r-- 1 root root     4 4月  19 22:46 a.txt
drwxr-xr-x 2 root root  1024 4月  19 22:41 bin
drwxr-xr-x 2 root root  1024 4月  19 22:37 lib64
drwx------ 2 root root 12288 4月  19 22:23 lost+found
[root@VM-16-13-centos ooxx]# cat a.txt 
111

脏读

# lost 查看进程打开了哪些文件 $$代表当前bash进程
[root@VM-16-13-centos /]# lsof -p $$
COMMAND     PID USER   FD   TYPE             DEVICE SIZE/OFF     NODE NAME
bash    1514926 root  cwd    DIR              253,1     4096        2 /
bash    1514926 root  rtd    DIR              253,1     4096        2 /
bash    1514926 root  txt    REG              253,1  1150584   268773 /usr/bin/bash
bash    1514926 root  mem    REG              253,1    83728   268316 /usr/lib64/libnss_files-2.28.so
bash    1514926 root  mem    REG              253,1  9253600   394534 /var/lib/sss/mc/passwd
bash    1514926 root  mem    REG              253,1  2801698   267884 /usr/lib/locale/zh_CN.utf8/LC_COLLATE
bash    1514926 root  mem    REG              253,1  3167976   268071 /usr/lib64/libc-2.28.so
bash    1514926 root    0u   CHR              136,0      0t0        3 /dev/pts/0
bash    1514926 root    1u   CHR              136,0      0t0        3 /dev/pts/0
bash    1514926 root    2u   CHR              136,0      0t0        3 /dev/pts/0
bash    1514926 root    3r   REG              253,1  9253600   394534 /var/lib/sss/mc/passwd
bash    1514926 root    4u  unix 0xffff968df7c5ad00      0t0 33848491 type=STREAM
bash    1514926 root  255u   CHR              136,0      0t0        3 /dev/pts/0

FD
- cwd:当前工作目录
- rtd:root根目录在哪
- txt:文本域,进程启动时加载的可执行程序
- mem:分配的内存空间
- xu(u:读写都可以、r:读)
  - x=0:程序的标准输入
  - x=1:程序的标准输出
  - x=2:程序报错输出
TYPE
- REG:-(普通文件:可执行、图片、文本)
- CHR:c(字符设备)
- DIR:d(文件目录)
DEVICE
- 设备号
SIZE/OFF
- 偏移量
NODE
- Inode号

# 令文件描述符 8 可以去读取 xxoo.txt 文件
[root@VM-16-13-centos ~]# vi xxoo.txt
[root@VM-16-13-centos ~]# exec 8< xxoo.txt 
[root@VM-16-13-centos ~]# cd /proc/$$/fd
[root@VM-16-13-centos fd]# ll
总用量 0
lrwx------ 1 root root 64 4月  20 22:35 0 -> /dev/pts/0
lrwx------ 1 root root 64 4月  20 22:35 1 -> /dev/pts/0
lrwx------ 1 root root 64 4月  20 22:35 2 -> /dev/pts/0
lrwx------ 1 root root 64 4月  20 22:36 255 -> /dev/pts/0
lr-x------ 1 root root 64 4月  20 22:35 3 -> /var/lib/sss/mc/passwd
lrwx------ 1 root root 64 4月  20 22:35 4 -> 'socket:[33848491]'
lr-x------ 1 root root 64 4月  20 22:35 8 -> /root/xxoo.txt

# 8r 8文件描述符读取 偏移量 0t0 为 0字节
[root@VM-16-13-centos fd]# lsof -op $$
COMMAND     PID USER   FD   TYPE             DEVICE OFFSET     NODE NAME
bash    1514926 root  cwd    DIR                0,5        33848475 /proc/1514926/fd
bash    1514926 root  rtd    DIR              253,1               2 /
bash    1514926 root  txt    REG              253,1          268773 /usr/bin/bash
bash    1514926 root  mem    REG              253,1          267823 /usr/lib/locale/en_US.utf8/LC_COLLATE
bash    1514926 root  mem    REG              253,1          268316 /usr/lib64/libnss_files-2.28.so
bash    1514926 root  mem    REG              253,1          394534 /var/lib/sss/mc/passwd
bash    1514926 root  mem    REG              253,1          280182 /usr/lib64/libnss_sss.so.2
bash    1514926 root    0u   CHR              136,0    0t0        3 /dev/pts/0
bash    1514926 root    1u   CHR              136,0    0t0        3 /dev/pts/0
bash    1514926 root    2u   CHR              136,0    0t0        3 /dev/pts/0
bash    1514926 root    3r   REG              253,1    0t0   394534 /var/lib/sss/mc/passwd
bash    1514926 root    4u  unix 0xffff968df7c5ad00    0t0 33848491 type=STREAM
bash    1514926 root    8r   REG              253,1    0t0   395721 /root/xxoo.txt
bash    1514926 root  255u   CHR              136,0    0t0        3 /dev/pts/0

# 读取 8 文件描述符中的首行(包括换行符)数据到变量 a
[root@VM-16-13-centos fd]# read a 0<& 8
# 打印变量 a 读到的数据
[root@VM-16-13-centos fd]# echo $a
affdsadgsdag

# 查看 8r 新的偏移量 0t13 偏移了13个字节
[root@VM-16-13-centos fd]# lsof -op $$
COMMAND     PID USER   FD   TYPE             DEVICE OFFSET     NODE NAME
bash    1514926 root  cwd    DIR                0,5        33848475 /proc/1514926/fd
bash    1514926 root  rtd    DIR              253,1               2 /
bash    1514926 root  txt    REG              253,1          268773 /usr/bin/bash
bash    1514926 root  mem    REG              253,1          267823 /usr/lib/locale/en_US.utf8/LC_COLLATE
bash    1514926 root  mem    REG              253,1          268316 /usr/lib64/libnss_files-2.28.so
bash    1514926 root  mem    REG              253,1          394534 /var/lib/sss/mc/passwd
bash    1514926 root  mem    REG              253,1          280182 /usr/lib64/libnss_sss.so.2
bash    1514926 root    0u   CHR              136,0    0t0        3 /dev/pts/0
bash    1514926 root    1u   CHR              136,0    0t0        3 /dev/pts/0
bash    1514926 root    2u   CHR              136,0    0t0        3 /dev/pts/0
bash    1514926 root    3r   REG              253,1    0t0   394534 /var/lib/sss/mc/passwd
bash    1514926 root    4u  unix 0xffff968df7c5ad00    0t0 33848491 type=STREAM
bash    1514926 root    8r   REG              253,1   0t13   395721 /root/xxoo.txt
bash    1514926 root  255u   CHR              136,0    0t0        3 /dev/pts/0

# 新开的 bash 进程如同迭代器获取到了新的数据,可以做同上操作,互不影响

# 查看脏页缓存字符
[root@VM-16-13-centos ~]# cat /proc/vmstat | grep dirty
nr_dirty 25
nr_dirty_threshold 235233
nr_dirty_background_threshold 78347

socket

# ‘<’ 输入 ‘>’ 输出
# 将 8 输入输出来自/dev/tcp/www.baidu.com/80的数据
[root@VM-16-13-centos fd]# exec 8<> /dev/tcp/www.baidu.com/80
[root@VM-16-13-centos fd]# ll
总用量 0
lrwx------ 1 root root 64 4月  20 23:16 0 -> /dev/pts/2
lrwx------ 1 root root 64 4月  20 23:16 1 -> /dev/pts/2
lrwx------ 1 root root 64 4月  20 23:16 2 -> /dev/pts/2
lrwx------ 1 root root 64 4月  20 23:23 255 -> /dev/pts/2
lr-x------ 1 root root 64 4月  20 23:16 3 -> /var/lib/sss/mc/passwd
lrwx------ 1 root root 64 4月  20 23:16 4 -> 'socket:[33875783]'
lrwx------ 1 root root 64 4月  20 23:16 8 -> 'socket:[33884341]'
# 8 指向的是一个 IPv4 的 TCP 数据(一切皆文件)
[root@VM-16-13-centos fd]# lsof -op $$
COMMAND     PID USER   FD   TYPE             DEVICE OFFSET     NODE NAME
bash    1520604 root  cwd    DIR                0,5        33875767 /proc/1520604/fd
bash    1520604 root  rtd    DIR              253,1               2 /
bash    1520604 root  txt    REG              253,1          268773 /usr/bin/bash
bash    1520604 root  mem    REG              253,1          268322 /usr/lib64/libresolv-2.28.so
bash    1520604 root  mem    REG              253,1          268081 /usr/lib64/libnss_dns-2.28.so
bash    1520604 root  mem    REG              253,1          267823 /usr/lib/locale/en_US.utf8/LC_COLLATE
bash    1520604 root  mem    REG              253,1          268316 /usr/lib64/libnss_files-2.28.so
bash    1520604 root  mem    REG              253,1          394534 /var/lib/sss/mc/passwd
bash    1520604 root    0u   CHR              136,2    0t0        5 /dev/pts/2
bash    1520604 root    1u   CHR              136,2    0t0        5 /dev/pts/2
bash    1520604 root    2u   CHR              136,2    0t0        5 /dev/pts/2
bash    1520604 root    3r   REG              253,1    0t0   394534 /var/lib/sss/mc/passwd
bash    1520604 root    4u  unix 0xffff968d471c9200    0t0 33875783 type=STREAM
bash    1520604 root    8u  IPv4           33884341    0t0      TCP VM-16-13-centos:60170->112.80.248.76:http (ESTABLISHED)
bash    1520604 root  255u   CHR              136,2    0t0        5 /dev/pts/2

/proc:映射内核变量属性的目录

/proc/$$:当前 bash 的 pid($$/$BASHPID)

/proc/$$/fd:当前 bash 程序的所有文件描述符 lsof -of $$(查看当前程序的细节)

重定向:不是命令,机制

File file = new File("/ooxx/txt");
OutputStream out「out会映射到fd目录,指向到/ooxx.txt文件」 = new FileOutputStream(file);
out.write("aaaaa")

# 将当前目录输出到~目录下的ls.out文件中 1为输出
ls ./ 1> ~/ls.out
# 将查看(输入)到的 ooxx.txt文件数据 输出到 cat.out 文件
cat 0< ooxx.txt 1> cat.out

read

# read对换行符敏感,遇见换行(回车)则退出,c 为变量 
armin@xiaobawxuexiji2 ~ % read c
hello world!
armin@xiaobawxuexiji2 ~ % echo $c
hello world!
# 将 cat.out 文件 标准(0)输入(<)到 a 变量 
read a 0< cat.out

cat.out 文件内容

aaa
bbbb
cccc
~                                                                                                                                                            
"cat.out" 3L, 14C                                             1,1          全部

读取到的数据为 cat.out 文件中的第一行,因为 read 读到换行符则结束

[root@VM-16-13-centos ~]# read a 0< cat.out
[root@VM-16-13-centos ~]# echo $a
aaa

ls

# ls [当前目录] [一个不存在的目录] 不存在的目录会显示报错(2-异常输出) 存在的则正常标准输出(1-标准输出) 
[root@VM-16-13-centos ~]# ll
总用量 12
drwxr-xr-x 2 root root 4096 4月  19 22:16 armin
-rw-r--r-- 1 root root   14 4月  21 22:15 cat.out
-rw-r--r-- 1 root root   50 4月  21 22:15 xxoo.txt
[root@VM-16-13-centos ~]# ls ./ /cmdb
ls: 无法访问'/cmdb': 没有那个文件或目录
./:
armin  cat.out  xxoo.txt

# 将异常输出到 ls02.out 文件 标准输出到 ls01.out 文件
[root@VM-16-13-centos ~]# ls ./ /cmdb 1> ls01.out 2> ls02.out
[root@VM-16-13-centos ~]# cat ls01.out 
./:
armin
cat.out
ls01.out
ls02.out
xxoo.txt
[root@VM-16-13-centos ~]# cat ls02.out
ls: 无法访问'/cmdb': 没有那个文件或目录

# 如同时输出到同一文件,则前面输出的会被覆盖
[root@VM-16-13-centos ~]# ls ./ /cmdb 1> ls03.out 2> ls03.out
[root@VM-16-13-centos ~]# cat ls03.out 
./:
armin
cat.out
ls01.out
ls02.out
ls03.out
xxoo.txt
[root@VM-16-13-centos ~]# 

# 重定向操作符 < or > 左边放的 文件描述符 右边放的文件 如 右边需要放 文件描述符 则 需要在重定向描述符右边加上&
# 报错是由于 2 指向 1 这时1未指向任何文件所以又指回了屏幕,所以出现了异常信息,文件也未写入异常信息
[root@VM-16-13-centos ~]# ls ./ /cmdb 2>& 1 1> ls04.out
ls: 无法访问'/cmdb': 没有那个文件或目录

# 此时交换他们的位置即可实现同时写入一个文件
[root@VM-16-13-centos ~]# ls ./ /cmdb 1> ls04.out 2>& 1
[root@VM-16-13-centos ~]# cat ls04.out 
ls: 无法访问'/cmdb': 没有那个文件或目录
./:
armin
cat.out
ls01.out
ls02.out
ls03.out
ls04.out
xxoo.txt

|(pipeline管道)

  • head

    • # 读取文件的前十行
      head [file]
      # 读取文件的第一行
      head -1 [file]
      # 读取文件的前十一行
      head -11 [file]
      
  • tail

    • # 读取文件的最好十行
      tail [file]
      # 读取文件的最后一行
      tail -1 [file]
      # 读取文件的最后十二行
      tail -12 [file]
      
  • |

    • # 将文件开头前8行交给tail取最后一行(读第8行)
      head -8 [file] | tail -1
      

Linux基础

程序有父子关系

# 由此可见第一个 bash 进程是第二个 bash 进程的父进程
[root@VM-16-13-centos /]# echo $$
2013804
[root@VM-16-13-centos /]# /bin/bash
[root@VM-16-13-centos /]# echo $$
2014070
[root@VM-16-13-centos /]# pstree
systemd─┬─NetworkManager───2*[{NetworkManager}]
        ├─YDLive─┬─YDService─┬─sh───10*[{sh}]
        │        │           └─24*[{YDService}]
        │        └─10*[{YDLive}]
        ├─2*[agetty]
        ├─atd
        ├─auditd─┬─sedispatch
        │        └─2*[{auditd}]
        ├─barad_agent─┬─barad_agent
        │             └─barad_agent───2*[{barad_agent}]
        ├─chronyd
        ├─crond
        ├─dbus-daemon
        ├─lsmd
        ├─mcelog
        ├─mysqld_safe───mysqld───31*[{mysqld}]
        ├─nginx───nginx
        ├─polkitd───7*[{polkitd}]
        ├─rngd───4*[{rngd}]
        ├─rsyslogd───2*[{rsyslogd}]
        ├─sgagent───{sgagent}
        ├─sshd───sshd───sshd───bash───bash───pstree
        ├─sssd─┬─sssd_be
        │      └─sssd_nss
        ├─systemd───(sd-pam)
        ├─systemd-journal
        ├─systemd-logind
        ├─systemd-udevd
        ├─tat_agent───4*[{tat_agent}]
        └─tuned───3*[{tuned}]
[root@VM-16-13-centos /]# ps -fe | grep 2013804
root     2013804 2013803  0 11:09 pts/0    00:00:00 -bash
root     2014070 2013804  0 11:11 pts/0    00:00:00 /bin/bash
root     2014564 2014070  0 11:14 pts/0    00:00:00 grep --color=auto 2013804

# 如果退出则退出到了父进程
[root@VM-16-13-centos /]# echo $$
2013804

变量,父子进程变量隔离,打破隔离使用 export

# 定义一个变量 x 取其值
[root@VM-16-13-centos /]# x=100
[root@VM-16-13-centos /]# echo $x
100

# 父进程的变量子进程无法取值
[root@VM-16-13-centos /]# echo $$
2013804
[root@VM-16-13-centos /]# /bin/bash
[root@VM-16-13-centos /]# echo $$
2015203
[root@VM-16-13-centos /]# echo $x

[root@VM-16-13-centos /]# 

# export 使进程具有导出能力,随时随地可以取出该变量值(环境变量均需加该参数,代表任意程序都可使用该变量)
[root@VM-16-13-centos /]# export x
[root@VM-16-13-centos /]# /bin/bash
[root@VM-16-13-centos /]# echo $x
100

指令块{ [指令1]; [指令2]; ... }

# 同时执行多行指令
[root@VM-16-13-centos /]# { echo "abc"; echo "cba"; }
abc
cba

管道与指令

# 管道左边指令 | 管道右边指令,在回车都会启动一个新的子进程对其进行代码块的解释执行,如:
# 左边的子进程 echo 通过管道交给了右边的 cat 进行输出了,执行完后,子进程自动终止,所以 变量 a 还是为 1
[root@VM-16-13-centos ~]# a=1
[root@VM-16-13-centos ~]# { a=9; echo "aaaaaa"; } | cat
aaaaaa
[root@VM-16-13-centos ~]# echo $a
1

|(管道) 与 $$ 优先级

# $$ 优先级高于 |,所以无法通过此方法获取管道开辟的子进程id号(获取结果还是为父进程id号)
[root@VM-16-13-centos ~]# echo $$
2017870
[root@VM-16-13-centos ~]# echo $$ | cat 
2017870

# 使用 $BASHPID 优先级则低于| 可正常 cat
[root@VM-16-13-centos ~]# echo $$
2019267
[root@VM-16-13-centos ~]# echo $BASHPID | cat
2019324
[root@VM-16-13-centos ~]# echo $$
2020123
[root@VM-16-13-centos ~]# { echo $BASHPID; read x;} | { cat; echo $BASHPID; redy; }
2020412
(此处进程阻塞,等待输入)
# 打开新的窗口查看父进程,2020123进程下有两个 bash 子进程 对应代码块中的左边 bash 与 右边 bash
[root@VM-16-13-centos ~]# ps -fe | grep 2020123
root     2020123 2020122  0 11:54 pts/4    00:00:00 -bash
root     2020412 2020123  0 11:56 pts/4    00:00:00 -bash
root     2020413 2020123  0 11:56 pts/4    00:00:00 -bash
root     2020606 2020464  0 11:57 pts/5    00:00:00 grep --color=auto 2020123

# 进入子进程的 fd 目录,对应pipe 1 为左边输出,右边 0 为输入。即左右两边通过 | 
[root@VM-16-13-centos ~]# cd /proc/2020412/fd
[root@VM-16-13-centos fd]# ll
总用量 0
lrwx------ 1 root root 64 4月  23 12:01 0 -> /dev/pts/4
l-wx------ 1 root root 64 4月  23 12:01 1 -> 'pipe:[36557933]'
lrwx------ 1 root root 64 4月  23 12:01 2 -> /dev/pts/4
lrwx------ 1 root root 64 4月  23 12:01 255 -> /dev/pts/4
lr-x------ 1 root root 64 4月  23 12:01 3 -> /var/lib/sss/mc/passwd
lrwx------ 1 root root 64 4月  23 12:01 4 -> 'socket:[36555355]'
[root@VM-16-13-centos fd]# cd /proc/2020413/fd
[root@VM-16-13-centos fd]# ll
总用量 0
lr-x------ 1 root root 64 4月  23 12:01 0 -> 'pipe:[36557933]'
lrwx------ 1 root root 64 4月  23 12:01 1 -> /dev/pts/4
lrwx------ 1 root root 64 4月  23 12:01 2 -> /dev/pts/4
lrwx------ 1 root root 64 4月  23 12:01 255 -> /dev/pts/4
lr-x------ 1 root root 64 4月  23 12:01 3 -> /var/lib/sss/mc/passwd
lrwx------ 1 root root 64 4月  23 12:01 4 -> 'socket:[36555355]'

# 左边写右边读
[root@VM-16-13-centos fd]# lsof -op 2020412
...
bash    2020412 root    1w  FIFO               0,13    0t0 36557933 pipe
...
[root@VM-16-13-centos fd]# lsof -op 2020413
...
bash    2020413 root    0r  FIFO               0,13    0t0 36557933 pipe
...

PageCache 4K/页(kernel)

kernel -> pagecache -> disk

# dirty 系统配置参数
[root@VM-16-13-centos ~]# sysctl -a | grep dirty
vm.dirty_background_bytes = 0				// 字节数
vm.dirty_background_ratio = 10			// 域值(%)内存达到这个值才会触发开启新的线程数据写入磁盘操作(后台触发,不会阻塞程序,剩余90%继续缓存到pagecache)。可以调优,如:程序写的慢,该参数写的快,内存将不会存在爆满出错情况
vm.dirty_bytes = 0									// 字节数
vm.dirty_expire_centisecs = 3000
vm.dirty_ratio = 30									// 域值(%)略大于background域值,达到该值,内核阻塞专注于执行数据写入磁盘操作,触发非脏页数据lru将老的没用分页淘汰
vm.dirty_writeback_centisecs = 500 // 100:1 s 500/100=5s
vm.dirtytime_expire_seconds = 43200
# 修改其参数
vi /etc/sysctl.conf

JAVA文件IO

FileOuputStream 普通IO

每调用一次 write 当读到数据末尾就进行写到 pagecache 的操作

单位时间内内核与用户态的切换比buffered频繁,浪费了许多系统调用的损耗

BufferedOutputStream 缓冲IO

buffer 比 普通IO快 (应用了缓冲,去解决系统调用的损耗)
因为 少 IO 次数
数据会临时存入jvm,当满了8kb才会调用内核进行pagecache(减少了内核调用)
out -> jvm 8kb byte[]  (full) -> kernel

内存淘汰机制(内存不够用/达到设定的域值):lfu、lru,不会淘汰脏页,仅会淘汰不脏的pagecache,如果除了脏页没有其他可用空间会将脏页数据写入磁盘再淘汰

DMA协处理器

倒腾数据的过程交给DMA,协助CPU完成内存寻址总线到磁盘的缓冲,搬运的过程

NIO

ByteBuffer

ByteBuffer buffer = ByteBuffer.allocate(8192); // 堆上分配
ByteBuffer buffer = ByteBuffer.allocateDirect(1024); // 堆外分配
- pos
	当前读到的位置
- limit
	当调用 flip()「读写交替」 翻转时,limit 指向 pos 指向的位置 并把 pos 初始化到起始位置
	只有 flip() 后 get() 才能让 pos 位置 +1 代表读一个字符
	当调用 campact() pos +1 移动到刚才读到位置的下一个位置,再将 limit 值调到 capacity
- capacity
	ByteBuffer 的最大位置 

RandomAccessFile

随机文件读写
- seek(pos) 
	seek 到指定 pos 位置,可在后进行 读(从该位置读)写(从该位置追加写) 操作
- getChannel()
	拿到 管道 调用 map(FileChannel.MapMode.READ_WRITE, pos, size) 获取 MappedByteBuffer「mmap」堆外 文件映射
	使用该 map.put(byte[]) // 不是系统调用,但数据会到达内核的 pagecache,省略了用户态到系统态的切换
  map 依然会等系统给你刷写到 pagecache 或者 调用 force()「类似flush」 刷写
//测试文件NIO
    public static void testRandomAccessFileWrite() throws Exception {
      
        RandomAccessFile raf = new RandomAccessFile(path, "rw");

        raf.write("hello mashibing\n".getBytes());
        raf.write("hello seanzhou\n".getBytes());
        System.out.println("write------------");
        System.in.read();

        raf.seek(4);
        raf.write("ooxx".getBytes());

        System.out.println("seek---------");
        System.in.read();

        FileChannel rafchannel = raf.getChannel();
        //mmap  堆外  和文件映射的   byte  not  object (映射文件大小 4096 Byte)
        MappedByteBuffer map = rafchannel.map(FileChannel.MapMode.READ_WRITE, 0, 4096);

        map.put("@@@".getBytes());  //不是系统调用  但是数据会到达 内核的pagecache
        //曾经我们是需要out.write()  这样的系统调用,才能让程序的data 进入内核的pagecache
        //曾经必须有用户态内核态切换
        //mmap的内存映射,依然是内核的pagecache体系所约束的!!!
        //换言之,丢数据
        //你可以去github上找一些 其他C程序员写的jni扩展库,使用linux内核的Direct IO
        //直接IO是忽略linux的pagecache
        //是把pagecache  交给了程序自己开辟一个字节数组当作pagecache,动用代码逻辑来维护一致性/dirty。。。一系列复杂问题
        //数据库一般使用 Direct IO

        System.out.println("map--put--------");
        System.in.read();

//        map.force(); //  flush

        raf.seek(0);

        ByteBuffer buffer = ByteBuffer.allocate(8192);
//        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

        int read = rafchannel.read(buffer);   //buffer.put()
        System.out.println(buffer);
        buffer.flip();	// 翻转 limit指向pos,pos初始化,准备读
        System.out.println(buffer);

        for (int i = 0; i < buffer.limit(); i++) {
            Thread.sleep(200);
            System.out.print(((char) buffer.get(i))); //读取每一个字节
        }
    }

IO原理与JAVA RPC_第1张图片

Socket

四元组(使客户端与服务器建立唯一通信):clinet ip & client port + server ip & server port

ServerSocket 对象

建立listen状态

Socket 对象

建立listen状态后进行读写操作

TCP/IP

IO原理与JAVA RPC_第2张图片

linux 传输通信数据包大小 ifconfig -> mtu = mss「数据内容大小」 + ip + package head
窗口机制,解决单个包发送的拥塞问题
	当客户端向服务端发送数据,一次性发送了多个包,服务端(根据队列是否还有位置)回复客户端是否可以继续发,实现拥	塞控制
当客户端与服务端建立通信后,程序还未接受时,客户端向服务端发送的数据会堆积到一个缓冲队列,当缓冲队列堆满后,后续发送的数据会被丢失,当服务端启动接收后缓冲区满后,后续发送的数据全部丢失

查看端口与进程情况:netstat -natp

[root@localhost ~]# netstat -natp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      1106/nginx: master  
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      1061/sshd           
tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN      1240/master         
tcp        0      0 172.16.179.138:22       172.16.179.1:50239      ESTABLISHED 1375/sshd: root@pts 
tcp6       0      0 :::8080                 :::*                    LISTEN      659/java            
tcp6       0      0 :::22                   :::*                    LISTEN      1061/sshd           
tcp6       0      0 ::1:25                  :::*                    LISTEN      1240/master 

Linux操作手册

man [需要查看的指令]
ex:
man ip「对照翻译理解」
IP(4)                    BSD Kernel Interfaces Manual                    IP(4)

NAME
     ip -- Internet Protocol

SYNOPSIS
     #include 
     #include 

     int
     socket(AF_INET, SOCK_RAW, proto);

DESCRIPTION
     IP is the transport layer protocol used by the Internet protocol family.
     Options may be set at the IP level when using higher-level protocols that
     are based on IP (such as TCP and UDP).  It may also be accessed through a
     ``raw socket'' when developing new protocols, or special-purpose applica-
     tions.

     There are several IP-level setsockopt(2) /getsockopt(2) options.
     IP_OPTIONS may be used to provide IP options to be transmitted in the IP
     header of each outgoing packet or to examine the header options on incom-
:

网络IO变化模型

IO原理与JAVA RPC_第3张图片

C10K问题(连接数到达1万)

IO原理与JAVA RPC_第4张图片

为什么192.168.110.100回来的确认包会丢包:

192.168.110.100不在网关192.168.150.0的网关范围所以先跳到nat程序网络192.168.150.2分配随机端口号再到目的地址时,windows发现不是原来发送的目的地址返回的数据,所以匹配不到该返回的目的ip则丢弃。解决办法添加主机条目,配置路由转发。

# 添加主机条目
route add -host [source ip] gw [gateway ip]
source ip 数据跳转到 gateway ip
# 查看路由条目
route -n

模拟10K个链接

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
import java.util.LinkedList;

public class C10Kclient {

    public static void main(String[] args) {
        LinkedList clients = new LinkedList<>();
        InetSocketAddress serverAddr = new InetSocketAddress("192.168.150.11", 9090);

        //端口号的问题:65535
        //  windows
        for (int i = 10000; i < 65000; i++) {
            try {
                SocketChannel client1 = SocketChannel.open();

                SocketChannel client2 = SocketChannel.open();

                /*
                linux中你看到的连接就是:
                client...port: 10508
                client...port: 10508
                 */

                client1.bind(new InetSocketAddress("192.168.150.1", i));
                //  192.168.150.1:10000   192.168.150.11:9090
                client1.connect(serverAddr);
                clients.add(client1);

                client2.bind(new InetSocketAddress("192.168.110.100", i));
                //  192.168.110.100:10000  192.168.150.11:9090
                client2.connect(serverAddr);
                clients.add(client2);

            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        System.out.println("clients "+ clients.size());

        try {
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

BIO为什么慢(同步阻塞)

IO原理与JAVA RPC_第5张图片

accept阻塞等待新的链接,链接完成kernel创建新的线程对内容进行克隆输出

循环等待accept系统调用,待调用后系统clone调用内核创建新的线程「又一次系统调用」(多了用户态到系统态的切换)再到主线程接收其他的链接

解决IOException:Too many open file

# 找到文件描述符的最大值 open files (u)「代表有多少个链接」 下一步将值设置为期望值
[root@VM-16-13-centos ~]# ulimit -a
...
open files                      (-n) 1024
...
# 将文件描述符调大SHn「软件/硬件/文件描述符」
[root@VM-16-13-centos ~]# ulimit -SHn 500000
# 查看调整后的文件描述符,最大支持500000万个链接(最多支持创建500000个文件描述符)
[root@VM-16-13-centos ~]# ulimit -a
...
open files                      (-n) 500000
...

# 查看指定进程的文件描述符清单
lsof -p [pid]

NIO(同步非阻塞)

IO原理与JAVA RPC_第6张图片

文件描述符root用户通常可以超过设定的值,普通用户到达临界值则抛出IOException:Too many open file异常

# 获取Linux OS kernel可以开辟的文件描述符数量
cat /proc/sys/fs/file-max
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.LinkedList;

public class SocketNIO {

    //  what   why  how
    public static void main(String[] args) throws Exception {

        LinkedList<SocketChannel> clients = new LinkedList<>();

        ServerSocketChannel ss = ServerSocketChannel.open();  //服务端开启监听:接受客户端
        ss.bind(new InetSocketAddress(9090));
        ss.configureBlocking(false); //重点  OS  NONBLOCKING!!!  //只让接受客户端  不阻塞

//        ss.setOption(StandardSocketOptions.TCP_NODELAY, false);
//        StandardSocketOptions.TCP_NODELAY
//        StandardSocketOptions.SO_KEEPALIVE
//        StandardSocketOptions.SO_LINGER
//        StandardSocketOptions.SO_RCVBUF
//        StandardSocketOptions.SO_SNDBUF
//        StandardSocketOptions.SO_REUSEADDR


        while (true) {
            //接受客户端的连接
            Thread.sleep(1000);
            SocketChannel client = ss.accept(); //不会阻塞?  -1 NULL
            //accept  调用内核了:1,没有客户端连接进来,返回值?在BIO 的时候一直卡着,但是在NIO ,不卡着,返回-1,NULL
            //如果来客户端的连接,accept 返回的是这个客户端的fd  5,client  object
            //NONBLOCKING 就是代码能往下走了,只不过有不同的情况

            if (client == null) {
             //   System.out.println("null.....");
            } else {
                client.configureBlocking(false); //重点  socket(服务端的listen socket<连接请求三次握手后,往我这里扔,我去通过accept 得到  连接的socket>,连接socket<连接后的数据读写使用的> )
                int port = client.socket().getPort();
                System.out.println("client..port: " + port);
                clients.add(client);
            }

            ByteBuffer buffer = ByteBuffer.allocateDirect(4096);  //可以在堆里   堆外

            //遍历已经链接进来的客户端能不能读写数据
            for (SocketChannel c : clients) {   //串行化!!!!  多线程!!
                int num = c.read(buffer);  // >0  -1  0   //不会阻塞
                if (num > 0) {
                    buffer.flip();
                    byte[] aaa = new byte[buffer.limit()];
                    buffer.get(aaa);

                    String b = new String(aaa);
                    System.out.println(c.socket().getPort() + " : " + b);
                    buffer.clear();
                }
            }
        }
    }

}

NIO效率不及多路复用器(Select)

多路复用器

IO原理与JAVA RPC_第7张图片

同步非阻塞(多路复用器)
- SELECT POSIX「解耦性很强的一种系统调用」所有OS都遵守POSIX组织的规范(如下是各个操作系统针对select「各个操作系统都有」进行向上的优化)
	FD_SIZE有限制 = 1024?
- POLL
	FD_SIZE无限制
- 基于I/O事件的一种通知行为
	- EPOLL「linux」
		相较于POLL多了个链表<红黑数>,能存放fd返回,即不用遍历fd
	- kqueue「unix」

NIO与SELECT/POLL

IO原理与JAVA RPC_第8张图片

拓展中断相关

IO原理与JAVA RPC_第9张图片

EPOLL与SELECT/POLL

IO原理与JAVA RPC_第10张图片

# 获取当前linux系统epoll epoll_ctl 注册支持的最大值
cat /proc/sys/fs/epoll/max_user_watches

四次挥手

IO原理与JAVA RPC_第11张图片

# 解决占用名额问题
[root@VM-16-13-centos ~]# vi /etc/sysctl.conf
[root@VM-16-13-centos ~]# sysctl -p
[root@VM-16-13-centos ~]# sysctl -a | grep reuse
net.ipv4.tcp_tw_reuse = 1

# net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
# net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
// 启用TIME_WAIT状态sockets的快速回收,这个选项不推荐启用。在NAT(Network Address Translation)网络下,会导致大量的TCP连接建立错误。

POLL与EPOLL底层实现

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;

public class SocketMultiplexingSingleThreadv1 {

    private ServerSocketChannel server = null;
    private Selector selector = null;   //linux 多路复用器(select poll    epoll kqueue) nginx  event{}
    int port = 9090;

    public void initServer() {
        try {
            server = ServerSocketChannel.open();
            server.configureBlocking(false);
            server.bind(new InetSocketAddress(port));


            //如果在epoll模型下,open--》  epoll_create -> fd3
            selector = Selector.open();  //  select  poll  *epoll  优先选择:epoll  但是可以 -D修正

            //server 约等于 listen状态的 fd4
            /*
            register
            如果:
            select,poll:jvm里开辟一个数组 fd4 放进去
            epoll:  epoll_ctl(fd3,ADD,fd4,EPOLLIN
             */
            server.register(selector, SelectionKey.OP_ACCEPT);


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

    public void start() {
        initServer();
        System.out.println("服务器启动了。。。。。");
        try {
            while (true) {  //死循环

                Set<SelectionKey> keys = selector.keys();
                System.out.println(keys.size()+"   size");


                //1,调用多路复用器(select,poll  or  epoll  (epoll_wait))
                /*
                select()是啥意思:
                1,select,poll  其实  内核的select(fd4)  poll(fd4)
                2,epoll:  其实 内核的 epoll_wait()
                *, 参数可以带时间:没有时间,0  :  阻塞,有时间设置一个超时
                selector.wakeup()  结果返回0

                懒加载:
                其实再触碰到selector.select()调用的时候触发了epoll_ctl的调用

                 */
                while (selector.select() > 0) {
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();  //返回的有状态的fd集合
                    Iterator<SelectionKey> iter = selectionKeys.iterator();
                    //so,管你啥多路复用器,你呀只能给我状态,我还得一个一个的去处理他们的R/W。同步好辛苦!!!!!!!!
                    //  NIO  自己对着每一个fd调用系统调用,浪费资源,那么你看,这里是不是调用了一次select方法,知道具体的那些可以R/W了?
                    //幕兰,是不是很省力?
                    //我前边可以强调过,socket:  listen   通信 R/W
                    while (iter.hasNext()) {
                        SelectionKey key = iter.next();
                        iter.remove(); //set  不移除会重复循环处理
                        if (key.isAcceptable()) {
                            //看代码的时候,这里是重点,如果要去接受一个新的连接
                            //语义上,accept接受连接且返回新连接的FD对吧?
                            //那新的FD怎么办?
                            //select,poll,因为他们内核没有空间,那么在jvm中保存和前边的fd4那个listen的一起
                            //epoll: 我们希望通过epoll_ctl把新的客户端fd注册到内核空间
                            acceptHandler(key);
                        } else if (key.isReadable()) {
                            readHandler(key);  //连read 还有 write都处理了
                            //在当前线程,这个方法可能会阻塞  ,如果阻塞了十年,其他的IO早就没电了。。。
                            //所以,为什么提出了 IO THREADS
                            //redis  是不是用了epoll,redis是不是有个io threads的概念 ,redis是不是单线程的
                            //tomcat 8,9  异步的处理方式  IO  和   处理上  解耦
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void acceptHandler(SelectionKey key) {
        try {
            ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
            SocketChannel client = ssc.accept(); //来啦,目的是调用accept接受客户端  fd7
            client.configureBlocking(false);

            ByteBuffer buffer = ByteBuffer.allocate(8192);  //前边讲过了

            // 0.0  我类个去
            //你看,调用了register
            /*
            select,poll:jvm里开辟一个数组 fd7 放进去
            epoll:  epoll_ctl(fd3,ADD,fd7,EPOLLIN
             */
            client.register(selector, SelectionKey.OP_READ, buffer);
            System.out.println("-------------------------------------------");
            System.out.println("新客户端:" + client.getRemoteAddress());
            System.out.println("-------------------------------------------");

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

    public void readHandler(SelectionKey key) {
        SocketChannel client = (SocketChannel) key.channel();
        ByteBuffer buffer = (ByteBuffer) key.attachment();
        buffer.clear();
        int read = 0;
        try {
            while (true) {
                read = client.read(buffer);
                if (read > 0) {
                    buffer.flip();
                    while (buffer.hasRemaining()) {
                        client.write(buffer);
                    }
                    buffer.clear();
                } else if (read == 0) {
                    break;
                } else {
                    client.close();
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();

        }
    }

    public static void main(String[] args) {
        SocketMultiplexingSingleThreadv1 service = new SocketMultiplexingSingleThreadv1();
        service.start();
    }
}

IO原理与JAVA RPC_第12张图片

key.cancel()解决重复触发问题

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

public class SocketMultiplexingSingleThreadv2 {

    private ServerSocketChannel server = null;
    private Selector selector = null;   //linux 多路复用器(select poll epoll) nginx  event{}
    int port = 9090;

    public void initServer() {
        try {
            server = ServerSocketChannel.open();
            server.configureBlocking(false);
            server.bind(new InetSocketAddress(port));
            selector = Selector.open();  //  select  poll  *epoll
            server.register(selector, SelectionKey.OP_ACCEPT);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void start() {
        initServer();
        System.out.println("服务器启动了。。。。。");
        try {
            while (true) {
//                Set keys = selector.keys();
//                System.out.println(keys.size()+"   size");
                while (selector.select(50) > 0) {
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();
                    Iterator<SelectionKey> iter = selectionKeys.iterator();
                    while (iter.hasNext()) {
                        SelectionKey key = iter.next();
                        iter.remove();
                        if (key.isAcceptable()) {
                            acceptHandler(key);
                        } else if (key.isReadable()) {
                            key.cancel();  //现在多路复用器里把key  cancel了
//                            System.out.println("in.....");
//                            key.interestOps(key.interestOps() | ~SelectionKey.OP_READ);
                            readHandler(key);//还是阻塞的嘛? 即便以抛出了线程去读取,但是在时差里,这个key的read事件会被重复触发

                        } else if(key.isWritable()){  //我之前没讲过写的事件!!!!!
                            //写事件<--  send-queue  只要是空的,就一定会给你返回可以写的事件,就会回调我们的写方法
                            //你真的要明白:什么时候写?不是依赖send-queue是不是有空间
                            //1,你准备好要写什么了,这是第一步
                            //2,第二步你才关心send-queue是否有空间
                            //3,so,读 read 一开始就要注册,但是write依赖以上关系,什么时候用什么时候注册
                            //4,如果一开始就注册了write的事件,进入死循环,一直调起!!!
                            key.cancel();
//                            key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);
                            writeHandler(key);
                        }
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void writeHandler(SelectionKey key) {
        new Thread(()->{
            System.out.println("write handler...");
            SocketChannel client = (SocketChannel) key.channel();
            ByteBuffer buffer = (ByteBuffer) key.attachment();
            buffer.flip();
            while (buffer.hasRemaining()) {
                try {

                    client.write(buffer);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            buffer.clear();
//            key.cancel();

//            try {
                client.shutdownOutput();
//
                client.close();
//
//            } catch (IOException e) {
//                e.printStackTrace();
//            }
        }).start();

    }

    public void acceptHandler(SelectionKey key) {
        try {
            ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
            SocketChannel client = ssc.accept();
            client.configureBlocking(false);
            ByteBuffer buffer = ByteBuffer.allocate(8192);
            client.register(selector, SelectionKey.OP_READ, buffer);
            System.out.println("-------------------------------------------");
            System.out.println("新客户端:" + client.getRemoteAddress());
            System.out.println("-------------------------------------------");

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

    public void readHandler(SelectionKey key) {
        new Thread(()->{
            System.out.println("read handler.....");
            SocketChannel client = (SocketChannel) key.channel();
            ByteBuffer buffer = (ByteBuffer) key.attachment();
            buffer.clear();
            int read = 0;
            try {
                while (true) {
                    read = client.read(buffer);
                    System.out.println(Thread.currentThread().getName()+ " " + read);
                    if (read > 0) {
//                        key.interestOps(  SelectionKey.OP_READ);

                        client.register(key.selector(),SelectionKey.OP_WRITE,buffer);
                    } else if (read == 0) {

                        break;
                    } else {
                        client.close();
                        break;
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();

    }

    public static void main(String[] args) {
        SocketMultiplexingSingleThreadv2 service = new SocketMultiplexingSingleThreadv2();
        service.start();
    }
}

IO原理与JAVA RPC_第13张图片

频繁的cancel「key.cancel()【相当于epoll_ctl(del会删除<通知区域(红黑树)>中的文件描述符】」与注册会增大内核的开销(用户态与内核态的切换)解决方案使用:

一个线程一个selector,每个selector有注册一组fd,分而治之

IO原理与JAVA RPC_第14张图片

基于netty的rpc框架

IO原理与JAVA RPC_第15张图片

IO原理与JAVA RPC_第16张图片

IO原理与JAVA RPC_第17张图片

IO原理与JAVA RPC_第18张图片

IO原理与JAVA RPC_第19张图片

IO原理与JAVA RPC_第20张图片

并发可达到多大

IO原理与JAVA RPC_第21张图片

IO原理与JAVA RPC_第22张图片

IO原理与JAVA RPC_第23张图片

IO原理与JAVA RPC_第24张图片
自定义RPC代码链接:https://github.com/armin1024/nio

你可能感兴趣的:(IO流,java,linux,centos,rpc)