audiostreamer.m的初识(一)

audiostreamer.m的初识(一)

标签: streamsocketnullunix数据结构文档

2012-06-06 13:45 4282人阅读 评论(0) 收藏 举报

版权声明:本文为博主原创文章,未经博主允许不得转载。


目录(?)

[+]


研究了audiostreamer一上午,看了一些帮助文档 http://www.apple.com.cn/developer/mac/library/documentation/IntelWeb/Conceptual/CFNetwork/Concepts/chapter_2_section_2.html#//apple_ref/doc/uid/TP30001132-CH4-DontLinkElementID_12 ,里面主要讲解的CFNetwork的一些基本的用法。要看懂audiostreamer.m必须对CFNetwork有比较深的了解。

audiostreamer主要用到了读操作流。而上边的文档也给我们讲解了读操作流的一些参数,方法,机制。


这篇主要是引用的参考库的CFNetwork编程指南:


-----------------------------------------------------------------------------------------------------------------

第一部分:

处理读操作流



1.为文件创建读操作流。


CFReadStreamRef myReadStream = CFReadStreamCreateWithFile(kCFAllocatorDefault, fileURL);



这行代码,KCFAllocatorDefault参数指定了当前缺省的系统定位符,它用于定位操作流的内存地址,fileURL指定了创建的读操作流所对应的文件名称,比如 file://Users/joeuser/Downloads/MyApp.sit。

一旦操作流被创建,它就可以被打开。打开一个操作流会导致这个流占用它所需要的任何系统资源,比如用于打开文件的文件描述符。下面代码说明了如何打开读操作流。


if (!CFReadStreamOpen(myReadStream)) {


    CFStreamError myErr = CFReadStreamGetError(myReadStream);


    // An error has occurred.


        if (myErr.domain == kCFStreamErrorDomainPOSIX) {


        // Interpret myErr.error as a UNIX errno.


        } else if (myErr.domain == kCFStreamErrorDomainMacOSStatus) {


        // Interpret myErr.error as a MacOS error code.


            OSStatus macError = (OSStatus)myErr.error;


        // Check other error domains.


    }


}


如果操作流打开成功,那么 CFReadStreamOpen 函数返回 TRUE ,如果因为某种原因打开失败就会返回 FALSE 。如果CFReadStreamOpen 返回 FALSE,示例程序调用了 CFReadStreamGetError 函数,它返回了一个 CFStreamError 类型的结构,其中包含两个值:一个域代码和一个错误代码。域代码决定了错误代码将被怎样解释。比如,如果域代码是kCFStreamErrorDomainPOSIX,错误代码是一个 UNIX errno 值。其他的错误域比如是 kCFStreamErrorDomainMacOSStatus,这说明错误代码是 MacErrors.h 中定义的一个 OSStatus 值,如果是 kCFStreamErrorDomainHTTP,这说明错误代码是枚举对象CFStreamErrorHTTP 中定义的一个值。

打开一个操作流可能会耗费比较长的时间,因此 CFReadStreamOpen 和 CFWriteStreamOpen 两个函数都不会被阻塞,它们返回TRUE 表示操作流的打开过程已经开始。如果想要检查打开过程的状态,可以调用函数 CFReadStreamGetStatus 和CFWriteStreamGetStatus,如果返回 kCFStreamStatusOpening 说明打开过程仍然在进行中,如果返回 kCFStreamStatusOpen说明打开过程已经完成,而返回 kCFStreamStatusErrorOccurred 说明打开过程已经完成,但是失败了。大部分情况下,打开过程是否完成并不重要,因为 CFStream 中负责读写操作的函数在操作流打开以前会被阻塞。

想要从读操作流中读取数据的话,需要调用函数 CFReadStreamRead,它类似于 UNIX 的 read() 系统调用。二者的相同之处包括:都需要缓冲区和缓冲区大小作为参数,都会返回读取的字节数,如果到了文件末尾会返回 0 ,如果遇到错误就会返回 -1。另外,二者都会在至少一个字节可以被读取之前被阻塞,并且如果没有遇到阻塞的情况下都会继续读取。


从读操作流中获取数据 (阻塞)

CFIndex numBytesRead;


do {


    UInt8 buf[kReadBufSize];


    numBytesRead = CFReadStreamRead(myReadStream, buf, sizeof(buf));


    if( numBytesRead > 0 ) {


        handleBytes(buf, numBytesRead);


    } else if( numBytesRead < 0 ) {


        CFStreamError error = CFReadStreamGetError(myReadStream);


        reportError(error);


    }


} while( numBytesRead > 0 );

当所有数据都被读取之后,你应该调用 CFReadStreamClose 函数关闭操作流,这样可以释放与它相关的系统资源。接着通过调用函数CFRelease 释放操作流的引用对象。你也可以通过把引用对象设置为 NULL 使它无效。



释放一个读操作流

CFReadStreamClose(myReadStream);


CFRelease(myReadStream);


myReadStream = NULL;

-----------------------------------------------------------------------------------------------------------------

第二部分:


处理操作流的时候防止阻塞



当利用流进行通讯的时候,常常会发生数据传输耗费大量时间,尤其是进行基于 socket 的流操作时。如果流操作是同步方式,那么整个应用都不得不停下来等待数据传输完成。因此,强烈建议您的代码中利用其它方法防止阻塞。

在读写 CFStream 对象的时候,有两种方法防止阻塞:

  • 轮询 — 在进行读操作的时候,在从流中读取数据以前检查是否有字节可读。在进行写操作的时候,在写入流之前检查数据是否可以无阻塞的写入。
  • 利用一个循环 — 注册接收一个与流相关的事件,并把流放入一个循环当中。当与流相关的事件发生时,你的回调函数(由注册函数指定) 就会被调用。

利用轮询防止阻塞

轮询的时候,需要确定读写操作流的状态是否就绪。在写入一个写操作流的时候,这是通过调用函数CFWriteStreamCanAcceptBytes 来实现的。如果返回 TRUE,那么你就可以利用 CFWriteStreamWrite 函数,因为它可以马上进行写入操作而不会被阻塞。类似的,对于一个读操作流,在调用 CFReadStreamRead 函数之前,可以调用函数CFReadStreamHasBytesAvailable。通过这种流轮询方式,你可以避免为了等待操作流就绪而阻塞整个线程。

轮询一个读操作流

while (!done) {


    if (CFReadStreamHasBytesAvailable(myReadStream)) {


        UInt8 buf[BUFSIZE];


        CFIndex bytesRead = CFReadStreamRead(myReadStream, buf, BUFSIZE);


        if (bytesRead < 0) {


            CFStreamError error = CFReadStreamGetError(myReadStream);


            reportError(error);


        } else if (bytesRead == 0) {


            if (CFReadStreamGetStatus(myReadStream) == kCFStreamStatusAtEnd) {


                done = TRUE;


            }


        } else {


            handleBytes(buf, bytesRead);


        }


    } else {


        // ...do something else while you wait...


    }


}

利用循环防止阻塞

线程的循环会监控某些事件的发生。当这些事件发生的时候,循环会调用某个特定的函数。循环会一直监测它的输入源的事件。在进行网络传输的时候,当你注册的事件发生时,你的回调函数会被循环所执行。这使你不必轮询你的 socket 操作流,这种轮询会降低线程执行效率。如果对循环不是很熟悉,可以阅读循环中的内容。

本例以创建一个 socket 读操作流开始:

CFStreamCreatePairWithSocketToCFHost(kCFAllocatorDefault, host, port,


                                   &myReadStream, NULL);


其中 CFHost 引用对象 host 确定了读操作流指向的远程主机,port 参数确定了这个主机所使用的端口号码。CFStreamCreatePairWithSocketToCFHost 函数返回了新的读操作流引用 myReadStream。最后一个参数 NULL 确定调用者不想创建一个写操作流。如果你想要创建一个写操作流,那么最后一个参数应该是,比如 &myWriteStream 这样的形式。

在打开 socket 读操作流之前,需要创建一个上下文对象,这个对象在你注册接收流相关的事件时会用到:

CFStreamClientContext myContext = {0, myPtr, myRetain, myRelease, myCopyDesc};


第一个参数为 0 表示版本号码。info 参数,也就是 myPtr,它指向你想要传递给回调函数的数据的指针。通常情况下,myPtr 是一个指向你所定义的数据结构的指针,这个结构中包含了与这个流相关的指针。retain 参数指向一个能够保存 info 参数的函数,因此如果你象上面的代码那样将这个参数设置为 myRetain, CFStream 会调用 myRetain(myPtr) 函数来保留 info 指针。类似的,release 参数 myRelease 指向一个释放 info 参数的函数。当操作流与上下文分离开时,CFStream 可以调用myRelease(myPtr)。最后,copyDescription 参数指向一个函数,这个函数能够提供对操作流的描述。比如,如果你需要利用上面的操作流客户端上下文调用 CFCopyDesc(myReadStream) 函数,CFStream 就会调用 myCopyDesc(myPtr)

客户端上下文还允许你将 retainrelease 和 copyDescription 等参数设置为 NULL。如果你将 retain 和 release 参数设置为NULL,那么系统就会认为你会一直保存 info 指针直到操作流本身被销毁。如果你设置 copyDescription 参数为 NULL,那么如果需要的话,系统将会提供一个 info 指针指向的内存的基本描述信息。

在客户端上下文设置好之后,可以调用函数 CFReadStreamSetClient 登记接收与操作流相关的事件。CFReadStreamSetClient 要求你指定一个回调函数,以及想要接收的事件。下面列表 2-8 的范例中,回调函数需要接收 kCFStreamEventHasBytesAvailable,kCFStreamEventErrorOccurred 以及 kCFStreamEventEndEncountered 事件。然后利用函数CFReadStreamScheduleWithRunLoop 在循环中调度这个操作流。列表 2-8 中的范例说明了如何进行这个操作。



列表 2-8  在循环中调度一个操作流



CFOptionsFlags registeredEvents = kCFStreamEventHasBytesAvailable |


        kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;


if (CFReadStreamSetClient(myReadStream, registeredEvents, myCallBack, &myContext)


{


    CFReadStreamScheduleWithRunLoop(myReadStream, CFRunLoopGetCurrent(),


                                    kCFRunLoopCommonModes);


}


当已经在循环中调度了操作流之后,你就可以象列表 2-9 中那样打开操作流了。

列表 2-9  打开一个非阻塞的读操作流

if (!CFReadStreamOpen(myReadStream)) {


    CFStreamError myErr = CFReadStreamGetError(myReadStream);


    if (myErr.error != 0) {


    // An error has occurred.


        if (myErr.domain == kCFStreamErrorDomainPOSIX) {


        // Interpret myErr.error as a UNIX errno.


            strerror(myErr.error);


        } else if (myErr.domain == kCFStreamErrorDomainMacOSStatus) {


            OSStatus macError = (OSStatus)myErr.error;


            }


        // Check other domains.


    } else


        // start the run loop


        CFRunLoopRun();


}


 


现在,只需要等待回调函数被执行到了。在你的回调函数中,要检查事件代码并采取相应的措施。参见列表 2-10。

列表 2-10  网络事件回调函数

void myCallBack (CFReadStreamRef stream, CFStreamEventType event, void *myPtr) {


    switch(event) {


        case kCFStreamEventHasBytesAvailable: 


            // It is safe to call CFReadStreamRead; it won’t block because bytes


            // are available.


            UInt8 buf[BUFSIZE];


            CFIndex bytesRead = CFReadStreamRead(stream, buf, BUFSIZE);


            if (bytesRead > 0) {


                handleBytes(buf, bytesRead);


            }


            // It is safe to ignore a value of bytesRead that is less than or


            // equal to zero because these cases will generate other events.


            break;


        case kCFStreamEventErrorOccurred: 


            CFStreamError error = CFReadStreamGetError(stream);


            reportError(error);


            CFReadStreamClose(stream);


            CFRelease(stream);


            break;


        case kCFStreamEventEndEncountered:


            reportCompletion();


            CFReadStreamClose(stream);


            CFRelease(stream);


            break;


    }


}


当回调函数接收到 kCFStreamEventHasBytesAvailable 事件代码时,它会调用 CFReadStreamRead 读取数据。

当回调函数接收到 kCFStreamEventErrorOccurred 事件代码时,它会调用 CFReadStreamGetError 来获取错误信息以及它自己的错误处理函数 (reportError) 来处理这个错误.

当回调函数接收到 kCFStreamEventEndEncountered 事件代码时,它会调用它自己的函数(reportCompletion) 处理数据结尾,然后调用函数 CFReadStreamClose 关闭操作流和 CFRelease 函数释放操作流引用。调用 CFReadStreamClose 会导致操作流被取消调度,因此回调函数不需要从循环中删除操作流。

你可能感兴趣的:(audiostreamer.m的初识(一))