flutter udp multicast 組播

寫這篇文章的緣由:在Android原生端鸣峭,iOS原生端,實現(xiàn)udp 組播相信很多小伙伴都會的酥艳,但在flutter 端實現(xiàn)這個對我們來說還是一個未知之?dāng)?shù)吧摊溶;

前期是想著去網(wǎng)上找一個第三方庫 或者找一個第三方的插件 來完成任務(wù);結(jié)果經(jīng)過幾天的調(diào)研下來充石,最終都是Android手機上可以正常使用莫换,但在iOS手機上都不能正常使用,報以下的錯誤

Unhandled Exception: OS Error: Can't assign requested address, errno = 49

沿著這個錯誤跟蹤下去 ,網(wǎng)上討論的結(jié)果貌似是Dart SDK層報出來的錯誤拉岁,很多熱心人事也與Dart團(tuán)隊在網(wǎng)上溝通坷剧,希望他們能解決此bug;不過大半年過去了喊暖,這個問題依舊存在惫企。

看來指望Dart團(tuán)隊解決此事,有點遙遙無期的感覺陵叽,怎么辦呢狞尔,我們不能因為這個bug ,就停止不前了吧巩掺;最終決定自己來造輪子----寫udp組播插件偏序;

寫flutter插件入門,在這里就不多說了锌半,小伙伴們自己去探索一下吧。下面就開始今天真正的代碼環(huán)節(jié)了

import 'dart:async';

import 'package:flutter/services.dart';

class Pluginudp {
  static const MethodChannel _channel =
  const MethodChannel('pluginudp');

  static Future<void> startScan(String ipString) async {
    await _channel.invokeMethod('startScan',{"data":ipString});
  }

  static Future<void> stopScan() async {
    await _channel.invokeMethod('stopScan');
  }

  static Future<void> onlyToReceive() async {
    await _channel.invokeMethod('onlyToReceive');
  }
}

上面的代碼是flutter層寇漫, 定義了3個方法刊殉,分別為

開始udp掃描--startScan

停止udp掃描--stopScan

只接收數(shù)據(jù)--onlyToReceive

iOS層的核心代碼

#import "PluginudpPlugin.h"
#import "ScanX3UdpSocket.h"
@interface PluginudpPlugin ()<FlutterStreamHandler,ScanX3UdpSocketDelegate>
@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, strong) FlutterEventSink eventSink;
@property (nonatomic, copy) NSString *ipString;

@end
@implementation PluginudpPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
  FlutterMethodChannel* channel = [FlutterMethodChannel
      methodChannelWithName:@"pluginudp"
            binaryMessenger:[registrar messenger]];
  PluginudpPlugin* instance = [[PluginudpPlugin alloc] init];
  [registrar addMethodCallDelegate:instance channel:channel];


    FlutterEventChannel *eventChannel = [FlutterEventChannel
                                eventChannelWithName:@"FlutterUdpEvent"
                                binaryMessenger:[registrar messenger]];
    [eventChannel setStreamHandler:instance];
}
#pragma mark - FlutterStreamHandler methods

- (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments
                                       eventSink:(nonnull FlutterEventSink)sink {
    _eventSink = sink;
    return nil;
}

- (FlutterError* _Nullable)onCancelWithArguments:(id _Nullable)arguments {
    _eventSink = nil;
    return nil;
}
- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
  if ([@"getPlatformVersion" isEqualToString:call.method]) {
    result([@"iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]);
  }else if ([@"startScan" isEqualToString:call.method]) {
      NSDictionary *dict = call.arguments;
      NSString *ipString = [dict valueForKey:@"data"];
      _ipString = ipString;
      [self startToScan];
  }else if ([@"stopScan" isEqualToString:call.method]) {
      [self stopToScan];
  }else if ([@"onlyToReceive" isEqualToString:call.method]) {
         [self onlyToReceive];
     }
  else {
    result(FlutterMethodNotImplemented);
  }
}

#pragma mark - ScanX3UdpSocketDelegate methods

- (void)udpSocket:(GCDAsyncUdpSocket *_Nullable)sock didReceiveData:(NSData *_Nullable)data
      fromAddress:(NSData *_Nullable)address
withFilterContext:(nullable id)filterContext {
    NSString *host = [GCDAsyncUdpSocket hostFromAddress:address];

    NSString *str = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
    NSString *str2 = [self trimIndicatorWithString:str];
    NSError *error;
    NSMutableDictionary<NSString*,id> *dict = [NSJSONSerialization JSONObjectWithData:[str2 dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:&error];
    [dict setValue:host forKey:@"host"];
    NSLog(@"udpSocket didReceiveData dict = %@",dict);
    if (!error) {
        if(_eventSink){
            _eventSink(dict);
        }

    }
}

- (void)startToScan {
    [self stopTimer];
    [self startTimer];
}
- (void)stopToScan {
    [self stopTimer];
    [ScanX3UdpSocket shareScanX3UdpSocket].delegate = nil;
    [[ScanX3UdpSocket shareScanX3UdpSocket] cutOffUdpSocket];

}
- (void)onlyToReceive {
       [ScanX3UdpSocket shareScanX3UdpSocket].delegate = self;
       [[ScanX3UdpSocket shareScanX3UdpSocket] toReceiveData];
}

#pragma mark - private method

- (void)startTimer {
    [self timer];
}

- (void)stopTimer {
    if (self.timer) {
         [self.timer invalidate];
         self.timer = nil;
     }
}

- (NSString *)trimIndicatorWithString: (NSString *)string {
    string = [string stringByReplacingOccurrencesOfString:@"\x02" withString:@""];
    string = [string stringByReplacingOccurrencesOfString:@"\x03" withString:@""];
    return string;
}

- (void)scanX3 {
    [ScanX3UdpSocket shareScanX3UdpSocket].delegate = self;
    [[ScanX3UdpSocket shareScanX3UdpSocket] startScanX3WithIpString:self.ipString];
}

#pragma mark - getter/setter
-(NSTimer *)timer{
    if (!_timer) {
        _timer = [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(scanX3) userInfo:nil repeats:YES];
    }
    return _timer;
}

@end

安卓層的核心代碼

import android.annotation.SuppressLint;
import android.os.Handler;
import android.os.Message;

import androidx.annotation.NonNull;

import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry.Registrar;

/** PluginudpPlugin */
public class PluginudpPlugin implements FlutterPlugin, MethodCallHandler {
  /// The MethodChannel that will the communication between Flutter and native Android
  ///
  /// This local reference serves to register the plugin with the Flutter Engine and unregister it
  /// when the Flutter Engine is detached from the Activity
  private MulticastSocket multicastSocket = null;
  private InetAddress group;
  private boolean isReceiving = true;/// when the Flutter Engine is detached from the Activity


  private MethodChannel channel;
  private static EventChannel.EventSink mEventSink = null;
  @Override
  public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
    channel = new MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "pluginudp");
    channel.setMethodCallHandler(this);

    final EventChannel eventChannel = new EventChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "FlutterUdpEvent");
    eventChannel.setStreamHandler(new EventChannel.StreamHandler() {
      @Override
      public void onListen(Object o, EventChannel.EventSink eventSink) {

        mEventSink = eventSink;
      }

      @Override
      public void onCancel(Object o) {
        mEventSink = null;
      }
    });
  }

  // This static function is optional and equivalent to onAttachedToEngine. It supports the old
  // pre-Flutter-1.12 Android projects. You are encouraged to continue supporting
  // plugin registration via this function while apps migrate to use the new Android APIs
  // post-flutter-1.12 via https://flutter.dev/go/android-project-migration.
  //
  // It is encouraged to share logic between onAttachedToEngine and registerWith to keep
  // them functionally equivalent. Only one of onAttachedToEngine or registerWith will be called
  // depending on the user's project. onAttachedToEngine or registerWith must both be defined
  // in the same class.
  public static void registerWith(Registrar registrar) {
    final MethodChannel channel = new MethodChannel(registrar.messenger(), "pluginudp");
    channel.setMethodCallHandler(new PluginudpPlugin());
  }

  @Override
  public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
    if (call.method.equals("getPlatformVersion")) {
      result.success("Android " + android.os.Build.VERSION.RELEASE);
    }else if(call.method.equals("startScan")) {
      Map<String,String> m = (Map<String, String>) call.arguments;
      String ipString = m.get("data");
      System.out.println("ipString = " + ipString);

      stop();
      initMultiCaseSocket();
      startScan(ipString);
      receive();

    }else if(call.method.equals("stopScan")) {
      stop();

    } else {
      result.notImplemented();
    }
  }

  @Override
  public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
    channel.setMethodCallHandler(null);
  }

  public void  initMultiCaseSocket() {
    try {
      group = InetAddress.getByName("231.0.1.1");
    } catch (UnknownHostException e) {
      e.printStackTrace();
    }
    try {
      multicastSocket = new MulticastSocket(11555);
    } catch (IOException e) {
      e.printStackTrace();
    }
    try {
      multicastSocket.joinGroup(group);// 加入該組
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  public  void startScan(String ipString) {
    isReceiving = true;
    new Thread(new Runnable() {
      public void run() {
        int I=0;
        System.out.println("run send");
        while(true && i<10 && isReceiving){

          String sendMessage = null;
          try {
            sendMessage = UDPServer("192.168.0.111","11555");
            System.out.println(sendMessage);
          } catch (JSONException e) {
            e.printStackTrace();
          }
          I++;
          System.out.println("i ="+ i);

          DatagramPacket datagramPacket = new DatagramPacket(
                  sendMessage.getBytes(), sendMessage.length(), group,
                  21555);
          try {
            multicastSocket.send(datagramPacket);
          } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
          }


          try {
            Thread.sleep(3000);
          } catch (InterruptedException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
          }
        }
        System.out.println("send over");
      }
    }).start();
  }

  public void receive() {
    new Thread(new Runnable() {
      public void run() {
        int I=0;
        byte[] arb = new byte[200];
        System.out.println("run receive");
        while (true && i<20 && isReceiving) {
          DatagramPacket datagramPacket = new DatagramPacket(arb,
                  arb.length);

          try {
            multicastSocket.receive(datagramPacket);
            Thread.sleep(1000);

          } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
          }
          I++;
          String host = datagramPacket.getAddress().getHostName();
          System.out.println("host====++++++" + host);

          System.out.println("re i ="+ i);
          System.out.println(arb.length);
          Map<String, Object> m = changeByteToMap(arb);
          m.put("host",host);
          Message msg = new Message();
          msg.obj = m;
          msg.what = 1000;
          handler.sendMessage(msg);
        }
      }
    }).start();

  }
  @SuppressLint("HandlerLeak")
  Handler handler = new Handler() {
    public void handleMessage(Message msg) {
      Map<String, Object> mm = (Map<String, Object>) msg.obj;
      switch (msg.what){
        case 1000:
          System.out.println("=====>"+ mm);
          if (mEventSink != null) {
            System.out.println("send before");
            mEventSink.success(mm);
            System.out.println("send after");
          }
          break;
      }
    }
  };

  public void stop() {
    isReceiving = false;
    if (multicastSocket != null) {
      try {
        multicastSocket.leaveGroup(group);
        multicastSocket.close();
      } catch (IOException e) {
        e.printStackTrace();
      }

    }
  }

  byte[] startFlag = {0x02};
  byte[] endFlag = {0x03};
  String start = new String(startFlag);
  String end = new String(endFlag);

  public String UDPServer(String ip, String port) throws JSONException {
    JSONObject mapBuf = new JSONObject();
    mapBuf.put("type", "scan");
    mapBuf.put("ip", ip);
    mapBuf.put("port", port);
    String value = start + mapBuf.toString() + end;
    return value;
  }

  public Map<String,Object> UDPServer(String data) throws JSONException {
    Map<String,Object> map=new HashMap<String,Object>();
    JSONTokener jsonParser = new JSONTokener(data);
    JSONObject jsonObj = (JSONObject) jsonParser.nextValue();
    map.put("result",jsonObj.optBoolean("result"));
    map.put("description",jsonObj.optString("description"));

    Map<String,Object> tempMap=new HashMap<String,Object>();
    JSONObject job=jsonObj.getJSONObject("response");
    tempMap.put("mac", job.optString("mac"));
    tempMap.put("name", job.optString("name"));
    tempMap.put("version",job.optString("version"));
    tempMap.put("netMode",job.optString("netMode"));
    map.put("response",tempMap);

    return map;
  }

  //將byte[]轉(zhuǎn)換成map
  public Map<String,Object> changeByteToMap(byte[] bytes) {
    ArrayList<Byte> tempList = new ArrayList<>();
    boolean isStart = false;
    boolean isEnd = false;
    for (int i = 0; i < bytes.length; i++) {
      Byte item = bytes[I];
      if (item == 0x02) {
        isStart = true;
        continue;
      }
      if (isStart) {

        if (item == 0x03) {
          isEnd = true;
          break;
        }
        if (!isEnd) {
          tempList.add(item);
        }
      }
    }
    System.out.println("------>" + tempList.size());
    Map<String, Object> retmap = null;
    try {
      retmap = UDPServer(new String(listTobyte(tempList)));
    } catch (JSONException e) {
      e.printStackTrace();
    }
    return retmap;
  }


  private byte[] listTobyte(List<Byte> list) {
    if (list == null || list.size() < 0)
      return null;
    byte[] bytes = new byte[list.size()];
    int i = 0;
    Iterator<Byte> iterator = list.iterator();
    while (iterator.hasNext()) {
      bytes[i] = iterator.next();
      I++;
    }
    return bytes;
  }
}

安卓里面

開始udp掃描--startScan

停止udp掃描--stopScan

只接收數(shù)據(jù)--onlyToReceive (這個暫時沒有實現(xiàn))

插件寫好了,在需要使用的地方使用起來

import 'dart:convert';
import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:res/constant.dart';
import 'package:utils/api_helper.dart';
import 'package:pluginudp/pluginudp.dart';
import 'utils/socket_client.dart';
import 'utils/http.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final EventChannel _eventChannel = EventChannel('FlutterUdpEvent');
  String _host = "";
  @override
  void initState() {
    super.initState();
    Pluginudp udp = Pluginudp();
    Future.delayed(Duration(seconds: 2), () {
      _eventChannel.receiveBroadcastStream().listen(eventListener,
          onError: (Object obj) => throw obj as PlatformException);
    });
  }

  void onData(dynamic data) {
    print("data:$data");
    Uint8List responseData = data;
    List<int> responseDataList = responseData.toList();
    responseDataList.removeAt(0);
    responseDataList.removeAt(responseDataList.length - 1);
    String str = String.fromCharCodes(responseDataList);
    Map map = jsonDecode(str);
    print(map);
  }

  void eventListener(dynamic data) {
    if (data.runtimeType.toString() == '(dynamic) => Null') {
      return;
    }
    Map<dynamic, dynamic> event = data as Map<dynamic, dynamic>;
    print("object=${event['host']}");
    print("object = $event");
    String mac = event["response"]['MAC'];
    if (mac.contains("EF71A")) {
      _host = event['host'];
      print("destines host = $_host");
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Container(
          width: double.infinity,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.center,
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text('Udp scan'),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  RaisedButton(
                      child: Text("start"),
                      onPressed: () {
                        Pluginudp.startScan("192.168.2.245");
                      }),
                  SizedBox(
                    width: 1,
                  ),
                  RaisedButton(
                      child: Text("stop"),
                      onPressed: () {
                        Pluginudp.stopScan();
                      }),
                  SizedBox(
                    width: 1,
                  ),
                  RaisedButton(
                      child: Text("onlyToReceive"),
                      onPressed: () {
                        Pluginudp.onlyToReceive();
                      })
                ],

              ),
              SizedBox(
                height: 50,
              ),
              Column(
                children: <Widget>[
                  RaisedButton(child: Text("建立Tcp鏈接"), onPressed: _connectTcp),
                  RaisedButton(
                      child: Text("配置服務(wù)器信息"), onPressed: _setServerInfo),
                  RaisedButton(
                      child: Text("配置設(shè)備owner"), onPressed: _setOwnerInfo),
                  RaisedButton(
                      child: Text("配置設(shè)備時區(qū)與位置"),
                      onPressed: _setTimeZoneLocation),
                  RaisedButton(
                      child: Text("啟動設(shè)備向服務(wù)器注冊"), onPressed: _startRegister),
                ],
              )
            ],
          ),
        ),
      ),
    );
  }

  _connectTcp() {
    OwonSocketClient.getInstance()
        .startConnect(_host, OwonConstant.tcpReceivePort)
        .then((value) {
      OwonSocketClient.getInstance().addListener(onData);
    });
  }

  _setServerInfo() {
//  Map map = Map();
//  map["username"] = "xiaoming";
//  map["password"] = "123456";
//
//  Map apiDict = Map();
//  apiDict["type"] = "login";
//  apiDict["argument"] = map;
//  apiDict["sequence"] = 10000;

    Map<String, dynamic> apiDict = Map();
    apiDict["host"] = "192.168.0.111";
    apiDict["sslport"] = 8883;
    apiDict["port"] = 1883;

    Map p = ApiHelper.apiHelper(
        type: "wizardConfig", command: "serverCfg", argument: apiDict);

    print("p = $p");

    SocketClient.getInstance().sendData(p);
  }

  _setOwnerInfo() {
    Map apiDict = Map();
    apiDict["Owner"] = "86-18559697013";
    apiDict["agentid"] = "Test";
    Map p = ApiHelper.apiHelper(
        type: "wizardConfig", command: "DevOwnerCfg", argument: apiDict);

    print("p = $p");

    SocketClient.getInstance().sendData(p);
  }

  _setTimeZoneLocation() {
    Map apiDict = Map();
    apiDict["Area"] = "Asia/Shanghai";
    apiDict["Addid"] = "11100000";
    Map p = ApiHelper.apiHelper(
        type: "wizardConfig", command: "DevLocationCfg", argument: apiDict);

    print("p = $p");

    SocketClient.getInstance().sendData(p);
  }

  _startRegister() {

    Map p = ApiHelper.apiHelper(
        type: "wizardConfig", command: "startRegister");

    print("p = $p");

    SocketClient.getInstance().sendData(p);
  }
}
image1.png

注意使用真機來調(diào)試州胳,點擊start按鈕记焊,控制臺打印信息

image2.png

寫在結(jié)尾

代碼里面會有一部分是與我司的業(yè)務(wù)掛鉤,大家可以忽略栓撞,這也是發(fā)布的第一篇文章遍膜,可能有諸多考慮不周的地方,請見諒瓤湘,只想在flutter udp 組播 這個地方做一個拋磚引玉的作用.需要源碼的話瓢颅,請在zz7516018@163.com上留言吧
http://note.youdao.com/s/DnDiFfGT

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市弛说,隨后出現(xiàn)的幾起案子挽懦,更是在濱河造成了極大的恐慌,老刑警劉巖木人,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件信柿,死亡現(xiàn)場離奇詭異,居然都是意外死亡醒第,警方通過查閱死者的電腦和手機渔嚷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來稠曼,“玉大人形病,你說我怎么就攤上這事。” “怎么了窒朋?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵搀罢,是天一觀的道長。 經(jīng)常有香客問我侥猩,道長榔至,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任欺劳,我火速辦了婚禮唧取,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘划提。我一直安慰自己枫弟,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布鹏往。 她就那樣靜靜地躺著淡诗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪伊履。 梳的紋絲不亂的頭發(fā)上韩容,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機與錄音唐瀑,去河邊找鬼邑时。 笑死块蚌,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播专执,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼毡证,長吁一口氣:“原來是場噩夢啊……” “哼愧口!你這毒婦竟也來了局义?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤当窗,失蹤者是張志新(化名)和其女友劉穎形真,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體超全,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡咆霜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了嘶朱。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蛾坯。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖疏遏,靈堂內(nèi)的尸體忽然破棺而出脉课,到底是詐尸還是另有隱情救军,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布倘零,位于F島的核電站唱遭,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏呈驶。R本人自食惡果不足惜拷泽,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望袖瞻。 院中可真熱鬧司致,春花似錦、人聲如沸聋迎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽霉晕。三九已至庭再,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間牺堰,已是汗流浹背拄轻。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留萌焰,地道東北人哺眯。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓谷浅,卻偏偏與公主長得像扒俯,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子一疯,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,486評論 2 348