framework初探之在指定channel上開啟softAP

需求:需要測(cè)試wifi模塊的5g吞吐量铺坞。需要開啟5g wifiap

實(shí)現(xiàn)過程:

1.可行性

首先看下WifiManager開啟ap函數(shù)的說明:

    /**
     * Start AccessPoint mode with the specified
     * configuration. If the radio is already running in
     * AP mode, update the new configuration
     * Note that starting in access point mode disables station
     * mode operation
     * @param wifiConfig SSID, security and channel details as
     *        part of WifiConfiguration
     * @return {@code true} if the operation succeeds, {@code false} otherwise
     *
     * @hide Dont open up yet
     */
    public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
        try {
            mService.setWifiApEnabled(wifiConfig, enabled);
            return true;
        } catch (RemoteException e) {
            return false;
        }
    }

參數(shù)wifiConfig可指定 SSID, security 和 channel
然而蜡歹,WifiConfiguration這個(gè)類中不包含channel或者freq這樣的屬性踱卵,那是如何實(shí)現(xiàn)配置的呢?

2. 跟源碼

跟一下framework的實(shí)現(xiàn):
目前環(huán)境是Rockchip3229 android 5.1 虑啤,代碼路徑與aosp有些差別作媚,不過總體區(qū)別不大拂募。
mService.setWifiApEnabled(wifiConfig, enabled);
這里通過Binder直接調(diào)用到WifiService的同名函數(shù)

2.1. frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiServiceImpl.java

    /**
     * see {@link android.net.wifi.WifiManager#setWifiApEnabled(WifiConfiguration, boolean)}
     * @param wifiConfig SSID, security and channel details as
     *        part of WifiConfiguration
     * @param enabled true to enable and false to disable
     */
    public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
        ...
        // null wifiConfig is a meaningful input for CMD_SET_AP
        if (wifiConfig == null || wifiConfig.isValid()) {
            mWifiController.obtainMessage(CMD_SET_AP, enabled ? 1 : 0, 0, wifiConfig).sendToTarget();
        } else {
            Slog.e(TAG, "Invalid WifiConfiguration");
        }
    }

這里可以看到庭猩,使用wifiController發(fā)送msg去下發(fā)指令

2.2. frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiController.java

WifiController繼承了StateMachine類窟她,上面的obtainMessage函數(shù)是StateMachine類的函數(shù),參數(shù)列表是(msgWhat, arg1, arg2, obj)
不清楚有哪些State或各個(gè)State下對(duì)命令的策略是什么的情況下蔼水,直接搜索case CMD_SET_AP震糖,
發(fā)現(xiàn)在ApStaDisabledState下的對(duì)該命令的處理為

class ApStaDisabledState extends State {
              ...
              if (msg.arg1 == 1) {
                 mWifiStateMachine.setHostApRunning((WifiConfiguration) msg.obj,
                         true);
                 transitionTo(mApEnabledState);
              }

可以看到他會(huì)執(zhí)行開啟softap,并轉(zhuǎn)到ApEnabledState趴腋,檢查一下這個(gè)State的操作

    class ApEnabledState extends State {
        @Override
        public boolean processMessage(Message msg) {
            switch (msg.what) {
                ...
                case CMD_SET_AP:
                    if (msg.arg1 == 0) {
                        mWifiStateMachine.setHostApRunning(null, false);
                        transitionTo(mApStaDisabledState);
                    }
                    break;
                default:
                    return NOT_HANDLED;
            }
            return HANDLED;
        }
    }

可以看到吊说,這個(gè)State沒有復(fù)寫enter()函數(shù),只處理了關(guān)閉softAP的命令

2.3 frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiStateMachine.java

    public void setHostApRunning(WifiConfiguration wifiConfig, boolean enable) {
        if (enable) {
            sendMessage(CMD_START_AP, wifiConfig);
        } else {
            sendMessage(CMD_STOP_AP);
        }
    }

WifiStateMachine同樣繼承StateMachine优炬,所以直接找處理CMD_START_AP指令的代碼颁井,搜索case CMD_START_AP:

class InitialState extends State {
        ...
        @Override
        public boolean processMessage(Message message) {
                ...
                case CMD_START_AP:
                    if (mWifiNative.loadDriver()) {
                        setWifiApState(WIFI_AP_STATE_ENABLING);
                        transitionTo(mSoftApStartingState);
                    } else {
                        loge("Failed to load driver for softap");
                    }
                ...
        }
        ...
}

這里的執(zhí)行了加載wifi驅(qū)動(dòng)并轉(zhuǎn)入softapStartingState,沒有其他操作蠢护,那具體配置softap的操作應(yīng)該是在該State的enter函數(shù)中處理的
我們來看SoftApStartingState這個(gè)類的enter函數(shù)

    class SoftApStartingState extends State {
        @Override
        public void enter() {
            final Message message = getCurrentMessage();
            if (message.what == CMD_START_AP) {
                final WifiConfiguration config = (WifiConfiguration) message.obj;

                if (config == null) {
                    mWifiApConfigChannel.sendMessage(CMD_REQUEST_AP_CONFIG);
                } else {
                    mWifiApConfigChannel.sendMessage(CMD_SET_AP_CONFIG, config);
                    startSoftApWithConfig(config);
                }
            } else {
                throw new RuntimeException("Illegal transition to SoftApStartingState: " + message);
            }
        }
    ...
    }

這里我們傳入的config是不為空的雅宾,那么核心邏輯應(yīng)該是在startSoftApWithConfig(config)這個(gè)函數(shù)中

    private void startSoftApWithConfig(final WifiConfiguration config) {
        // Start hostapd on a separate thread
        new Thread(new Runnable() {
            public void run() {
                try {
                    mNwService.startAccessPoint(config, mInterfaceName);
                } catch (Exception e) {
                    loge("Exception in softap start " + e);
                    try {
                        mNwService.stopAccessPoint(mInterfaceName);
                        mNwService.startAccessPoint(config, mInterfaceName);
                    } catch (Exception e1) {
                        loge("Exception in softap re-start " + e1);
                        sendMessage(CMD_START_AP_FAILURE);
                        return;
                    }
                }
                ...
            }
        }).start();
    }

這里調(diào)用mNwService.startAccessPoint(config, mInterfaceName)并進(jìn)行了一次出錯(cuò)的重試,這個(gè)mInterfaceName是初始化WifiStateMachine時(shí)賦值的葵硕,我們?cè)赪ifiServiceImpl類的構(gòu)造函數(shù)中可以看到:

public WifiServiceImpl(Context context) {
        mContext = context;
        mInterfaceName =  SystemProperties.get("wifi.interface", "wlan0");
        mTrafficPoller = new WifiTrafficPoller(mContext, mInterfaceName);
        ...
    }

這里執(zhí)行g(shù)etprop眉抬,默認(rèn)值為wlan0,實(shí)際也就是wlan0
現(xiàn)在我們繼續(xù)跟到mNwService這個(gè)對(duì)象懈凹,這里也是通過Binder的叫做INetworkManagementService的接口調(diào)用的蜀变,在frameworks目錄find一下文件NetworkManagementService.java

前方高能!U号;杷铡!M!M葑ā0袈印!Fㄉ獭Q毯堋!@狻雾袱!

2.4 frameworks/base/services/core/java/com/android/server/NetworkManagementService.java

    @Override
    public void startAccessPoint(
            WifiConfiguration wifiConfig, String wlanIface) {
        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
        try {
            wifiFirmwareReload(wlanIface, "AP");
            if (wifiConfig == null) {
                mConnector.execute("softap", "set", wlanIface);
            } else {
                mConnector.execute("softap", "set", wlanIface, wifiConfig.SSID,
                                   "broadcast", "6", getSecurityType(wifiConfig),
                                   new SensitiveArg(wifiConfig.preSharedKey));
            }
            mConnector.execute("softap", "startap");
        } catch (NativeDaemonConnectorException e) {
            throw e.rethrowAsParcelableException();
        }
    }

這里可以看到,這個(gè)execute函數(shù)里面官还,終于用到了我們傳入的WifiConfiguration芹橡,這個(gè)參數(shù)經(jīng)歷了長(zhǎng)途跋涉,終于被解析了望伦!
但是林说,這里只解析了ssid和preSharedKey煎殷,也就是wifiap的用戶名密碼,沒有留地方給我們需要的channel或者freq腿箩。我們繼續(xù)往下看這個(gè)函數(shù)的形參豪直。

2.5 frameworks/base/services/core/java/com/android/server/NativeDaemonConnector.java

在這個(gè)類中搜到了三個(gè)execute()函數(shù),根據(jù)上面的類型珠移,只能是下面這個(gè)

    /**
     * Issue the given command to the native daemon and return a single expected
     * response. Any arguments must be separated from base command so they can
     * be properly escaped.
     */
    public NativeDaemonEvent execute(String cmd, Object... args)
            throws NativeDaemonConnectorException {
        final NativeDaemonEvent[] events = executeForList(cmd, args);
        if (events.length != 1) {
            throw new NativeDaemonConnectorException(
                    "Expected exactly one response, but received " + events.length);
        }
        return events[0];
    }

函數(shù)說明中說道弓乙,將指令傳給native daemon,參數(shù)和基礎(chǔ)命令必須分開钧惧,繼續(xù)跟調(diào)用

    public NativeDaemonEvent[] executeForList(String cmd, Object... args)
            throws NativeDaemonConnectorException {
            return execute(DEFAULT_TIMEOUT, cmd, args);
    }

最終跟到了boss唆貌,

    public NativeDaemonEvent[] execute(int timeout, String cmd, Object... args)
            throws NativeDaemonConnectorException {
        ...
        final StringBuilder rawBuilder = new StringBuilder();
        final StringBuilder logBuilder = new StringBuilder();
        final int sequenceNumber = mSequenceNumber.incrementAndGet();

        makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args);

        final String rawCmd = rawBuilder.toString();
        final String logCmd = logBuilder.toString();

        synchronized (mDaemonLock) {
            if (mOutputStream == null) {
                throw new NativeDaemonConnectorException("missing output stream");
            } else {
                try {
                    mOutputStream.write(rawCmd.getBytes(StandardCharsets.UTF_8));
                } catch (IOException e) {
                    throw new NativeDaemonConnectorException("problem sending command", e);
                }
            }
        }
        ...
    }

核心部分是通過這個(gè)outputStream將指令寫出去,寫到哪里呢垢乙,我們看下這個(gè)流對(duì)象如何被賦值的:

private void listenToSocket() throws IOException {
        LocalSocket socket = null;
        try {
            socket = new LocalSocket();
            LocalSocketAddress address = determineSocketAddress();
            socket.connect(address);
            InputStream inputStream = socket.getInputStream();
            synchronized (mDaemonLock) {
                mOutputStream = socket.getOutputStream();
            }

            ...
        } catch (IOException ex) {
            loge("Communications error: " + ex);
            throw ex;
        } finally {

           ...
        }
    }

很明顯這個(gè)是通過determineSocketAddress()這個(gè)函數(shù)建立的unixSocket锨咙,并以客戶端的形式連接上這個(gè)socket, 看下bind到了哪個(gè)地址上:

    private LocalSocketAddress determineSocketAddress() {
        if (mSocket.startsWith("__test__") && Build.IS_DEBUGGABLE) {
            return new LocalSocketAddress(mSocket);
        } else {
            return new LocalSocketAddress(mSocket, LocalSocketAddress.Namespace.RESERVED);
        }
    }

這個(gè)mSocket又是在構(gòu)造中賦值的追逮,那追構(gòu)造到NativeDaemonConnector->NetworkManagementService
發(fā)現(xiàn)這個(gè)構(gòu)造是private的酪刀,直接本地搜索,發(fā)現(xiàn)在

    static NetworkManagementService create(Context context,
            String socket) throws InterruptedException {
        final NetworkManagementService service = new NetworkManagementService(context, socket);
        final CountDownLatch connectedSignal = service.mConnectedSignal;
        if (DBG) Slog.d(TAG, "Creating NetworkManagementService");
        service.mThread.start();
        if (DBG) Slog.d(TAG, "Awaiting socket connection");
        connectedSignal.await();
        if (DBG) Slog.d(TAG, "Connected");
        return service;
    }

    public static NetworkManagementService create(Context context) throws InterruptedException {
        return create(context, NETD_SOCKET_NAME);
    }
    private static final String NETD_SOCKET_NAME = "netd";

可以看到NETD_SOCKET_NAME就是剛才unixSocket通信的文件名钮孵,值為"netd"骂倘,那connector這邊是socket的client端,另一端在哪里呢巴席。
我們可以看下剛才的代碼

return new LocalSocketAddress(mSocket, LocalSocketAddress.Namespace.RESERVED);

注意第二個(gè)參數(shù)namespace是RESERVED历涝,這說明這個(gè)socket必須是init進(jìn)程開啟的,netd是android的很重要的daemon進(jìn)程漾唉,我們?cè)趇nit.rc中可以找到netd的聲明

service netd /system/bin/netd
    class main
    socket netd stream 0660 root system
    socket dnsproxyd stream 0660 root inet
    socket mdns stream 0660 root system
    socket fwmarkd stream 0660 root inet

那么在/system/core/init/init.c中的main函數(shù)中可以看到解析init.rc的操作

int main(int argc, char **argv)
{
      ...
      restorecon("/dev");
      restorecon("/dev/socket");
      …
      init_parse_config_file("/init.rc");
      …
}

socket的創(chuàng)建過程是在啟動(dòng)netd service的時(shí)候荧库,在函數(shù)service_start中實(shí)現(xiàn)

void service_start(struct service *svc, const char *dynamic_args)
{
        ...
        for (si = svc->sockets; si; si = si->next) {
        int socket_type = (!strcmp(si->type, "stream") ? SOCK_STREAM : (!strcmp(si->type, "dgram") ? SOCK_DGRAM :                     SOCK_SEQPACKET));
            int s = create_socket(si->name, socket_type,
                                  si->perm, si->uid, si->gid);
            if (s >= 0) {
                publish_socket(si->name, s);
            }
        }
        …
}

socket的建立是在create_socket函數(shù)中

int create_socket(const char *name, int type, mode_t perm, uid_t uid, gid_t gid)
{
    struct sockaddr_un addr;
    int fd, ret;
#ifdef HAVE_SELINUX
    char *secon;
#endif
 
    fd = socket(PF_UNIX, type, 0);
    ...
}

其實(shí)從init啟動(dòng)到socket建立還有很復(fù)雜的過程,如要詳細(xì)說明需要另起篇幅赵刑,這里就說到netd的socket建立分衫,然后繼續(xù)查看netd作為socket的server端,如何處理從NativeDaemonConnector寫過去的命令的般此。
在netd的main.cpp中蚪战,可以看到他如何處理命令

2.6 system/netd/server/main.cpp

int main() {
    CommandListener *cl;
    NetlinkManager *nm;
    ...

    ALOGI("Netd 1.0 starting");
    remove_pid_file();
    blockSigpipe();
    if (!(nm = NetlinkManager::Instance())) {
        ALOGE("Unable to create NetlinkManager");
        exit(1);
    };
    cl = new CommandListener();
    nm->setBroadcaster((SocketListener *) cl);
    if (nm->start()) {
        ALOGE("Unable to start NetlinkManager (%s)", strerror(errno));
        exit(1);
    }
    ...

}

這里注冊(cè)了一個(gè)command監(jiān)聽器,來處理通過netd socket傳進(jìn)來的指令

2.7 /system/netd/server/CommandListener.cpp

CommandListener::CommandListener() :
                 FrameworkListener("netd", true) {
    registerCmd(new InterfaceCmd());
    registerCmd(new IpFwdCmd());
    registerCmd(new TetherCmd());
    registerCmd(new NatCmd());
    registerCmd(new ListTtysCmd());
    registerCmd(new PppdCmd());
    registerCmd(new SoftapCmd());
    registerCmd(new BandwidthControlCmd());
    registerCmd(new IdletimerControlCmd());
    registerCmd(new ResolverCmd());
    registerCmd(new FirewallCmd());
    registerCmd(new ClatdCmd());
    registerCmd(new NetworkCommand());
    if (!sNetCtrl)
        sNetCtrl = new NetworkController();
    if (!sTetherCtrl)
        sTetherCtrl = new TetherController();
    if (!sNatCtrl)
        sNatCtrl = new NatController();
    if (!sPppCtrl)
        sPppCtrl = new PppController();
    if (!sSoftapCtrl)
        sSoftapCtrl = new SoftapController();
    if (!sBandwidthCtrl)
        sBandwidthCtrl = new BandwidthController();
    if (!sIdletimerCtrl)
        sIdletimerCtrl = new IdletimerController();
    if (!sResolverCtrl)
        sResolverCtrl = new ResolverController();
    if (!sFirewallCtrl)
        sFirewallCtrl = new FirewallController();
    if (!sInterfaceCtrl)
        sInterfaceCtrl = new InterfaceController();
    if (!sClatdCtrl)
        sClatdCtrl = new ClatdController(sNetCtrl);
    ...

}

這個(gè)commandListener的構(gòu)造里面铐懊,定義了很多針對(duì)特定指令的處理器邀桑,這里我們發(fā)送的是softap類型的command,指令是set和startap科乎,處理softap指令部分的代碼如下

int CommandListener::SoftapCmd::runCommand(SocketClient *cli,
                                        int argc, char **argv) {
    ...

    if (argc < 2) {
        cli->sendMsg(ResponseCode::CommandSyntaxError,
                     "Missing argument in a SoftAP command", false);
        return 0;
    }
    if (!strcmp(argv[1], "startap")) {
        rc = sSoftapCtrl->startSoftap();
    } else if (!strcmp(argv[1], "stopap")) {
        rc = sSoftapCtrl->stopSoftap();
    } else if (!strcmp(argv[1], "fwreload")) {
        rc = sSoftapCtrl->fwReloadSoftap(argc, argv);
    } else if (!strcmp(argv[1], "status")) {
        asprintf(&retbuf, "Softap service %s running",
                 (sSoftapCtrl->isSoftapStarted() ? "is" : "is not"));
        cli->sendMsg(rc, retbuf, false);
        free(retbuf);
        return 0;
    } else if (!strcmp(argv[1], "set")) {
        rc = sSoftapCtrl->setSoftap(argc, argv);
    } else {
        cli->sendMsg(ResponseCode::CommandSyntaxError, "Unrecognized SoftAP command", false);
        return 0;
    }
    ...

    return 0;
}

看到調(diào)用的是softapController->setSoftap()和softapController->startSoftap()

2.7 system/netd/server/SoftapController.cpp

static const char HOSTAPD_CONF_FILE[]    = "/data/misc/wifi/hostapd.conf";
int SoftapController::setSoftap(int argc, char *argv[]) {
    ...

    if (argc > 7) {
        if (!strcmp(argv[6], "wpa-psk")) {
            generatePsk(argv[3], argv[7], psk_str);
            asprintf(&fbuf, "%swpa=3\nwpa_pairwise=TKIP CCMP\nwpa_psk=%s\n", wbuf, psk_str);
        } else if (!strcmp(argv[6], "wpa2-psk")) {
            generatePsk(argv[3], argv[7], psk_str);
            asprintf(&fbuf, "%swpa=2\nrsn_pairwise=CCMP\nwpa_psk=%s\n", wbuf, psk_str);
        } else if (!strcmp(argv[6], "open")) {
            asprintf(&fbuf, "%s", wbuf);
        }
    }
    ...

    fd = open(HOSTAPD_CONF_FILE, O_CREAT | O_TRUNC | O_WRONLY | O_NOFOLLOW, 0660);
    ...

    if (write(fd, fbuf, strlen(fbuf)) < 0) {
        ALOGE("Cannot write to \"%s\": %s", HOSTAPD_CONF_FILE, strerror(errno));
        ret = ResponseCode::OperationFailed;
    }
    ...

    return ret;
}

可以看到setSoftap()實(shí)際就是把參數(shù)存入了配置文件/data/misc/wifi/hostapd.conf
而startSoftap()函數(shù)如下:

static const char HOSTAPD_BIN_FILE[]    = "/system/bin/hostapd";
int SoftapController::startSoftap() {
    ...

    if ((pid = fork()) < 0) {
        ALOGE("fork failed (%s)", strerror(errno));
        return ResponseCode::ServiceStartFailed;
    }
    if (!pid) {
        ensure_entropy_file_exists();
        if (execl(HOSTAPD_BIN_FILE, HOSTAPD_BIN_FILE,
                  "-e", WIFI_ENTROPY_FILE,
                  HOSTAPD_CONF_FILE, (char *) NULL)) {
            ALOGE("execl failed (%s)", strerror(errno));
        }
        ALOGE("SoftAP failed to start");
        return ResponseCode::ServiceStartFailed;
    } else {
        mPid = pid;
        ALOGD("SoftAP started successfully");
        usleep(AP_BSS_START_DELAY);
    }
    return ResponseCode::SoftapStatusResult;
}

核心就是調(diào)用/system/bin/hostapd壁畸,然后使用/data/misc/wifi/hostapd.conf中存儲(chǔ)的參數(shù),開啟ap喜喂。接下來就是hostapd去調(diào)用驅(qū)動(dòng)的過程了瓤摧,平臺(tái)層的代碼就分析完了竿裂。接下還會(huì)出發(fā)TetherStateChange,然后通過TetherController去調(diào)用dnsmasq去開啟DHCP服務(wù)照弥,這里就不做詳細(xì)分析了腻异。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市这揣,隨后出現(xiàn)的幾起案子悔常,更是在濱河造成了極大的恐慌,老刑警劉巖给赞,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件机打,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡片迅,警方通過查閱死者的電腦和手機(jī)残邀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來柑蛇,“玉大人芥挣,你說我怎么就攤上這事〕芴ǎ” “怎么了空免?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)盆耽。 經(jīng)常有香客問我蹋砚,道長(zhǎng),這世上最難降的妖魔是什么摄杂? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任坝咐,我火速辦了婚禮,結(jié)果婚禮上匙姜,老公的妹妹穿的比我還像新娘畅厢。我一直安慰自己,他們只是感情好氮昧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著浦楣,像睡著了一般袖肥。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上振劳,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天椎组,我揣著相機(jī)與錄音,去河邊找鬼历恐。 笑死寸癌,一個(gè)胖子當(dāng)著我的面吹牛专筷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蒸苇,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼磷蛹,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了溪烤?” 一聲冷哼從身側(cè)響起味咳,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎檬嘀,沒想到半個(gè)月后槽驶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體喜每,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡里初,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了埋合。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片揍异。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡全陨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蒿秦,到底是詐尸還是另有隱情烤镐,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布棍鳖,位于F島的核電站炮叶,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏渡处。R本人自食惡果不足惜镜悉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望医瘫。 院中可真熱鬧侣肄,春花似錦、人聲如沸醇份。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)僚纷。三九已至矩距,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間怖竭,已是汗流浹背锥债。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人哮肚。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓登夫,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親允趟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子恼策,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

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