Netty笔记之九: Netty多种通讯协议支持

上篇文章讲解了自定义通信协议,本章节介绍如何支持多种协议。

会构建一个Server,同时支持Cat,Dog和People通信协议。有二种实现方式:

  • 第一种方式利用了自定义协议,传递消息的时候,对消息的前几位(比如2位)进行自定义的位置(比如AB)解码器解析的时候前二位为AB表示一种协议类型,CD一种协议类型。这种方式没有利用protobuf,而是直接使用Netty自定义协议来解决的方案。
  • 第二种方式使用protobuf来实现,实际上是对消息的定义方式进行规定,因为netty本身,客户端和服务器端建立的是一条TCP连接,一方必须要判断对方发送过来的对象是什么类型。

Protocol Buffers实现netty的多种传输协议

我们知道使用Protocol Buffers首先定义一个.proto文件

定义一个最外层的消息,最外层的消息(MyMessage)包含了所有传递的消息类型,所有的消息类型嵌套在最外层的消息类型中,每次传递都将传递具体消息类型(以最外层消息类型的枚举类型传递)

syntax ="proto2";

package com.zhihao.miao.netty.sixthexample;

option optimize_for = SPEED;
option java_package = "com.zhihao.miao.netty.seventhexample";
option java_outer_classname="MyDataInfo";

message MyMessage {

    enum DataType{
        PeopleType = 1;
        DogType = 2;
        CatType = 3;
    }

    required DataType data_type = 1;

    //oneof的意思:如果有多个可选字段,在某一个时刻只能只有一个值被设置,可以节省内存空间
    oneof dataBody {
        People people = 2;
        Dog dog = 3;
        Cat cat = 4;
    }
}

message People{
    optional string name = 1;
    optional int32 age = 2;
    optional string address = 3;
}

message Dog{
    optional string name = 1;
    optional string age = 2;
}

message Cat{
    optional string name = 1;
    optional string city = 2;
}

使用编译器编译生成代码

protoc --java_out=src/main/java src/protobuf/People.proto

关于proto协议中的Oneof含义,如果有多个可选字段,在某一个时刻只能只有一个值被设置,官方链接,生成MyDataInfo类,类代码太多,这边不贴出了

服务端代码:

package com.zhihao.miao.netty.seventhexample;


import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

public class TestServer {
    public static void main(String[] args) throws Exception{
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup wokerGroup = new NioEventLoopGroup();

        try{
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup,wokerGroup).channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new TestServerInitializer());

            ChannelFuture channelFuture = serverBootstrap.bind(8888).sync();
            channelFuture.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            wokerGroup.shutdownGracefully();
        }
    }
}

服务端初始化链接:

package com.zhihao.miao.netty.seventhexample;


import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;


public class TestServerInitializer extends ChannelInitializer<SocketChannel>{

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();

        pipeline.addLast(new ProtobufVarint32FrameDecoder());
        //使用最外层的消息实例
        pipeline.addLast(new ProtobufDecoder(MyDataInfo.MyMessage.getDefaultInstance()));
        pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
        pipeline.addLast(new ProtobufEncoder());

        pipeline.addLast(new TestServerHandler());
    }
}

其实实现的关键就在于此,使用MyDataInfo.MyMessage实列(MyDataInfo.MyMessage是枚举类型),而我们定义的三种对象刚好就是其枚举对象

自定义的服务端的Handler,根据通道中传递数据的不同dataType值来解析程具体的类型:

package com.zhihao.miao.netty.seventhexample;


import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

public class TestServerHandler extends SimpleChannelInboundHandler<MyDataInfo.MyMessage> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MyDataInfo.MyMessage msg) throws Exception {
        MyDataInfo.MyMessage.DataType dataType = msg.getDataType();

        if(dataType == MyDataInfo.MyMessage.DataType.PeopleType){
            MyDataInfo.People people = msg.getPeople();

            System.out.println(people.getName());
            System.out.println(people.getAge());
            System.out.println(people.getAddress());
        }else if(dataType == MyDataInfo.MyMessage.DataType.DogType){
            MyDataInfo.Dog dog = msg.getDog();

            System.out.println(dog.getName());
            System.out.println(dog.getAge());
        }else if(dataType == MyDataInfo.MyMessage.DataType.CatType){
            MyDataInfo.Cat cat = msg.getCat();

            System.out.println(cat.getName());
            System.out.println(cat.getCity());
        }
    }
}

客户端代码:

package com.zhihao.miao.netty.seventhexample;


import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

public class TestClient {

    public static void main(String[] args) throws Exception{
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();

        try{
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
                    .handler(new TestClientInitializer());

            ChannelFuture channelFuture = bootstrap.connect("localhost",8888).sync();
            channelFuture.channel().closeFuture().sync();

        }finally {
            eventLoopGroup.shutdownGracefully();
        }
    }
}

客户端的初始化链接:

package com.zhihao.miao.netty.seventhexample;


import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;

public class TestClientInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();

        pipeline.addLast(new ProtobufVarint32FrameDecoder());
        //使用最外层的消息实例
        pipeline.addLast(new ProtobufDecoder(MyDataInfo.MyMessage.getDefaultInstance()));
        pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
        pipeline.addLast(new ProtobufEncoder());

        pipeline.addLast(new TestClientHandler());
    }
}

自定义处理器端的handler,随机发送不同协议的数据:

package com.zhihao.miao.netty.seventhexample;


import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

import java.util.Random;

public class TestClientHandler extends SimpleChannelInboundHandler<MyDataInfo.MyMessage> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MyDataInfo.MyMessage msg) throws Exception {

    }

    //客户端像服务器端发送数据
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        int randomInt = new Random().nextInt(3);

        MyDataInfo.MyMessage myMessage = null;

        if(0 == randomInt){
            myMessage = MyDataInfo.MyMessage.newBuilder().
                    setDataType(MyDataInfo.MyMessage.DataType.PeopleType).
                    setPeople(MyDataInfo.People.newBuilder().setName("张三").
                            setAddress("上海").setAge(26).build()).build();
        }else if(1 == randomInt){
            myMessage = MyDataInfo.MyMessage.newBuilder().
                    setDataType(MyDataInfo.MyMessage.DataType.DogType).
                    setDog(MyDataInfo.Dog.newBuilder().setName("旺财")
                            .setAge("2").build()).build();
        }else if(2 == randomInt){
            myMessage = MyDataInfo.MyMessage.newBuilder().
                    setDataType(MyDataInfo.MyMessage.DataType.CatType).
                    setCat(MyDataInfo.Cat.newBuilder().setName("汤姆")
                            .setCity("上海").build()).build();
        }

        ctx.channel().writeAndFlush(myMessage);
    }
}

启动服务器端,然后启动客户端多执行几次,服务器的控制台显示:

七月 05, 2017 10:10:37 下午 io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0xd5f957bd, L:/0:0:0:0:0:0:0:0:8888] READ: [id: 0x82a26e9f, L:/127.0.0.1:8888 - R:/127.0.0.1:51777]
七月 05, 2017 10:10:37 下午 io.netty.handler.logging.LoggingHandler channelReadComplete
信息: [id: 0xd5f957bd, L:/0:0:0:0:0:0:0:0:8888] READ COMPLETE
汤姆
上海
七月 05, 2017 10:11:38 下午 io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0xd5f957bd, L:/0:0:0:0:0:0:0:0:8888] READ: [id: 0x128da3e7, L:/127.0.0.1:8888 - R:/127.0.0.1:52049]
七月 05, 2017 10:11:38 下午 io.netty.handler.logging.LoggingHandler channelReadComplete
信息: [id: 0xd5f957bd, L:/0:0:0:0:0:0:0:0:8888] READ COMPLETE
张三
26
上海
七月 05, 2017 10:11:49 下午 io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0xd5f957bd, L:/0:0:0:0:0:0:0:0:8888] READ: [id: 0xa8220c73, L:/127.0.0.1:8888 - R:/127.0.0.1:52097]
七月 05, 2017 10:11:49 下午 io.netty.handler.logging.LoggingHandler channelReadComplete
信息: [id: 0xd5f957bd, L:/0:0:0:0:0:0:0:0:8888] READ COMPLETE
汤姆
上海
七月 05, 2017 10:11:55 下午 io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0xd5f957bd, L:/0:0:0:0:0:0:0:0:8888] READ: [id: 0x9ac52ec1, L:/127.0.0.1:8888 - R:/127.0.0.1:52125]
七月 05, 2017 10:11:55 下午 io.netty.handler.logging.LoggingHandler channelReadComplete
信息: [id: 0xd5f957bd, L:/0:0:0:0:0:0:0:0:8888] READ COMPLETE
张三
26
上海
七月 05, 2017 10:12:07 下午 io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0xd5f957bd, L:/0:0:0:0:0:0:0:0:8888] READ: [id: 0x797d03b6, L:/127.0.0.1:8888 - R:/127.0.0.1:52178]
七月 05, 2017 10:12:07 下午 io.netty.handler.logging.LoggingHandler channelReadComplete
信息: [id: 0xd5f957bd, L:/0:0:0:0:0:0:0:0:8888] READ COMPLETE
旺财
2

使用netty实现多种传输协议

官网类似的demo,自己写了很长也参考了官网才写出这个demo,对netty的理解又加深了:

三种协议实体类:

Person协议

package com.zhihao.miao.test.day10;

public class Person {

    private String username;

    private int age;
    
    //get set方法

}

Dog协议

package com.zhihao.miao.test.day10;
public class Dog {

    private String name;

    private String age;

    //get set方法
}

Cat协议

package com.zhihao.miao.test.day10;

public class Cat {
    private String name;
    private String city;
  //get set方法
}

服务端:

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

public class MultiServer {

    public static void main(String args[]) throws Exception {

        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        // 指定socket的一些属性
        serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024);
        serverBootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)  // 指定是一个NIO连接通道
                .handler(new LoggingHandler(LogLevel.INFO))
                .childHandler(new ServerChannelInitializer());

        // 绑定对应的端口号,并启动开始监听端口上的连接
        Channel ch = serverBootstrap.bind(8899).sync().channel();


        // 等待关闭,同步端口
        ch.closeFuture().sync();

    }
}

服务器端初始化lInitializer

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;

public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        //解析handler
        pipeline.addLast(new ServlerDecoder());
        pipeline.addLast(new TestServerHandler());
    }
}

服务端解码器Handler,如果解析的位置数据是0则按照 Person协议进行解码,如果传递的位置数据是1,则按照Dog协议进行解码,如果传递的位置数据是2,则按照Cat协议进行解码:

public class ServlerDecoder extends ByteToMessageDecoder {

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {

        int flag = in.readInt();

        if(flag == 0){
            int usernamelength = in.readInt();

            byte[] usernamebys = new byte[usernamelength];
            in.readBytes(usernamebys);

            String username = new String(usernamebys);

            int age = in.readInt();

            Person pserson = new Person();
            pserson.setUsername(username);
            pserson.setAge(age);

            out.add(pserson);


        }
        if(flag ==1){
            int namelength =in.readInt();

            byte[] namebys = new byte[namelength];
            in.readBytes(namebys);

            String name = new String(namebys);

            byte[] agebys = new byte[in.readableBytes()];
            in.readBytes(agebys);

            String age = new String(agebys);

            Dog dog = new Dog();
            dog.setName(name);
            dog.setAge(age);

            out.add(dog);
        }
        if(flag ==2){
            int namelength = in.readInt();

            byte[] nameByte = new byte[namelength];
            in.readBytes(nameByte);

            String name = new String(nameByte);

            byte[] colorbys = new byte[in.readableBytes()];
            in.readBytes(colorbys);

            String color = new String(colorbys);

            Cat cat = new Cat();
            cat.setName(name);
            cat.setColor(color);

            out.add(cat);
        }
    }

自定义服务器端Handler:

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

public class TestServerHandler extends SimpleChannelInboundHandler<Object> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        if(msg instanceof Person){
            System.out.println(((Person) msg).getUsername());
            System.out.println(((Person) msg).getAge());
        }

        if(msg instanceof Dog){
            System.out.println(((Dog) msg).getName());
            System.out.println(((Dog) msg).getAge());
        }

        if(msg instanceof Cat){
            System.out.println(((Cat) msg).getName());
            System.out.println(((Cat) msg).getColor());
        }
    }
}

客户端:

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

public class MultiClient {

    public static void main(String[] args) throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        Bootstrap b = new Bootstrap();
        b.group(group).channel(NioSocketChannel.class).handler(new ClientChannelInitializer());

        // Start the connection attempt.
        Channel ch = b.connect("127.0.0.1", 8899).sync().channel();

        ch.flush();
    }
}

客户端初始化Initializer

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;

import java.util.Random;

public class ClientChannelInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();

        int randomInt = new Random().nextInt(3);

        /**
         * 编码动作,如果随机参数是1,则传输Person协议,如果随机参数是2,则传递Dog协议,
         * 如果随机参数是3,则传递Cat协议
         *
         * Person协议就是传递一个标识位为0,然后将Person对象编码成二进制传输
         * Dog协议传递一个标识位为1,然后将Dog对象编码成二进制进行传输
         * Cat协议传递一个标识为2,然后将Cat对象编码成二进制进行传输
         */
        if(0 == randomInt){
            pipeline.addLast(new PersonEncoder());
            Person person = new Person();
            person.setUsername("zhihao");
            person.setAge(27);
            pipeline.addLast(new TestClientHandler(person));
        }

        if(1 == randomInt){
            pipeline.addLast(new DogEncoder());
            Dog dog = new Dog();
            dog.setName("wangcai");
            dog.setAge("2");
            pipeline.addLast(new TestClientHandler(dog));
        }

        if(2 == randomInt){
            pipeline.addLast(new CatEncoder());
            Cat cat = new Cat();
            cat.setName("maomi");
            cat.setColor("yellow");
            pipeline.addLast(new TestClientHandler(cat));
        }
    }
}

三种自定义编码协议,与服务器端进行对应传输Person数据的时候,在Person数据之前加上标识位置数据0,在Dog数据之前加上标识位置数据1,在Cat数据之前加上标识位置数据2,然后将其与本身的数据一起编码成二进制进行传输。

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

public class PersonEncoder extends MessageToByteEncoder<Person> {

    @Override
    protected void encode(ChannelHandlerContext ctx, Person msg, ByteBuf out) throws Exception {
        String username = msg.getUsername();
        int usernamelength = username.length();
        int age = msg.getAge();

        out.writeInt(0); //标识位
        out.writeInt(usernamelength);
        out.writeBytes(username.getBytes());
        out.writeInt(age);
    }
}

Dog协议编码器

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

public class DogEncoder extends MessageToByteEncoder<Dog> {

    @Override
    protected void encode(ChannelHandlerContext ctx, Dog msg, ByteBuf out) throws Exception {

        String name = msg.getName();
        int namelength = name.length();
        String age = msg.getAge();

        out.writeInt(1); //标识位
        out.writeInt(namelength);
        out.writeBytes(name.getBytes());
        out.writeBytes(age.getBytes());
    }
}

Cat协议编码器:

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

public class CatEncoder extends MessageToByteEncoder<Cat> {

    @Override
    protected void encode(ChannelHandlerContext ctx, Cat msg, ByteBuf out) throws Exception {
        String name = msg.getName();
        int namelength = name.length();
        String color = msg.getColor();

        out.writeInt(2); //标识位
        out.writeInt(namelength);
        out.writeBytes(name.getBytes());
        out.writeBytes(color.getBytes());

    }
}

自定义客户端处理器:

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class TestClientHandler extends ChannelInboundHandlerAdapter {

    private Person person;

    private Cat cat;

    private Dog dog;

    public TestClientHandler(Person person){
        this.person = person;
    }

    public TestClientHandler(Dog dog){
        this.dog = dog;
    }

    public TestClientHandler(Cat cat){
        this.cat =cat;
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        if(person != null){
            ctx.channel().writeAndFlush(person);
        }

        if(dog != null){
            ctx.channel().writeAndFlush(dog);
        }

        if(cat != null){
            ctx.channel().writeAndFlush(cat);
        }
    }
}

启动服务端,再多次启动客户端,服务器控制台打印出不同协议传输的结果

maomi
yellow
十月 15, 2017 4:33:43 下午 io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0x930eab24, L:/0:0:0:0:0:0:0:0:8899] READ: [id: 0xf40f7b07, L:/127.0.0.1:8899 - R:/127.0.0.1:57879]
十月 15, 2017 4:33:43 下午 io.netty.handler.logging.LoggingHandler channelReadComplete
信息: [id: 0x930eab24, L:/0:0:0:0:0:0:0:0:8899] READ COMPLETE
wangcai
2
十月 15, 2017 4:33:48 下午 io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0x930eab24, L:/0:0:0:0:0:0:0:0:8899] READ: [id: 0x3384f158, L:/127.0.0.1:8899 - R:/127.0.0.1:57914]
十月 15, 2017 4:33:48 下午 io.netty.handler.logging.LoggingHandler channelReadComplete
信息: [id: 0x930eab24, L:/0:0:0:0:0:0:0:0:8899] READ COMPLETE
zhihao
27

demo链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容