Linux系統(tǒng)讀取目錄內(nèi)文件順序

昨晚服務(wù)在發(fā)布的時(shí)候, 出現(xiàn)如下異常

在這里插入圖片描述

Caused by: java.lang.NoSuchMethodError: ...

Dubbo在暴露服務(wù)的時(shí)候, 需要啟動(dòng)Netty服務(wù)端, 在啟動(dòng)服務(wù)端的過程中, 根據(jù)Reactor模型, 它需要?jiǎng)?chuàng)建IO線程.會(huì)涉及到使用Netty中的io.netty.util.concurrent.SingleThreadEventExecutor類, 根據(jù)錯(cuò)誤提示, 在構(gòu)造SingleThreadEventExecutor對(duì)象的時(shí)候, 找不到符合的構(gòu)造器方法.

查看下應(yīng)用依賴的Netty包

在這里插入圖片描述

雖然有2個(gè)3.x版本的Netty包, 但是3.x版本的Netty包名都是 org.jboss.netty, 4.x版本的包名都是io.netty, 根據(jù)錯(cuò)誤提示的包名, 因此排除3.x版本的嫌疑.

剩下的就是4.1.43版本和4.1.29版本, 版本不一致, 很可能就是因?yàn)檫@個(gè)原因造成的.

io.netty.util.concurrent.SingleThreadEventExecutor 這個(gè)類出現(xiàn)在兩個(gè)包里.netty-all-4.1.43.Final.jar 和 netty-common-4.1.29.Final.jar 包中都有SingleThreadEventExecutor 類.

寫了一個(gè)簡(jiǎn)單的測(cè)試案例


在這里插入圖片描述
// Example.java

package com.infuq;

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;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateHandler;

public class Example {

    public static void main(String[] args) throws Exception {
        // 加載SingleThreadEventExecutor類
        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)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                    .childHandler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel ch) {
                            ChannelPipeline channelPipeline = ch.pipeline();

                            channelPipeline.addLast(new StringEncoder());
                            channelPipeline.addLast(new StringDecoder());
                            channelPipeline.addLast("idleEventHandler", new IdleStateHandler(0, 10, 0));
                            channelPipeline.addAfter("idleEventHandler","loggingHandler",new LoggingHandler(LogLevel.INFO));
                        }
                    });

            ChannelFuture channelFuture = serverBootstrap.bind("127.0.0.1", 8080).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

}

以上代碼會(huì)使用Netty創(chuàng)建一個(gè)服務(wù)端, 也是在模擬Dubbo使用Netty創(chuàng)建服務(wù)端, 本質(zhì)是一樣的. 只是在我的代碼中, 使用

Class.forName("io.netty.util.concurrent.SingleThreadEventExecutor");

手動(dòng)提前加載SingleThreadEventExecutor類.

編譯程序

javac -d . -classpath ".:./netty-all-4.1.43.Final.jar:./netty-common-4.1.29.Final.jar" Example.java

在這里我們手動(dòng)指定了jar包的加載順序

運(yùn)行程序

java -cp ".:./netty-all-4.1.43.Final.jar:./netty-common-4.1.29.Final.jar" com.infuq.Example
在這里插入圖片描述

服務(wù)正常啟動(dòng)了...

接下來改變一下運(yùn)行時(shí)加載Jar包的順序, 讓類加載器在加載SingleThreadEventExecutor類的時(shí)候, 先從netty-common-4.1.29.Final.jar包中查找加載.

在這里插入圖片描述

出現(xiàn)了與文章一開始一樣的錯(cuò)誤. 因?yàn)樘崆凹虞d了netty-common-4.1.29.Final.jar版本中的SingleThreadEventExecutor類, 而接下來創(chuàng)建Netty服務(wù)端的時(shí)候, 在構(gòu)造SingleThreadEventExecutor對(duì)象的時(shí)候, 傳入的參數(shù)格式是按照netty-all-4.1.43.Final.jar包中的SingleThreadEventExecutor類傳參. netty-common-4.1.29.Final.jar 和 netty-all-4.1.43.Final.jar 中關(guān)于SingleThreadEventExecutor類構(gòu)造器的確不同, 如下

在這里插入圖片描述

netty-all-4.1.43.Final.jar 包中的SingleThreadEventExecutor類構(gòu)造器比netty-common-4.1.29.Final.jar包中的SingleThreadEventExecutor類構(gòu)造器多一個(gè), 而且就是錯(cuò)誤中提示的`缺失`那個(gè)構(gòu)造器.

使用mvn dependency:tree > tmp.txt命令導(dǎo)出來依賴關(guān)系, 查看了下, netty-common-4.1.29.Final.jar 和 netty-all-4.1.43.Final.jar 這兩個(gè)包分別是被架構(gòu)組A和團(tuán)隊(duì)B使用, 而作為使用方的我們, 需要手動(dòng)解決版本不一樣的問題, 否則就會(huì)出現(xiàn)許多莫名其妙錯(cuò)誤.

在這之前應(yīng)用沒有出現(xiàn)過類似錯(cuò)誤, 所以感覺很奇怪, 為什么最近突然出現(xiàn)了這樣的錯(cuò)誤, 原來是我們最近代碼中接入了團(tuán)隊(duì)B的一個(gè)能力框架, 它的底層間接依賴了Netty, 只是版本與我們代碼中依賴架構(gòu)組A使用的Netty版本不一致引起的.

世界大同, 版本一致是原則.

應(yīng)用加載jar包的順序顛倒, 導(dǎo)致應(yīng)用啟動(dòng)報(bào)錯(cuò). 而重點(diǎn)就在于加載jar包順序.

接下來我們簡(jiǎn)單驗(yàn)證下, 在Linux系統(tǒng)中, 讀取目錄下的文件, 它的順序是怎樣的.

當(dāng)我們使用ll 命令查看目錄下文件的時(shí)候, 默認(rèn)是按照字母排序的, 這個(gè)依據(jù)在man手冊(cè)中可以查找到, 如下
man ls

在這里插入圖片描述

描述中已經(jīng)說明, ls默認(rèn)按照字母次序排序文件

如果使用ll -r 查看目錄內(nèi)容, 又會(huì)看到另一種排序結(jié)果, 如下圖, netty-common-4.1.29.Final.jar排在netty-all-4.1.43.Final.jar前面了


在這里插入圖片描述

那么我們平時(shí)寫的Java程序, 在加載某個(gè)目錄下的Jar文件時(shí), 比如Tomcat讀取WEB-INF/lib目錄下的jar文件時(shí), 先讀取哪個(gè)后讀取哪個(gè)總該有個(gè)順序吧, 它的底層不會(huì)像ls命令排序那樣的, 那么它的底層是依據(jù)什么呢? 往下看

這里寫了一個(gè)C程序(read_dir.c), 它的功能就是讀取當(dāng)前目錄下的文件


// 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 (;;) {
       // 調(diào)用系統(tǒng)函數(shù)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_name\n");
       for (long bpos = 0; bpos < nread;) {
           d = (struct linux_dirent *) (buf + bpos);
           printf("%18ld  ", 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("%4d %10jd       %s\n", d->d_reclen,
                   (intmax_t) d->d_off, d->d_name);
           bpos += d->d_reclen;
       }
   }

   exit(EXIT_SUCCESS);
}

編譯這個(gè)C程序

gcc -o read_dir read_dir.c

在這里插入圖片描述

執(zhí)行生成的read_dir, 輸出結(jié)果如下

在這里插入圖片描述

【第一列inode】在Linux文件系統(tǒng)中, 標(biāo)識(shí)一個(gè)文件并不是根據(jù)它的名稱, 而是根據(jù)這個(gè)inode值. 不同文件的inode值不同.

比如在tmp目錄下有三個(gè)文件,分別是-not,1.txt,2.txt

在這里插入圖片描述

如果要?jiǎng)h除1.txt , 可以使用rm 1.txt把文件刪除掉. 但是當(dāng)使用rm -not刪除-not文件時(shí), 它就會(huì)提示錯(cuò)誤

在這里插入圖片描述

rm 命令會(huì)把中劃線-后面當(dāng)成命令參數(shù), 而rm沒有-n的命令參數(shù),因此報(bào)錯(cuò)了. 這個(gè)時(shí)候我們就可以使用inode值刪除文件.查看文件的inode值


在這里插入圖片描述

-not文件的inode值是317158, 于是使用rm `find . -inum 317158`;命令就可以刪除-not文件.

在這里插入圖片描述

【第二列file type】表示文件類型

【第三列d_reclen】表示文件長(zhǎng)度

【第四列d_off】可以理解成這個(gè)文件在目錄中的偏移, 具體含義在它的結(jié)構(gòu)體中有說明, 上面輸出的每行記錄都使用下面的結(jié)構(gòu)體表示

在這里插入圖片描述
【第五列d_name】表示文件名



<font >而我們讀取目錄下的文件就是根據(jù)<font color=red size=5>d_off</font>值排序的.</font>

我們?cè)俅问褂肞ython語言程序驗(yàn)證下


#! /usr/bin/env python

import os

r = os.listdir(".")
print(r)
在這里插入圖片描述

輸出的結(jié)果與C程序一致, 畢竟Python語言底層也是調(diào)用相同的C庫函數(shù).

對(duì)應(yīng)的底層系統(tǒng)調(diào)用API是getdents, 可以參考 https://man7.org/linux/man-pages/man2/getdents.2.html 或man getdents 查看下相關(guān)的介紹.

附錄: 本篇文章的實(shí)驗(yàn)代碼地址
https://github.com/infuq/infuq-others/tree/master/Lab/2022-3-16

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末婉烟,一起剝皮案震驚了整個(gè)濱河市险绘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌膀曾,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,542評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件眯勾,死亡現(xiàn)場(chǎng)離奇詭異添寺,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)孟辑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門哎甲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人饲嗽,你說我怎么就攤上這事炭玫。” “怎么了貌虾?”我有些...
    開封第一講書人閱讀 158,021評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵吞加,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我尽狠,道長(zhǎng)衔憨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,682評(píng)論 1 284
  • 正文 為了忘掉前任晚唇,我火速辦了婚禮巫财,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘哩陕。我一直安慰自己平项,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評(píng)論 6 386
  • 文/花漫 我一把揭開白布悍及。 她就那樣靜靜地躺著闽瓢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪心赶。 梳的紋絲不亂的頭發(fā)上扣讼,一...
    開封第一講書人閱讀 49,985評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音缨叫,去河邊找鬼椭符。 笑死,一個(gè)胖子當(dāng)著我的面吹牛耻姥,可吹牛的內(nèi)容都是我干的销钝。 我是一名探鬼主播,決...
    沈念sama閱讀 39,107評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼琐簇,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼蒸健!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起婉商,我...
    開封第一講書人閱讀 37,845評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤似忧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后丈秩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盯捌,經(jīng)...
    沈念sama閱讀 44,299評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評(píng)論 2 327
  • 正文 我和宋清朗相戀三年蘑秽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了饺著。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片滤祖。...
    茶點(diǎn)故事閱讀 38,747評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖瓶籽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情埂材,我是刑警寧澤塑顺,帶...
    沈念sama閱讀 34,441評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站俏险,受9級(jí)特大地震影響严拒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜竖独,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評(píng)論 3 317
  • 文/蒙蒙 一裤唠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧莹痢,春花似錦种蘸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至坦辟,卻和暖如春刊侯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背锉走。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評(píng)論 1 267
  • 我被黑心中介騙來泰國打工滨彻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人挪蹭。 一個(gè)月前我還...
    沈念sama閱讀 46,545評(píng)論 2 362
  • 正文 我出身青樓亭饵,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親嚣潜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子冬骚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容