Mac OS X不使用輪詢來監(jiān)控進(jìn)程的生命周期

做mac應(yīng)用開發(fā)與IOS一個很大的不同楣嘁,是多進(jìn)程,一個應(yīng)用中存在多個的進(jìn)程篙梢。很多時候我們都有監(jiān)控進(jìn)程的需求淳衙,下面就介紹多種OSX監(jiān)控進(jìn)程的方式,總有一種適合你。

簡介

做一段時間的Mac OS X開發(fā)之后,你將不可避免的遇到需要創(chuàng)建協(xié)作進(jìn)程的情況,例如:

  • 在寫一個應(yīng)用程序中肚豺,你可能會想要將一些代碼封裝到一個獨(dú)立的輔助進(jìn)程中去〗缋梗或許你想要將一些不可靠的代碼放到一個獨(dú)立的進(jìn)程中去吸申,這樣,這個進(jìn)程崩潰了也不會影響主程序的運(yùn)行享甸。又或者你可能想要訪問某些不是線程安全的API截碴,而不會鎖定應(yīng)用程序的用戶界面。
  • 您可能正在編寫一套合作應(yīng)用程序蛉威。也許你正在編寫一個文字處理器日丹,并且想要調(diào)用一個單獨(dú)的公式編輯器服務(wù)。
  • 如果您正在編寫一個守護(hù)進(jìn)程蚯嫌,則可能需要與可訪問每個用戶狀態(tài)的各種代理程序進(jìn)行交互哲虾。

一旦你有多個進(jìn)程,就不可避免的遇到進(jìn)程生命周期的問題:也就是說择示,一個進(jìn)程需要知道另一個進(jìn)程是否在運(yùn)行束凑。這篇文檔描述了多種可以在進(jìn)程啟動或者終止時通知你的方法。它分為兩個主要部分栅盲。監(jiān)控一個你自己啟動的進(jìn)程汪诉,監(jiān)控一個不是你自己啟動的進(jìn)程。最后谈秫,進(jìn)程的序列號中包含了一些本文中討論的進(jìn)程序列號的基礎(chǔ)API的重要信息扒寄。

首先,讓我們談?wù)撘粋€提供了大量關(guān)鍵優(yōu)勢的替代方法拟烫。

重要提示:所有本文中討論的技術(shù)都會在事件發(fā)生變化時通知你旗们。通過輪詢進(jìn)程列表可以獲得相同的信息,但輪詢通常是一個壞主意(它消耗CPU時間构灸,減少電池壽命,增加你設(shè)置進(jìn)程的工作量,并且還會增加響應(yīng)事件的延遲喜颁。)

面向服務(wù)的替代方案

監(jiān)控進(jìn)程生命周期的最常見原因之一是該進(jìn)程為您提供一些服務(wù)稠氮。例如,一個電影轉(zhuǎn)碼應(yīng)用程序半开,該程序通常會將實(shí)際的轉(zhuǎn)碼工作放到一個子進(jìn)程中去執(zhí)行隔披,主進(jìn)程負(fù)責(zé)監(jiān)控子進(jìn)程的工作狀態(tài),一旦子進(jìn)程意外退出了寂拆,主進(jìn)程可以重新啟動它奢米。

你可以通過重新構(gòu)思你的方法來避免這個需求。與其明確的管理你的輔助進(jìn)程狀態(tài)纠永,還不如將其重新定義為應(yīng)用程序所需要的服務(wù)鬓长,然后通過launchd來管理該服務(wù),它將負(fù)責(zé)啟動和終止提供該服務(wù)的進(jìn)程的所有細(xì)節(jié)尝江。

關(guān)于面向服務(wù)的更全面的討論涉波,可以閱讀launchd相關(guān)的文檔。

監(jiān)控自己啟動的進(jìn)程

有許多種不同的方式監(jiān)控自己啟動的進(jìn)程炭序,每種技術(shù)都各有利弊啤覆,閱讀下面的內(nèi)容,以選擇一個最適合自己情況的惭聂。

NSTask

NSTask可以輕松的啟動一個幫助進(jìn)程并等待它結(jié)束窗声。你可以同步等待(使用-[NSTask waitUntilExit]方法),也可以注冊一個通知辜纲,接收NSTaskDidTerminateNotification通知笨觅。代碼清單1展示了同步方式,代碼清單2展示了異步的方式侨歉。

清單1:同步使用NSTask

- (IBAction)testNSTaskSync:(id)sender
{
    NSTask *    syncTask;

    syncTask = [NSTask 
        launchedTaskWithLaunchPath:@"/bin/sleep" 
        arguments:[NSArray arrayWithObject:@"1"]
    ];
    [syncTask waitUntilExit];
}

清單2:異步使用NSTask

- (IBAction)testNSTaskAsync:(id)sender{
 task = [[NSTask alloc] init]; 
 [task setLaunchPath:@"/bin/sleep"];
 [task setArguments:[NSArray arrayWithObject:@"1"]];
 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskExited:) name:NSTaskDidTerminateNotification object:task ];
 [task launch]; 
 //在下面的-taskExited:中繼續(xù)執(zhí)行屋摇。
}
- (void)taskExited:(NSNotification *)note{
 // 收到通知!
 [[NSNotificationCenter defaultCenter] removeObserver:self name:NSTaskDidTerminateNotification object:task ];
 [task release]; 
 task = nil;
}

進(jìn)程死亡事件

如果您使用基于序列號的API啟動應(yīng)用程序,則可以通過注冊kAEApplicationDiedApple事件來得知其終止幽邓。

重要提示:此事件僅適用于您啟動的應(yīng)用程序炮温。

清單3顯示了如何注冊和處理應(yīng)用程序死亡事件。

清單3:使用進(jìn)程死亡事件

- (IBAction)testApplicationDied:(id)sender
{
    NSURL *     url;
    static BOOL sHaveInstalledAppDiedHandler;

    if ( ! sHaveInstalledAppDiedHandler ) {
        (void) AEInstallEventHandler(
            kCoreEventClass, 
            kAEApplicationDied, 
            (AEEventHandlerUPP) AppDiedHandler, 
            (SRefCon) self, 
            false
        );
        sHaveInstalledAppDiedHandler = YES;
    }

    url = [NSURL fileURLWithPath:@"/Applications/TextEdit.app"];
    (void) LSOpenCFURLRef( (CFURLRef) url, NULL);

    // Execution continues in AppDiedHandler, below.
}

static OSErr AppDiedHandler(
    const AppleEvent *  theAppleEvent, 
    AppleEvent *        reply, 
    SRefCon             handlerRefcon
)
{
    SInt32              errFromEvent;
    ProcessSerialNumber psn;
    DescType            junkType;
    Size                junkSize;

    (void) AEGetParamPtr(
        theAppleEvent, 
        keyErrorNumber, 
        typeSInt32, 
        &junkType, 
        &errFromEvent, 
        sizeof(errFromEvent), 
        &junkSize
    );
    (void) AEGetParamPtr(
        theAppleEvent, 
        keyProcessSerialNumber, 
        typeProcessSerialNumber, 
        &junkType, 
        &psn, 
        sizeof(psn), 
        &junkSize
    );

    // You've been notified!

    NSLog(
        @"died %lu.%lu %d", 
        (unsigned long) psn.highLongOfPSN, 
        (unsigned long) psn.lowLongOfPSN, 
        (int) errFromEvent
    );

    return noErr;
}

重要提示:進(jìn)程死亡事件基于序列號牵舵,這是一個具有一些重要后果的事實(shí)柒啤。有關(guān)詳細(xì)信息,請參閱過程序列號畸颅。

UNIX方式

Mac OS X的BSD子系統(tǒng)有兩個開啟新進(jìn)程的基本API:

  • fork和exec—這種技術(shù)起源于第一個UNIX系統(tǒng)担巩,使用fork創(chuàng)建一個進(jìn)程,是對當(dāng)前進(jìn)程的精確克隆没炒,而exec(實(shí)際上是一個基于exec的例程簇)會使當(dāng)前進(jìn)程去啟動運(yùn)行一個新的可執(zhí)行文件涛癌。

  • posix spawn—這個API就像fork和exec的組合。它是在Mac OS X 10.5中引入的。
    在上述兩種情況下拳话,生成的進(jìn)程都是當(dāng)前進(jìn)程的子進(jìn)程先匪。有兩種傳統(tǒng)的UNIX方法能知道子進(jìn)程的死亡:

  • 同步方法:使用一系列的等待例程(典型的:waitpid)

  • 異步方法:通過SIGCHLD信號(SIGCHLD,在一個進(jìn)程終止或者停止時弃衍,將SIGCHLD信號發(fā)送給其父進(jìn)程呀非,按系統(tǒng)默認(rèn)將忽略此信號,如果父進(jìn)程希望被告知其子系統(tǒng)的這種狀態(tài)镜盯,則應(yīng)捕捉此信號岸裙。)

在許多情況下同步等待是比較適用的方法,例如速缆,如果父進(jìn)程在子進(jìn)程完成之前無法進(jìn)行降允,理所應(yīng)當(dāng)?shù)膽?yīng)該同步等待。清單4顯示了如何fork激涤,然后exec 拟糕,再等待的示例。

清單4:Fork, exec, wait

extern char **environ;

- (IBAction)testWaitPID:(id)sender
{
    pid_t       pid;
    char *      args[3] = { "/bin/sleep", "1", NULL };
    pid_t       waitResult;
    int         status;

    // I used fork/exec rather than posix_spawn because I would like this 
    // code to be compatible with 10.4.x.

    pid = fork();
    switch (pid) {
        case 0:
            // child
            (void) execve(args[0], args, environ);
            _exit(EXIT_FAILURE);
            break;
        case -1:
            // error
            break;
        default:
            // parent
            break;
    }
    if (pid >= 0) {
        do {
            waitResult = waitpid(pid, &status, 0);
        } while ( (waitResult == -1) && (errno == EINTR) );
    }
}

另一方面倦踢,有些情況同步等待是一個非常糟糕的主意送滞。例如,如果您正在應(yīng)用程序的主線程上面運(yùn)行辱挥,并且子進(jìn)程可能會執(zhí)行一個耗時操作犁嗅,則不希望阻塞應(yīng)用程序的用戶界面等待該子進(jìn)程退出。
在這種情況下晤碘,您可以通過監(jiān)聽SIGCHLD信號異步等待褂微。

重要提示:如果你使用監(jiān)聽SIGCHLD信號異步等待的方式,你仍然需要通過調(diào)用等待例程來獲取子進(jìn)程园爷,否則將會導(dǎo)致僵尸進(jìn)程宠蚂。

由于與信號處理程序相關(guān)聯(lián)的環(huán)境很復(fù)雜,可能監(jiān)聽信號會很棘手童社。具體來說求厕,如果你使用了一個信號處理程序(signalsigaction,那么你必須非常小心你在該處理程序中所做的工作扰楼。很少的函數(shù)能被信號處理程序安全的調(diào)用呀癣,例如,使用malloc分配內(nèi)存空間就是不安全的弦赖。

內(nèi)唄信號處理程序安全調(diào)用的函數(shù)(async-signal safe函數(shù))列在sigaction手冊頁上项栏。
在大部分情況下,你必須采用額外的手段蹬竖,將傳入信號重定向到更加合理的環(huán)境中沼沈。有兩種標(biāo)準(zhǔn)的做法:

  • sockets(套接字)—在此技術(shù)中流酬,您將創(chuàng)建一個UNIX域套接字對,并使用CFSocket將一端添加到您的循環(huán)中列另。當(dāng)信號到達(dá)時康吵,信號處理器將一個虛擬消息寫入套接字。這將喚醒循環(huán)访递,并允許您在安全的環(huán)境中處理信號。要查看此技術(shù)的演示同辣,請查看示例代碼“CFLocalServer”中的InstallSignalToSocket例程拷姿。
  • kqueues —kqueue機(jī)制允許您收聽信號而不安裝任何信號處理程序。所以你可以創(chuàng)建一個kqueue旱函,指示它來監(jiān)聽SIGCHLD信號响巢,然后將其包裝在一個CFFileDescriptor中并將其添加到你的runloop中。當(dāng)信號到達(dá)時棒妨,與CFFileDescriptor關(guān)聯(lián)的回調(diào)例程運(yùn)行踪古,您可以在安全的環(huán)境中處理信號。要查看此技術(shù)的演示券腔,請查看示例代碼“PreLoginAgents”中的InstallHandleSIGTERMFromRunLoop例程伏穆。

重要提示: kqueue技術(shù)需要Mac OS X 10.5或更高版本,因?yàn)樗褂肅FFileDescriptor纷纫。

UNIX替代方案

處理SIGCHLD信號有許多陷阱枕扫。上一節(jié)描述了最深刻的一部分,但還有其他部分辱魁。當(dāng)你在寫library code的時候烟瞧,使用SIGCHLD是一件非常棘手的事情,因?yàn)镾IGCHLD由主程序本身控制染簇,你的library code不能要求其被設(shè)置為某種方式参滴。
有多種方法來避免SIGCHLD的這種混亂,一種方式就是創(chuàng)建一個域套接字對锻弓,并且為了使子進(jìn)程具有引用一端的唯一描述符砾赔,父進(jìn)程具有另一端的描述符。當(dāng)子進(jìn)程中止的時候弥咪,系統(tǒng)關(guān)閉子進(jìn)程的描述符过蹂,這導(dǎo)致套接字的另一端指向文件的結(jié)尾(這意味著它變成可讀的了,但是當(dāng)你嘗試讀取的時候聚至,返回的是0).當(dāng)父進(jìn)程監(jiān)控到文件結(jié)束的狀態(tài)酷勺,就可以獲取子進(jìn)程信息。清單5展示了這種方案的示例扳躬。

清單5:使用套接字監(jiān)測子進(jìn)程的中止

- (IBAction)testSocketPair:(id)sender
{
    int                 fds[2];
    int                 remoteSocket;
    int                 localSocket;
    CFSocketContext     context = { 0, self, NULL, NULL, NULL };
    CFRunLoopSourceRef  rls;
    char *              args[3] = { "/bin/sleep", "1", NULL } ;

    // Create a socket pair and wrap the local end up in a CFSocket.

    (void) socketpair(AF_UNIX, SOCK_STREAM, 0, fds);

    remoteSocket = fds[0];
    localSocket  = fds[1];
    socket = CFSocketCreateWithNative(
        NULL, 
        localSocket, 
        kCFSocketDataCallBack, 
        SocketClosedSocketCallBack, 
        &context
    );
    CFSocketSetSocketFlags(
        socket, 
        kCFSocketAutomaticallyReenableReadCallBack | kCFSocketCloseOnInvalidate
    );

    // Add the CFSocket to our runloop.

    rls = CFSocketCreateRunLoopSource(NULL, socket, 0);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
    CFRelease(rls);

    // fork and exec the child process.

    childPID = fork();
    switch (childPID) {
        case 0:
            // child
            (void) execve(args[0], args, environ);
            _exit(EXIT_FAILURE);
            break;
        case -1:
            // error
            break;
        default:
            // parent
            break;
    }

    // Close our reference to the remote socket. The only reference remaining 
    // is the one in the child. When that dies, the socket will become readable.

    (void) close(remoteSocket);

    // Execution continues in SocketClosedSocketCallBack, below.
}

static void SocketClosedSocketCallBack(
    CFSocketRef             s, 
    CFSocketCallBackType    type, 
    CFDataRef               address, 
    const void *            data, 
    void *                  info
)
{
    int             waitResult;
    int             status;

    // Reap the child.

    do {
        waitResult = waitpid( ((AppDelegate *) info)->childPID, &status, 0);
    } while ( (waitResult == -1) && (errno == EINTR) );

    // You've been notified!
}

監(jiān)控任一進(jìn)程

如果要監(jiān)控一個非自己啟動的進(jìn)程脆诉,則可選用的方案比較少甚亭。但是,這些可以的方案就可以滿足我們大部分的需求击胜。此外亏狰,你要根據(jù)自己的情況選擇正確的API,閱讀以下部分以讓你了解哪個API是適合你的偶摔。

NSWorkspace

NSWorkspace提供了一個非常簡單的方式來監(jiān)控進(jìn)程的啟動和退出暇唾。
要注冊這些通知,您必須:

1辰斋、獲得NSWorkspace的自定義通知中心 策州,調(diào)用-[NSWorkspace notificationCenter]

2、添加NSWorkspaceDidLaunchApplicationNotification和NSWorkspaceDidTerminateApplicationNotification事件的觀察者

當(dāng)收到通知的時候宫仗,user info字典包含了受影響進(jìn)程的信息够挂。NSWorkspace.h頭文件中列出了user info字典的key,以"NSApplicationPath"開頭藕夫。
清單6顯示了如何使用NSWorkspace獲取應(yīng)用程序啟動和終止的示例孽糖。

清單6:使用NSWorkspace獲取應(yīng)用程序啟動和終止

- (IBAction)testNSWorkspace:(id)sender
{
    NSNotificationCenter *  center;

    NSLog(@"-[AppDelegate testNSWorkspace:]");

    // Get the custom notification center.

    center = [[NSWorkspace sharedWorkspace] notificationCenter];

    // Install the notifications.

    [center addObserver:self 
        selector:@selector(appLaunched:) 
        name:NSWorkspaceDidLaunchApplicationNotification 
        object:nil
    ];
    [center addObserver:self 
        selector:@selector(appTerminated:) 
        name:NSWorkspaceDidTerminateApplicationNotification 
        object:nil
    ];

    // Execution continues in -appLaunched: and -appTerminated:, below.
}

- (void)appLaunched:(NSNotification *)note
{
    NSLog(@"launched %@\n", [[note userInfo] objectForKey:@"NSApplicationName"]);

    // You've been notified!
}

- (void)appTerminated:(NSNotification *)note
{
    NSLog(@"terminated %@\n", [[note userInfo] objectForKey:@"NSApplicationName"]);

    // You've been notified!
}

重要提示: NSWorkspace基于序列號,這是一個具有一些重要后果的事實(shí)毅贮。有關(guān)詳細(xì)信息办悟,請參閱過程序列號

Carbon Event Manager

Carbon Event Manager 發(fā)送大量的與進(jìn)程管理相關(guān)的事件,具體來說嫩码,當(dāng)應(yīng)用程序啟動的時候誉尖,發(fā)送kEventAppLaunched事件,當(dāng)應(yīng)用程序中止時铸题,發(fā)送kEventAppTerminated事件铡恕,你可以像任何其他Carbon事件一樣注冊這些事件。清單7顯示了一個例子丢间。

調(diào)用事件處理程序時探熔,kEventParamProcessID參數(shù)將包含受影響的進(jìn)程的ProcesSerialNumber。

重要提示:當(dāng)您的應(yīng)用程序收到該kEventAppTerminated事件時烘挫,終止應(yīng)用程序可能已經(jīng)退出诀艰。因此,您無法獲取有關(guān)該應(yīng)用程序的信息GetProcessInformation饮六。如果您需要有關(guān)終止應(yīng)用程序的信息其垄,則必須提前緩存。

清單7:使用Carbon事件來獲取應(yīng)用程序啟動和終止

- (IBAction)testCarbonEvents:(id)sender
{
    static EventHandlerRef sCarbonEventsRef = NULL;
    static const EventTypeSpec kEvents[] = {
        { kEventClassApplication, kEventAppLaunched },
        { kEventClassApplication, kEventAppTerminated }
    };

    if (sCarbonEventsRef == NULL) {
        (void) InstallEventHandler(
            GetApplicationEventTarget(),
            (EventHandlerUPP) CarbonEventHandler,
            GetEventTypeCount(kEvents),
            kEvents,
            self,
            &sCarbonEventsRef
        );
    }

    // Execution continues in CarbonEventHandler, below.
}

static OSStatus CarbonEventHandler(
    EventHandlerCallRef inHandlerCallRef, 
    EventRef            inEvent, 
    void *              inUserData
)
{
    ProcessSerialNumber psn;

    (void) GetEventParameter(
        inEvent, 
        kEventParamProcessID, 
        typeProcessSerialNumber, 
        NULL, 
        sizeof(psn), 
        NULL, 
        &psn
    );
    switch ( GetEventKind(inEvent) ) {
        case kEventAppLaunched:
            NSLog(
                @"launched %u.%u", 
                (unsigned int) psn.highLongOfPSN, 
                (unsigned int) psn.lowLongOfPSN
            );
            // You've been notified!
            break;
        case kEventAppTerminated:
            NSLog(
                @"terminated %u.%u", 
                (unsigned int) psn.highLongOfPSN, 
                (unsigned int) psn.lowLongOfPSN
            );
            // You've been notified!
            break;
        default:
            assert(false);
    }
    return noErr;
}

kqueues

NSWorkspace和Carbon事件只能在單個GUI登錄上下文中工作卤橄。如果您正在編寫一個不在GUI登錄上下文中運(yùn)行的程序(也許是守護(hù)程序)绿满,或者您需要監(jiān)視與運(yùn)行時不同的上下文中的進(jìn)程,則需要考慮替代方法窟扑。 kqueue NOTE_EXIT
事件是一個不錯的選擇喇颁。您可以使用它來檢測進(jìn)程何時退出漏健,無論它運(yùn)行的是哪個上下文。與NSWorkspace和Carbon事件不同橘霎,您必須準(zhǔn)確指定要監(jiān)視的進(jìn)程; 否則任何進(jìn)程的中止都無法得到通知蔫浆。
清單8是一個簡單的例子,說明如何使用kqueue來監(jiān)視特定進(jìn)程的終止姐叁。

清單8:使用kqueue監(jiān)視特定進(jìn)程

static pid_t gTargetPID = -1;
    // We assume that some other code sets up gTargetPID.

- (IBAction)testNoteExit:(id)sender
{
    FILE *                  f;
    int                     kq;
    struct kevent           changes;
    CFFileDescriptorContext context = { 0, self, NULL, NULL, NULL };
    CFRunLoopSourceRef      rls;

    // Create the kqueue and set it up to watch for SIGCHLD. Use the 
    // new-in-10.5 EV_RECEIPT flag to ensure that we get what we expect.

    kq = kqueue();

    EV_SET(&changes, gTargetPID, EVFILT_PROC, EV_ADD | EV_RECEIPT, NOTE_EXIT, 0, NULL);
    (void) kevent(kq, &changes, 1, &changes, 1, NULL);

    // Wrap the kqueue in a CFFileDescriptor (new in Mac OS X 10.5!). Then 
    // create a run-loop source from the CFFileDescriptor and add that to the 
    // runloop.

    noteExitKQueueRef = CFFileDescriptorCreate(NULL, kq, true, NoteExitKQueueCallback, &context);
    rls = CFFileDescriptorCreateRunLoopSource(NULL, noteExitKQueueRef, 0);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
    CFRelease(rls);

    CFFileDescriptorEnableCallBacks(noteExitKQueueRef, kCFFileDescriptorReadCallBack);

    // Execution continues in NoteExitKQueueCallback, below.
}

static void NoteExitKQueueCallback(
    CFFileDescriptorRef f, 
    CFOptionFlags       callBackTypes, 
    void *              info
)
{
    struct kevent   event;

    (void) kevent( CFFileDescriptorGetNativeDescriptor(f), NULL, 0, &event, 1, NULL);

    NSLog(@"terminated %d", (int) (pid_t) event.ident);

    // You've been notified!
}

進(jìn)程序列號(Process Serial Numbers)

Mac OS X具有許多用于進(jìn)程管理的高級API瓦盛,可以按進(jìn)程序列號(ProcessSerialNumber)進(jìn)行處理。這些包括啟動服務(wù)外潜,進(jìn)程管理器和NSWorkspace谭溉。這些API都有三個重要的功能:

  • 它們在單個GUI登錄會話的上下文中工作。例如橡卤,如果您使用NSWorkspace來觀察正在啟動和終止的應(yīng)用程序,那么只會在同一個GUI登錄會話中運(yùn)行的應(yīng)用程序被通知损搬。

  • 他們只看到連接到窗口服務(wù)器的進(jìn)程碧库。
    例如,如果您使用NSTask來運(yùn)行BSD命令行工具巧勤,如find嵌灰,那么基于NSWorkspace的觀察者將不會被通知該工具的啟動或終止。

  • 它們通常不能在GUI登錄上下文之外運(yùn)行的進(jìn)程(例如颅悉,守護(hù)程序)使用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末沽瞭,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子剩瓶,更是在濱河造成了極大的恐慌驹溃,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件延曙,死亡現(xiàn)場離奇詭異豌鹤,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)枝缔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門布疙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人愿卸,你說我怎么就攤上這事灵临。” “怎么了趴荸?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵儒溉,是天一觀的道長。 經(jīng)常有香客問我赊舶,道長睁搭,這世上最難降的妖魔是什么赶诊? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮园骆,結(jié)果婚禮上舔痪,老公的妹妹穿的比我還像新娘。我一直安慰自己锌唾,他們只是感情好锄码,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著晌涕,像睡著了一般滋捶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上余黎,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天重窟,我揣著相機(jī)與錄音,去河邊找鬼惧财。 笑死巡扇,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的垮衷。 我是一名探鬼主播厅翔,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼搀突!你這毒婦竟也來了刀闷?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤仰迁,失蹤者是張志新(化名)和其女友劉穎甸昏,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體徐许,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡筒扒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了绊寻。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片花墩。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖澄步,靈堂內(nèi)的尸體忽然破棺而出冰蘑,到底是詐尸還是另有隱情,我是刑警寧澤村缸,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布祠肥,位于F島的核電站,受9級特大地震影響梯皿,放射性物質(zhì)發(fā)生泄漏仇箱。R本人自食惡果不足惜县恕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望剂桥。 院中可真熱鬧忠烛,春花似錦、人聲如沸权逗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽斟薇。三九已至师坎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間堪滨,已是汗流浹背胯陋。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留袱箱,地道東北人惶岭。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像犯眠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子症革,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

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