游戲服務(wù)器中的Netty應(yīng)用以及源碼淺析

最近因?yàn)楣ぷ餍枰卫恚瑢W(xué)習(xí)了一段時(shí)間Netty的源碼,并做了一個(gè)簡單的分享俄删,研究還不是特別深入宏怔,繼續(xù)努力。因?yàn)榉窒硪膊簧婕肮緲I(yè)務(wù)畴椰,所以這里也把這次對(duì)源碼的研究成果分享出來
以下都是在游戲服務(wù)器開發(fā)中針對(duì)Netty使用需要了解知識(shí)點(diǎn)以及相關(guān)優(yōu)化

這次分享主要設(shè)計(jì)以下內(nèi)容

  • Netty線程模型
  • Netty對(duì)TCP相關(guān)參數(shù)的配置和具體含義
  • Netty對(duì)Epoll的封裝
  • Netty的優(yōu)雅關(guān)閉

Netty相關(guān)

一臊诊、Reactor模式和Netty線程模型

客戶端連接數(shù)的限制

  • 內(nèi)存資源
  • CPU資源
  • 端口號(hào)資源
    cat /proc/sys/net/ipv4/ip_local_port_range
    
  • 文件描述符資源
    系統(tǒng)級(jí):當(dāng)前系統(tǒng)可打開的最大數(shù)量,通過 cat /proc/sys/fs/file-max 查看
    用戶級(jí):指定用戶可打開的最大數(shù)量迅矛,通過 cat /etc/security/limits.conf 查看
    進(jìn)程級(jí):單個(gè)進(jìn)程可打開的最大數(shù)量妨猩,通過 cat /proc/sys/fs/nr_open 查看
    
  • 線程資源
    BIO/NIO

1. BIO模型

  • 所有操作都是同步阻塞(accept,read)
  • 客戶端連接數(shù)與服務(wù)器線程數(shù)比例是1:1
BIO線程模型

2. NIO模型

  • 非阻塞IO
  • 通過selector實(shí)現(xiàn)可以一個(gè)線程管理多個(gè)連接
  • 通過selector的事件注冊(cè)(OP_READ/OP_WRITE/OP_CONNECT/OP_ACCEPT)秽褒,處理自己感興趣的事件
  • 客戶端連接數(shù)與服務(wù)器線程數(shù)比例是n:1


    NIO線程模型

3. Reacor模型

①. 單Reacor單線程模型

    所有IO在同一個(gè)NIO線程完成(處理連接壶硅,分派請(qǐng)求,編碼销斟,解碼庐椒,邏輯運(yùn)算,發(fā)送)
  • 優(yōu)點(diǎn)

    • 編碼簡單
    • 不存在共享資源競爭
    • 并發(fā)安全
  • 缺點(diǎn)

    • 單線程處理大量鏈路時(shí)蚂踊,性能無法支撐约谈,不能合理利用多核處理
    • 線程過載后,處理速度變慢犁钟,會(huì)導(dǎo)致消息積壓
    • 一旦線程掛掉棱诱,整個(gè)通信層不可用
      redis使用的就是reactor單進(jìn)程模型,redis由于都是內(nèi)存級(jí)操作涝动,所以使用此模式?jīng)]什么問題
  • reactor單線程模型圖

    reactor單線程模型

  • netty reactor單線程模型圖

    netty reactor單線程模型

// Netty對(duì)應(yīng)實(shí)現(xiàn)方式:創(chuàng)建io線程組是迈勋,boss和worker,使用同一個(gè)線程組醋粟,并且線程數(shù)為1
EventLoopGroup ioGroup = new NioEventLoopGroup(1);
b.group(ioGroup, ioGroup)
        .channel(NioServerSocketChannel.class)
        .childHandler(initializer);
ChannelFuture f = b.bind(portNumner);
cf = f.sync();
f.get();

②. 單Reactor多線程模型

根據(jù)單線程模型靡菇,io處理中最耗時(shí)的編碼,解碼米愿,邏輯運(yùn)算等cpu消耗較多的部分厦凤,可提取出來使用多線程實(shí)現(xiàn),并充分利用多核cpu的優(yōu)勢

  • 優(yōu)點(diǎn)
    • 多線程處理邏輯運(yùn)算育苟,提高多核CPU利用率
  • 缺點(diǎn)
    • 對(duì)于單Reactor來說较鼓,大量鏈接的IO事件處理依然是性能瓶頸
  • reactor多線程模型圖
    reactor多線程模型
  • netty reactor多線程模型圖
    netty reactor多線程模型
// Netty對(duì)應(yīng)實(shí)現(xiàn)方式:創(chuàng)建io線程組是,boss和worker违柏,使用同一個(gè)線程組博烂,并且線程數(shù)為1拓哺,把邏輯運(yùn)算部分投遞到用戶自定義線程處理
EventLoopGroup ioGroup = new NioEventLoopGroup(1);
b.group(ioGroup, ioGroup)
        .channel(NioServerSocketChannel.class)
        .childHandler(initializer);
ChannelFuture f = b.bind(portNumner);
cf = f.sync();
f.get();

③. 主從Reactor多線程模型

根據(jù)多線程模型,可把它的性能瓶頸做進(jìn)一步優(yōu)化脖母,即把reactor由單個(gè)改為reactor線程池,把原來的reactor分為mainReactor和subReactor

  • 優(yōu)點(diǎn)

    • 解決單Reactor的性能瓶頸問題(Netty/Nginx采用這種設(shè)計(jì))
  • reactor主從多線程模型圖

    reactor主從多線程模型

  • netty reactor主從多線程模型圖

    netty reactor主從多線程模型

// Netty對(duì)應(yīng)實(shí)現(xiàn)方式:創(chuàng)建io線程組boss和worker闲孤,boss線程數(shù)為1谆级,work線程數(shù)為cpu*2(一般IO密集可設(shè)置為2倍cpu核數(shù))
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
b.group(bossGroup, workerGroup)
        .channel(NioServerSocketChannel.class)
        .childHandler(initializer);
ChannelFuture f = b.bind(portNumner);
cf = f.sync();
f.get();

④. 部分源碼分析

  1. 創(chuàng)建group實(shí)例
// 1.構(gòu)造參數(shù)不傳或傳0,默認(rèn)取系統(tǒng)參數(shù)配置讼积,沒有參數(shù)配置肥照,取CPU核數(shù)*2
super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);

private static final int DEFAULT_EVENT_LOOP_THREADS;
static {
    DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
            "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
}

// 2.不同版本的JDK會(huì)有不同版本的SelectorProvider實(shí)現(xiàn),Windows下的是WindowsSelectorProvider
public NioEventLoopGroup(int nThreads, Executor executor) {
    //默認(rèn)selector,最終實(shí)現(xiàn)類似:https://github.com/frohoff/jdk8u-jdk/blob/master/src/macosx/classes/sun/nio/ch/DefaultSelectorProvider.java
    //basic flow: 1 java.nio.channels.spi.SelectorProvider 2 META-INF/services 3 default
    this(nThreads, executor, SelectorProvider.provider());
}

// 3.創(chuàng)建nThread個(gè)EventExecutor勤众,并封裝到選擇器chooser舆绎,chooser會(huì)根據(jù)線程數(shù)分別有兩種實(shí)現(xiàn)(GenericEventExecutorChooser和PowerOfTwoEventExecutorChooser,算法不同们颜,但實(shí)現(xiàn)邏輯一樣吕朵,就是均勻的分配線程處理)
EventExecutorChooserFactory.EventExecutorChooser chooser;
children = new EventExecutor[nThreads];
for (int i = 0; i < nThreads; i ++) {
    // ...
    children[i] = newChild(executor, args);
    // ...
}
chooser = chooserFactory.newChooser(children);
  1. 設(shè)置group
// 兩種方式設(shè)置group
// parent和child使用同一個(gè)group,調(diào)用仍然是分別設(shè)置parent和child
@Override
public ServerBootstrap group(EventLoopGroup group) {
    return group(group, group);
}

ServerBootstrap.group(EventLoopGroup parentGroup, EventLoopGroup childGroup){
    // 具體代碼略窥突,可直接參考源碼
    // 里面實(shí)現(xiàn)內(nèi)容是把parentGroup綁定到this.group努溃,把childGroup綁定到this.childGroup
}
  1. Netty啟動(dòng)
// 調(diào)用順序
ServerBootstrap:bind() -> doBind() -> initAndRegister()

private ChannelFuture doBind(final SocketAddress localAddress) {
    final ChannelFuture regFuture = initAndRegister();
    // ...
    doBind0(regFuture, channel, localAddress, promise);
    // ...
}

final ChannelFuture initAndRegister() {
    // 創(chuàng)建ServerSocketChannel
    Channel channel = channelFactory.newChannel();
    // ...
    // 開始register
    ChannelFuture regFuture = config().group().register(channel);
    // register調(diào)用順序
    // next().register(channel) -> (EventLoop) super.next() -> chooser.next()
    // ...
}

由以上源碼可得知,bind只在起服調(diào)用一次阻问,因此bossGroup僅調(diào)用一次regist梧税,也就是僅調(diào)用一次next,因此只有一根線程是有用的称近,其余線程都是廢棄的第队,所以bossGroup線程數(shù)設(shè)置為1即可

// 啟動(dòng)BossGroup線程并綁定本地SocketAddress
private static void doBind0(
        final ChannelFuture regFuture, final Channel channel,
        final SocketAddress localAddress, final ChannelPromise promise) {
    channel.eventLoop().execute(new Runnable() {
        @Override
        public void run() {
            if (regFuture.isSuccess()) {
                channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
            } else {
                promise.setFailure(regFuture.cause());
            }
        }
    });
}
  1. 客戶端連接
// 消息事件讀取
NioEventLoop.run() -> processSelectedKeys() -> ... -> ServerBootstrapAcceptor.channelRead

// ServerBootstrapAcceptor.channelRead處理客戶端連接事件
// 最后一行的childGroup.register的邏輯和上面的代碼調(diào)用處一樣
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    child.pipeline().addLast(childHandler);
    setChannelOptions(child, childOptions, logger);
    setAttributes(child, childAttrs);
    childGroup.register(child)
}

二、select/poll和epoll

1.概念

  • select(時(shí)間復(fù)雜度O(n)):用一個(gè)fd數(shù)組保存所有的socket刨秆,然后通過死循環(huán)遍歷調(diào)用操作系統(tǒng)的select方法找到就緒的fd
while(1) {
  nready = select(list);
  // 用戶層依然要遍歷凳谦,只不過少了很多無效的系統(tǒng)調(diào)用
  for(fd <-- fdlist) {
    if(fd != -1) {
      // 只讀已就緒的文件描述符
      read(fd, buf);
      // 總共只有 nready 個(gè)已就緒描述符,不用過多遍歷
      if(--nready == 0) break;
    }
  }
}
  • poll(時(shí)間復(fù)雜度O(n)):同select坛善,不過把fd數(shù)組換成了fd鏈表晾蜘,去掉了fd最大連接數(shù)(1024個(gè))的數(shù)量限制
  • epoll(時(shí)間復(fù)雜度O(1)):解決了select/poll的幾個(gè)缺陷
    • 調(diào)用需傳入整個(gè)fd數(shù)組或fd鏈表,需要拷貝數(shù)據(jù)到內(nèi)核
    • 內(nèi)核層需要遍歷檢查文件描述符的就緒狀態(tài)
    • 內(nèi)核僅返回可讀文件描述符個(gè)數(shù)眠屎,用戶仍需自己遍歷所有fd
  • epoll是操作系統(tǒng)基于事件關(guān)聯(lián)fd剔交,做了以下優(yōu)化:
    • 內(nèi)核中保存一份文件描述符集合,無需用戶每次都重新傳入改衩,只需告訴內(nèi)核修改的部分即可岖常。(epoll_ctl)
    • 內(nèi)核不再通過輪詢的方式找到就緒的文件描述符,而是通過異步 IO 事件喚醒葫督。(epoll_wait)
    • 內(nèi)核僅會(huì)將有 IO 事件的文件描述符返回給用戶竭鞍,用戶也無需遍歷整個(gè)文件描述符集合板惑。

epoll僅在Linux系統(tǒng)上支持

2.jdk提供selector

// DefaultSelectorProvider.create方法在不同版本的jdk下有不同實(shí)現(xiàn),創(chuàng)建不同Selector

// Windows版本的jdk偎快,其實(shí)現(xiàn)中調(diào)用的是native的poll方法
public static SelectorProvider create() {
    return new WindowsSelectorProvider();
}

// Linux版本的jdk
public static SelectorProvider create() {
    String str = (String)AccessController.doPrivileged(new GetPropertyAction("os.name"));
    if (str.equals("SunOS")) {
        return createProvider("sun.nio.ch.DevPollSelectorProvider");
    }
    if (str.equals("Linux")) {
        return createProvider("sun.nio.ch.EPollSelectorProvider");
    }
    return new PollSelectorProvider();
}

3.Netty提供的Epoll封裝

netty依然基于epoll做了一層封裝冯乘,主要做了以下事情:

(1)java的nio默認(rèn)使用水平觸發(fā),Netty的Epoll默認(rèn)使用邊緣觸發(fā)晒夹,且可配置

  • 邊緣觸發(fā):當(dāng)狀態(tài)變化時(shí)才會(huì)發(fā)生io事件裆馒。
  • 水平觸發(fā):只要滿足條件,就觸發(fā)一個(gè)事件(只要有數(shù)據(jù)沒有被獲取丐怯,內(nèi)核就不斷通知你)

(2)Netty的Epoll提供更多的nio的可配參數(shù)茫打。
(3)調(diào)用c代碼县好,更少gc藤滥,更少synchronized
具體可以參考源碼NioEventLoop.run和EpollEventLoop.run進(jìn)行對(duì)比

4.Netty相關(guān)類圖

  • 線程組類圖

    線程組類圖

  • channel類圖

    channel類圖

5.配置Netty為EpollEventLoop

// 創(chuàng)建指定的EventLoopGroup
bossGroup = new EpollEventLoopGroup(1, new DefaultThreadFactory("BOSS_LOOP"));
workerGroup = new EpollEventLoopGroup(32, new DefaultThreadFactory("IO_LOOP"));
b.group(bossGroup, workerGroup)
        // 指定channel的class
        .channel(EpollServerSocketChannel.class)
        .childHandler(initializer);

// 其中channel(clz)方法是通過class來new一個(gè)反射ServerSocketChannel創(chuàng)建工廠類
public B channel(Class<? extends C> channelClass) {
    if (channelClass == null) {
        throw new NullPointerException("channelClass");
    }
    return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
}

final ChannelFuture initAndRegister() {
    // ...
    Channel channel = channelFactory.newChannel();
    // ...
}

三蠢络、Netty相關(guān)參數(shù)

1.SO_KEEPALIVE

childOption(ChannelOption.SO_KEEPALIVE, true)

TCP鏈路探活

1.SO_REUSEADDR

option(ChannelOption.SO_REUSEADDR, true)

重用處于TIME_WAIT但是未完全關(guān)閉的socket地址,讓端口釋放后可立即被重用效览。默認(rèn)關(guān)閉无切,需要手動(dòng)開啟

2.TCP_NODELAY

childOption(ChannelOption.TCP_NODELAY, true)

IP報(bào)文格式


IP報(bào)文格式

TCP報(bào)文格式


TCP報(bào)文格式
  • 開啟則禁用TCP Negal算法,優(yōu)點(diǎn)低延時(shí)钦铺,缺點(diǎn)在大量小數(shù)據(jù)包的情況下订雾,網(wǎng)絡(luò)利用率低

  • 關(guān)閉則開啟TCP Negal算法,優(yōu)點(diǎn)提高網(wǎng)絡(luò)利用率(數(shù)據(jù)緩存到一定量才發(fā)送)矛洞,缺點(diǎn)延時(shí)高

  • Negal算法

    1. 如果包長度達(dá)到MSS(maximum segment size最大分段長度)洼哎,則允許發(fā)送;
    2. 如果該包含有FIN沼本,則允許發(fā)送噩峦;
    3. 設(shè)置了TCP_NODELAY選項(xiàng),則允許發(fā)送抽兆;
    4. 未設(shè)置TCP_CORK選項(xiàng)(是否阻塞不完整報(bào)文)時(shí)识补,若所有發(fā)出去的小數(shù)據(jù)包(包長度小于MSS)均被確認(rèn),則允許發(fā)送辫红;
    5. 上述條件都未滿足凭涂,但發(fā)生了超時(shí)(一般為200ms),則立即發(fā)送贴妻。
  • MSS計(jì)算規(guī)則
    MSS的值是在TCP三次握手建立連接的過程中切油,經(jīng)通信雙方協(xié)商確定的
    802.3標(biāo)準(zhǔn)里,規(guī)定了一個(gè)以太幀的數(shù)據(jù)部分(Payload)的最大長度是1500個(gè)字節(jié)(MTU)

    MSS = MTU - IP首部 - TCP首部
    
    以太網(wǎng)環(huán)境下:
      MTU = 1500字節(jié)
    IP首部 = 32*5/4 = 160bit = 20字節(jié)
    TCP首部 = 32*5/4 = 160bit = 20字節(jié)
    
    最終得出MSS = 1460字節(jié)
    

結(jié)論:因?yàn)橛螒蚍?wù)器的實(shí)時(shí)性要求名惩,在網(wǎng)絡(luò)帶寬足夠的情況下澎胡,建議開啟TCP_NODELAY,關(guān)閉Negal算法,帶寬可以浪費(fèi)攻谁,響應(yīng)必須及時(shí)

注意:需要客戶端服務(wù)器均關(guān)閉Negal算法稚伍,否則仍然會(huì)有延遲發(fā)送,影響傳輸速度

3.SO_BACKLOG

option(ChannelOption.SO_BACKLOG, 100)

操作系統(tǒng)內(nèi)核中維護(hù)的兩個(gè)隊(duì)列

  • syns queue:保存syn到達(dá)戚宦,但沒完成三次握手的半連接
cat /proc/sys/net/ipv4/tcp_max_syn_backlog
  • accpet queue:保存完成三次握手个曙,內(nèi)核等待accept調(diào)用的連接
cat /proc/sys/net/core/somaxconn

netty對(duì)于backlog的默認(rèn)值設(shè)置在NetUtil類253行

 SOMAXCONN = AccessController.doPrivileged(new PrivilegedAction<Integer>() {
    @Override
    public Integer run() {
        // 1.設(shè)置默認(rèn)值
        int somaxconn = PlatformDependent.isWindows() ? 200 : 128;
        
        File file = new File("/proc/sys/net/core/somaxconn");
        if (file.exists()) {
            // 2.文件存在,讀取操作系統(tǒng)配置
            in = new BufferedReader(new FileReader(file));
            somaxconn = Integer.parseInt(in.readLine());
        } else {
            // 3.文件不存在受楼,從各個(gè)參數(shù)中讀取
            if (SystemPropertyUtil.getBoolean("io.netty.net.somaxconn.trySysctl", false)) {
                tmp = sysctlGetInt("kern.ipc.somaxconn");
                if (tmp == null) {
                    tmp = sysctlGetInt("kern.ipc.soacceptqueue");
                    if (tmp != null) {
                        somaxconn = tmp;
                    }
                } else {
                    somaxconn = tmp;
                }
            }
        }
    }
}

結(jié)論:Linux下/proc/sys/net/core/somaxconn一定存在困檩,所以backlog一定取得它的值,我參考prod機(jī)器的參數(shù)配置的65535那槽,也就是不設(shè)置backlog的情況下,服務(wù)器運(yùn)行緩存65535個(gè)全連接

4.ALLOCATOR和RCVBUF_ALLOCATOR

ByteBuf

默認(rèn)分配ByteBuffAllocator賦值如下:
ByteBufUtil.java

static {
    //以io.netty.allocator.type為準(zhǔn)等舔,沒有的話骚灸,安卓平臺(tái)用非池化實(shí)現(xiàn),其他用池化實(shí)現(xiàn)
    String allocType = SystemPropertyUtil.get(
            "io.netty.allocator.type", PlatformDependent.isAndroid() ? "unpooled" : "pooled");
    allocType = allocType.toLowerCase(Locale.US).trim();

    ByteBufAllocator alloc;
    if ("unpooled".equals(allocType)) {
        alloc = UnpooledByteBufAllocator.DEFAULT;
        logger.debug("-Dio.netty.allocator.type: {}", allocType);
    } else if ("pooled".equals(allocType)) {
        alloc = PooledByteBufAllocator.DEFAULT;
        logger.debug("-Dio.netty.allocator.type: {}", allocType);
    } else {
        //io.netty.allocator.type設(shè)置的不是"unpooled"或者"pooled"慌植,就用池化實(shí)現(xiàn)甚牲。
        alloc = PooledByteBufAllocator.DEFAULT;
        logger.debug("-Dio.netty.allocator.type: pooled (unknown: {})", allocType);
    }
    DEFAULT_ALLOCATOR = alloc;
}

RCVBUF_ALLOCATOR默認(rèn)AdaptiveRecvByteBufAllocator

public class DefaultChannelConfig implements ChannelConfig {

    // ...

    public DefaultChannelConfig(Channel channel) {
        this(channel, new AdaptiveRecvByteBufAllocator());
    }
    // ...
}

四、Netty關(guān)閉

/**
 * Shortcut method for {@link #shutdownGracefully(long, long, TimeUnit)} with sensible default values.
 *
 * @return the {@link #terminationFuture()}
 */
Future<?> shutdownGracefully();

/**
 * Signals this executor that the caller wants the executor to be shut down.  Once this method is called,
 * {@link #isShuttingDown()} starts to return {@code true}, and the executor prepares to shut itself down.
 * Unlike {@link #shutdown()}, graceful shutdown ensures that no tasks are submitted for <i>'the quiet period'</i>
 * (usually a couple seconds) before it shuts itself down.  If a task is submitted during the quiet period,
 * it is guaranteed to be accepted and the quiet period will start over.
 *
 * @param quietPeriod the quiet period as described in the documentation
                     靜默期:在此期間蝶柿,仍然可以提交任務(wù)
 * @param timeout     the maximum amount of time to wait until the executor is {@linkplain #shutdown()}
 *                    regardless if a task was submitted during the quiet period
                     超時(shí)時(shí)間:等待所有任務(wù)執(zhí)行完的最大時(shí)間
 * @param unit        the unit of {@code quietPeriod} and {@code timeout}
 *
 * @return the {@link #terminationFuture()}
 */
Future<?> shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit);

// 抽象類中的實(shí)現(xiàn)
static final long DEFAULT_SHUTDOWN_QUIET_PERIOD = 2;
static final long DEFAULT_SHUTDOWN_TIMEOUT = 15;
@Override
public Future<?> shutdownGracefully() {
    return shutdownGracefully(DEFAULT_SHUTDOWN_QUIET_PERIOD, DEFAULT_SHUTDOWN_TIMEOUT, TimeUnit.SECONDS);
}
  1. 把NIO線程的狀態(tài)位設(shè)置成ST_SHUTTING_DOWN狀態(tài)丈钙,不再處理新的消息(不允許再對(duì)外發(fā)送消息);
  2. 退出前的預(yù)處理操作:把發(fā)送隊(duì)列中尚未發(fā)送或者正在發(fā)送的消息發(fā)送完交汤、把已經(jīng)到期或者在退出超時(shí)之前到期的定時(shí)任務(wù)執(zhí)行完成雏赦、把用戶注冊(cè)到NIO線程的退出Hook任務(wù)執(zhí)行完成;
  3. 資源的釋放操作:所有Channel的釋放芙扎、多路復(fù)用器的去注冊(cè)和關(guān)閉星岗、所有隊(duì)列和定時(shí)任務(wù)的清空取消,最后是NIO線程的退出戒洼。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末俏橘,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子圈浇,更是在濱河造成了極大的恐慌寥掐,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件磷蜀,死亡現(xiàn)場離奇詭異召耘,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蠕搜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門怎茫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事轨蛤∶巯埽” “怎么了?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵祥山,是天一觀的道長圃验。 經(jīng)常有香客問我,道長缝呕,這世上最難降的妖魔是什么澳窑? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮供常,結(jié)果婚禮上摊聋,老公的妹妹穿的比我還像新娘。我一直安慰自己栈暇,他們只是感情好麻裁,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著源祈,像睡著了一般煎源。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上香缺,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天手销,我揣著相機(jī)與錄音,去河邊找鬼图张。 笑死锋拖,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的祸轮。 我是一名探鬼主播姑隅,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼倔撞!你這毒婦竟也來了讲仰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤痪蝇,失蹤者是張志新(化名)和其女友劉穎鄙陡,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體躏啰,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡趁矾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了给僵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片毫捣。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡详拙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蔓同,到底是詐尸還是另有隱情饶辙,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布斑粱,位于F島的核電站弃揽,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏则北。R本人自食惡果不足惜矿微,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望尚揣。 院中可真熱鬧涌矢,春花似錦、人聲如沸快骗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽滨巴。三九已至,卻和暖如春俺叭,著一層夾襖步出監(jiān)牢的瞬間恭取,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來泰國打工熄守, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蜈垮,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓裕照,卻偏偏與公主長得像攒发,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子晋南,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

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

  • 本文主要從IO模型惠猿、Netty邏輯架構(gòu)、Netty各組件的設(shè)計(jì)與應(yīng)用為主導(dǎo)负间,由簡-難-細(xì)展開來介紹偶妖,其中包括IO模...
    RalapHao閱讀 1,882評(píng)論 2 14
  • Java IO知識(shí)圖譜 1 OSI七層網(wǎng)絡(luò)模型 上下層之間遵循的約定叫做"接口",同層之間遵循的約定叫做"協(xié)議"....
    allen鍋閱讀 920評(píng)論 0 1
  • 何為Reactor線程模型政溃? Reactor模式是事件驅(qū)動(dòng)的趾访,有一個(gè)或多個(gè)并發(fā)輸入源,有一個(gè)Service Han...
    未名枯草閱讀 3,491評(píng)論 2 11
  • 前奏 https://tech.meituan.com/2016/11/04/nio.html 綜述 netty通...
    jiangmo閱讀 5,844評(píng)論 0 13
  • 什么是nio? nio 本質(zhì)就是多路復(fù)用董虱,內(nèi)核告訴你那些句柄可讀可寫 jdk的selector就是對(duì)操作系統(tǒng)的io...
    張帆demo閱讀 2,565評(píng)論 0 9