在上一篇应用依赖不同的Netty版本引发的错误文章中, 在WEB-INF/lib目录下存在多个版本的Netty, 应用加载jar包的顺序颠倒, 导致应用启动报错. 而重点就在于加载jar包顺序.
本篇文章, 我们简单验证下, 在Linux系统中, 读取目录下的文件, 它的顺序是怎样的.
我们还是拿上篇文章使用的实验环境
当然我这里增加了2个文件, read_dir.c 和 read_dir.py . C程序和Python程序, 接下来会使用这2个程序分别验证下在Linux系统中, 读取目录下文件的顺序是怎样的.
代码还是上一篇文章中的Example.java代码, 提前先类加载SingleThreadEventExecutor类, 然后在启动一个Netty服务端.
编译&运行Example.java代码
代码语言:javascript复制// Example.java
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
public class Example {
public static void main(String[] args) throws Exception {
Class.forName("io.netty.util.concurrent.SingleThreadEventExecutor");
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(8);
EventLoopGroup businessGroup = new NioEventLoopGroup(8);
ServerBootstrap serverBootstrap = new ServerBootstrap();
try {
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) {
ChannelPipeline channelPipeline = ch.pipeline();
}
});
ChannelFuture channelFuture = serverBootstrap.bind("127.0.0.1", 8080).sync();
channelFuture.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
如下图, 程序正常运行
既然能正常运行, 表明程序是先加载的netty-all-4.1.43.Final.jar.
使用ll命令查看目录下的文件时, 如下图
netty-all-4.1.43.Final.jar的确排在netty-common-4.1.29.Final.jar前面, 那是因为它默认是按照字母排序的, 这个依据在man手册中可以查找到, 如下
man ls
描述中已经说明, ls默认按照字母次序排序文件
如果使用ll -r 查看目录内容, 又会看到另一种排序结果, 如下图, netty-common-4.1.29.Final.jar排在netty-all-4.1.43.Final.jar前面了
那么我们平时写的Java程序, 在加载某个目录下的Jar文件时, 比如Tomcat读取WEB-INF/lib目录下的jar文件时, 先读取哪个后读取哪个总该有个顺序吧, 它的底层不会像ls命令排序那样的, 那么它的底层是依据什么呢? 往下看
这里写了一个C程序(read_dir.c), 它的功能就是读取当前目录下的文件
代码语言:javascript复制// read_dir.c
#define _GNU_SOURCE
#include <dirent.h> /* Defines DT_* constants */
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#define handle_error(msg)
do { perror(msg); exit(EXIT_FAILURE); } while (0)
struct linux_dirent {
unsigned long d_ino;
off_t d_off;
unsigned short d_reclen;
char d_name[];
};
#define BUF_SIZE 1024
int
main(int argc, char *argv[])
{
int fd;
long nread;
char buf[BUF_SIZE];
struct linux_dirent *d;
char d_type;
fd = open(argc > 1 ? argv[1] : ".", O_RDONLY | O_DIRECTORY);
if (fd == -1)
handle_error("open");
for (;;) {
// 调用系统函数getdents()
nread = syscall(SYS_getdents, fd, buf, BUF_SIZE);
if (nread == -1)
handle_error("getdents");
if (nread == 0)
break;
printf("--------------- nread=%ld ---------------n", nread);
printf("inode# file type d_reclen d_off d_namen");
for (long bpos = 0; bpos < nread;) {
d = (struct linux_dirent *) (buf bpos);
printf("ld ", d->d_ino);
d_type = *(buf bpos d->d_reclen - 1);
printf("%-10s ", (d_type == DT_REG) ? "regular" :
(d_type == DT_DIR) ? "directory" :
(d_type == DT_FIFO) ? "FIFO" :
(d_type == DT_SOCK) ? "socket" :
(d_type == DT_LNK) ? "symlink" :
(d_type == DT_BLK) ? "block dev" :
(d_type == DT_CHR) ? "char dev" : "???");
printf("M jd %sn", d->d_reclen,
(intmax_t) d->d_off, d->d_name);
bpos = d->d_reclen;
}
}
exit(EXIT_SUCCESS);
}
编译这个C程序
gcc -o read_dir read_dir.c
执行生成的read_dir, 输出结果如下
【第一列inode】在Linux文件系统中, 标识一个文件并不是根据它的名称, 而是根据这个inode值. 不同文件的inode值不同.
比如在tmp目录下有三个文件,分别是-not,1.txt,2.txt
如果要删除1.txt , 可以使用rm 1.txt把文件删除掉. 但是当使用rm -not删除-not文件时, 它就会提示错误
rm 命令会把中划线-后面当成命令参数, 而rm没有-n的命令参数,因此报错了. 这个时候我们就可以使用inode值删除文件.查看文件的inode值
-not文件的inode值是5018049, 于是使用rm `find . -inum 5018049`;命令就可以删除-not文件.
【第二列file type】表示文件类型
【第三列d_reclen】表示文件长度
【第四列d_off】可以理解成这个文件在目录中的偏移, 具体含义在它的结构体中有说明, 上面输出的每行记录都使用下面的结构体表示
【第五列d_name】表示文件名
而我们读取目录下的文件就是根据d_off值排序的.
我们再次使用Python语言程序验证下
代码语言:javascript复制#! /usr/bin/env python
import os
r = os.listdir(".")
print(r)
输出的结果与C程序一致, 毕竟Python语言底层也是调用相同的C库函数.
我们在另一台Linux机器上验证下
程序启动报错了, 表明程序先加载的netty-common-4.1.29.Final.jar, 我们使用C程序验证下.
如上图, 根据d_off排序, netty-common-4.1.29.Final.jar的确排在netty-all-4.1.43.Final.jar前面, 所以应用启动报错了.
我们简单写个Java程序读取当前目录, 看一下Java程序读取的目录中的文件列表是否与上面一致.
代码语言:javascript复制import java.io.File;
public class ReadDir {
public static void main(String[] args) throws Exception {
File file = new File("/root/2022-3-16");
String[] files = file.list();
for (String fileName : files) {
System.out.println(fileName);
}
}
}
编译&运行
输出的结果与C程序一样, 毕竟Java程序底层也需要依赖C库函数.
对应的底层系统调用API是getdents
可以参考
https://man7.org/linux/man-pages/man2/getdents.2.html
或man getdents 查看下相关的介绍
附录: 本篇文章的实验代码地址
https://github.com/infuq/infuq-others/tree/master/Lab/2022-3-16