ios局域网联机——苹果官方源码之WiTap剖析(二)

这篇文章是"ios局域网联机——苹果官方源码之WiTap剖析"系列的第二部分,它和第一部分紧紧相连,因此阅读此文章的前提是你已经阅读了这个系列的第一部分

打起精神继续战斗

     好吧,让我们接着第一部分继续剖析这个TCPServer.m文件吧,上一部分中我们是讲到了TCPServerAcceptCallBack:这个回调方法,讲到了它的格式,它是一个符合CFSocketCallBack这个函数指针类型的函数。

     现在让我们继续研究它的实现吧,为了方便阅读,我们再一次把这个函数的实现展示出来:

复制代码
static void TCPServerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
 { 
    TCPServer *server = (TCPServer *)info;  //1  

    if (kCFSocketAcceptCallBack == type) {   //2

        // for an AcceptCallBack, the data parameter is a pointer to a CFSocketNativeHandle
        CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;  //3

        uint8_t name[SOCK_MAXADDRLEN];   //4
        socklen_t namelen = sizeof(name);

        NSData *peer = nil; //5
        if (0 == getpeername(nativeSocketHandle, (struct sockaddr *)name, &namelen)) {
            peer = [NSData dataWithBytes:name length:namelen];
        }

        CFReadStreamRef readStream = NULL;   //6
        CFWriteStreamRef writeStream = NULL;
        CFStreamCreatePairWithSocket(kCFAllocatorDefault, nativeSocketHandle, &readStream, &writeStream);

        if (readStream && writeStream) {
            CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); //7
            CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
            [server handleNewConnectionFromAddress:peer inputStream:(NSInputStream *)readStream outputStream:(NSOutputStream *)writeStream];
        } else {
            // on any failure, need to destroy the CFSocketNativeHandle
            // since we are not going to use it any more
            close(nativeSocketHandle);
        }

        if (readStream) CFRelease(readStream);   //8
        if (writeStream) CFRelease(writeStream);
    }
}
复制代码

 

  注释1,我们看到这里它把这个函数的参数info转成了一个TCPServer类型,并申请了一个TCPServer类的变量来跟踪它。(我们在第一部分里已经说了,这里的info参数就是触发这个回调的socket再被创建时,传入到创建函数里的CFSocketContext结构的info成员,在后面的start方法里我们会看到,这个结构的info成员是我们的这个TCPServer类本身)

     注释2,这里是判断一下我们这次回调的事件类型,如果事件是成功连接我们就进行一系列操作,否则我们什么也不做。

     注释3,这里我们是把这个函数的参数data转成了一个CFSocketNativeHandle类型。(同样的也是我们在第一部分里说过的,当这个回调事件的类型是连接成功的时候,这个data就是一个CFSocketNativeHandle类型指针,这个CFSocketNativeHandle类型其实就是我们的特定平台的socket,你就当成正常的socket理解就行了,值得注意的是这里的socket是什么,是呢儿来的?我们知道,在正常的socket流程中,作为服务器的一方会有一个socket一直处于监听连接的状态,一旦有新的连接请求到来,系统会自己创建一个新的socket与这个请求的客户端进行连接,此后客户端和服务器端就通过这个新的连接进行通讯,而服务器负责监听网络连接的socket则继续监听连接。现在这个函数里的这个data应该就是在响应连接请求的时候系统自己创建的新的socket吧。<声明:这些概念我是了解自互联网,如果有什么不对的地方请及时指出,我会及时纠正,以免误导他人>)

     注释4,申请了一个255大小的数组用来接收这个新的data转成的socket的地址,还申请了一个socklen_t变量来接收这个地址结构的大小。

     注释5,这里又申请了一个NSData类的变量peer,在这个注释的if语句的{}里,我们看到它是用来存储我们的新的socket的地址的。我们先来看这个if语句的判断表达式吧,其实这里是一个getpeername()函数的调用,这个函数有3个参数,第一个参数是一个已经连接的socket,这里就是nativeSocketHandle;第二个参数是用来接收地址结构的,就是说这个函数从第一个参数的socket中获取与它捆绑的端口号和地址等信息,并把它存放在这第二个参数中;第三个参数差不多了,它是取出这个socket的地址结构的数据长度放到这个参数里面。如果没有错误的话这个函数会返回0,如果有错误的话会返回一个错误代码。这里判断了getpeername的返回值,没有错误的情况下,把得到的地址结构存储到我们申请的peer里。

     注释6,申请了一对输入输出流,用CFStreamCreatePairWithSocket()方法把我们申请的这一对输入输出流和我们的已建立连接的socket(即现在的nativeSocketHandle)进行绑定,这样我们的这个连接就可以通过这一对流进行输入输出的操作了,这个函数操作完成之后,这两个输入输出流会被重新指向,使其指向有效的地址区域。此函数的的一个参数是一个内存分配器(苹果管理优化内存的一种措施,更多信息可网上查询),第二个参数就是想用我们第三和第四个参数代表的输入输出流的socket,第三和第四个参数就是要绑定到第二个参数表示的socket的输入输出流的地址。

     注释7,如果我们的CFStreamCreatePairWithSocket()方法操作成功的话,那么我们现在的readStream和writeStream应该指向有效的地址,而不是我们在刚申请时赋给的NULL了。此时,判断这两个流是不是NULL就等于说我们判断函数CFStreamCreatePairWithSocket()有没有操作成功。

      如果成功的话,我们就设置这两个流的属性,这里是把这两个流的属性kCFStreamPropertyShouldCloseNativeSocket设置为真,默认情况下这个属性是假的,这个设为真就是说,如果我们的流释放的话,我们这个流绑定的socket也要释放。(这里是对两个流都进行了相同的属性设置,事实上苹果的文档里在对CFStreamCreaterPairWithSocket()这个方法说明的时候提到,大多数流的属性是共享的,你只要对这一对中的一个设置了属性,那么也会自动为另一个流设置这个属性)。然后我们对注释1中得到的TCPServer类的server变量调用handleNewConnectionFromAddress:方法,这个方法的三个参数,一个是已连接socket的地址,另两个就是输入和输出流了。这个方法的内容很简单,在本系列的第一部分我们已经介绍了,这里就不重复了。

      如果失败的话,我们就销毁着了已经连接的socket。

      注释8,这里是先对流的内容进行清空操作,防止在使用它们的时候,里面有我们不需要的垃圾数据。

      真是出了一头汗呀,这个函数太费劲了讲起来⋯⋯好在是说完了,不过,呵呵,下一个函数依然是艰巨呀。

艰苦继续,你敢坚持吗?

     哈哈,又是一个巨大的函数呀,恐怖的start方法,不要害怕,我们会把它分析的支离破碎的哈哈哈,好吧,先看看它的实现:

复制代码
- (BOOL)start:(NSError **)error { 

    CFSocketContext socketCtxt = {0, self, NULL, NULL, NULL};        //1 

    // Start by trying to do everything with IPv6.  This will work for both IPv4 and IPv6 clients
    // via the miracle of mapped IPv4 addresses.   

    witap_socket = CFSocketCreate(kCFAllocatorDefault, PF_INET6, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, (CFSocketCallBack)&TCPServerAcceptCallBack, &socketCtxt);       //2       

        if (witap_socket != NULL) // the socket was created successfully   //3
        {
                protocolFamily = PF_INET6;      //4

        } else // there was an error creating the IPv6 socket - could be running under iOS 3.x   //5
        {      
                witap_socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, (CFSocketCallBack)&TCPServerAcceptCallBack, &socketCtxt);
                if (witap_socket != NULL)
                {
                         protocolFamily = PF_INET;
                }
        } 

    if (NULL == witap_socket) {     //6

        if (error) *error = [[NSError alloc] initWithDomain:TCPServerErrorDomain code:kTCPServerNoSocketsAvailable userInfo:nil];
        if (witap_socket) CFRelease(witap_socket);
        witap_socket = NULL;
        return NO;
    }       

    int yes = 1;  //7
    setsockopt(CFSocketGetNative(witap_socket), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));       

        // set up the IP endpoint; use port 0, so the kernel will choose an arbitrary port for us, which will be advertised using Bonjour
        if (protocolFamily == PF_INET6)  //8
        {
                struct sockaddr_in6 addr6;
                memset(&addr6, 0, sizeof(addr6));
                addr6.sin6_len = sizeof(addr6);
                addr6.sin6_family = AF_INET6;
                addr6.sin6_port = 0;
                addr6.sin6_flowinfo = 0;
                addr6.sin6_addr = in6addr_any;
                NSData *address6 = [NSData dataWithBytes:&addr6 length:sizeof(addr6)];               

                if (kCFSocketSuccess != CFSocketSetAddress(witap_socket, (CFDataRef)address6)) {
                         if (error) *error = [[NSError alloc] initWithDomain:TCPServerErrorDomain code:kTCPServerCouldNotBindToIPv6Address userInfo:nil];
                         if (witap_socket) CFRelease(witap_socket);
                         witap_socket = NULL;
                         return NO;
                }           

                // now that the binding was successful, we get the port number
                // -- we will need it for the NSNetService
                NSData *addr = [(NSData *)CFSocketCopyAddress(witap_socket) autorelease];
                memcpy(&addr6, [addr bytes], [addr length]);
                self.port = ntohs(addr6.sin6_port);      

        } else {             //9
                struct sockaddr_in addr4;
                memset(&addr4, 0, sizeof(addr4));
                addr4.sin_len = sizeof(addr4);
                addr4.sin_family = AF_INET;
                addr4.sin_port = 0;
                addr4.sin_addr.s_addr = htonl(INADDR_ANY);
                NSData *address4 = [NSData dataWithBytes:&addr4 length:sizeof(addr4)];               

                if (kCFSocketSuccess != CFSocketSetAddress(witap_socket, (CFDataRef)address4)) {
                         if (error) *error = [[NSError alloc] initWithDomain:TCPServerErrorDomain code:kTCPServerCouldNotBindToIPv4Address userInfo:nil];
                         if (witap_socket) CFRelease(witap_socket);
                         witap_socket = NULL;
                         return NO;
                }        

                // now that the binding was successful, we get the port number
                // -- we will need it for the NSNetService
                NSData *addr = [(NSData *)CFSocketCopyAddress(witap_socket) autorelease];
                memcpy(&addr4, [addr bytes], [addr length]);
                self.port = ntohs(addr4.sin_port);
        }    

    // set up the run loop sources for the sockets
    CFRunLoopRef cfrl = CFRunLoopGetCurrent();    //10
    CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, witap_socket, 0);
    CFRunLoopAddSource(cfrl, source, kCFRunLoopCommonModes);
    CFRelease(source);
    return YES;
}
复制代码

 

   先看一下这个函数的定义,他返回BOOL类型的值以表示期望的操作是否成功;他还有一个NSError**类型的参数,我们看定义会发现,其实NSError是一个继承者NSObject的类,NSError**是一个指针的指针,就是说error这个参数是一个指针的指针,那么*error是一个指针,*error指向一个NSError对象。

      注释1,我们定义了一个CFSocketContext结构类型变量socketCtxt,并对这个结构进行初始化。我们来解释一下这个结构的定义,这个结构有5个成员。第一个成员是这个结构的版本号,这个必需是0;第二个成员可以是一个你程序内定义的任何数据的指针,这里我们传入的是self,就是这们这个类本身了,所以我们的TCPServerAcceptCallBack这个回调方法可以把它的info参数转成TCPServer,并且这个参数会被传入在这个结构内定义的所有回调函数;第三、四、五这三个成员其实就是3个回调函数的指针,一般我们都设为NULL,就是不用它们。(如果你要具体了解这个结构,请参考官方文档

      注释2,这是创建一个socket并把它赋给我们在TCPServer.h文件里定义的witap_socket变量,这个CFSocketCreate()方法有7个参数之多,我们来一个一个解释吧,你如果想了解的更清楚我建议你还是查阅官方文档。第一个是一个内存分配器(我们前边已经提到过了);第二个是我们要创建的socket的协议族(更多这方面的知识你可能需要参考TCP/IP协议),这里我们是传入PF_INET6(表明我们希望用IPv6协议);第三个参数是socket的类型,我们这里是传入SOCK_STREAM(表明我们是要数据流服务的,还有一种选择是数据报服务,这两种是基于不同的协议的,数据流是基于TCP/IP协议的,数据报是基于UDP协议的);第四个参数是我们要创建的socket所用的具体的协议,这里我们传入IPPROTO_TCP 表明我们是遵守TCP/IP协议的;第五个是回调事件的类型,就是说当这个类型的事件发生时我们的回调函数会被调用,我们这里传入kCFSocketAcceptCallBack表明当连接成功里我们的回调会被触发。(这里可以设置不只一个回调事件类型,多个不同的事件类型用"|"(位或运算符)连起来就可以了) ;第六个就是我们的回调函数的地址,当我们指定的回调事件出现时就调用这个回调函数,我们传入我们的TCPServerAcceptCallBack()回调函数的地址;第七个是一个结构指针,这个结构就是CFSocketContext类型,它保存socket的上下文信息,我们这里传入我们在注释1中定义的socketCtxt的地址。(这个CFSocketCreate()函数会拷贝一份这个结构的数据,所以在出了这个create函数之后,这个结构可以被置为NULL。)

      注释3,我们通过判断这个witap_socket是否为空来判断我们刚才执行的socket创建工作有没有成功。

      注释4,如果我们刚才的创建工作成功了,我们就把我们在TCPServer.h文件中定义的protocolFamily设为PF_INET6。

      注释5,如果刚才的创建失败了,我们再进行一次socket的创建工作,这次和刚才不同的是,这次我们把CFSocketCreate函数的协议族参数设为PF_INET,这表示这次是使用IPv4协议。同样的,在创建操作完成之后判断witap_socket的状态,如果创建成功了就把protocolFamily设为PF_INET,如果又失败了就什么也不做。

      注释6,如果两次创建操作都失败了,如果我们的参数error这个指针的指针是用效的,我们就初始化一个NSError,这个NSError的内容就是我们在TCPServer.h里定义好的错误代码,和在TCPServer.m里定义的常量字符串TCPServerErrorDomain,然后我们把这个NSError对象赋给*error,*error才是一个指向NSError类型的指针;下面一句是如果这个witap_socket不为空,就把这个witap_socket释放了。(我个人认为这句是多余的,这句代码本身就包含在witap_socket是空的if语句里了,还有必要再判断一次吗?),然后按苹果的逻辑,把这个witap_socket释放,之后把它设为NULL,防止野指针。然后返回一个NO,这是告诉我们的这个函数的调用都,我们创建socket失败了。

      注释7,首先是定义了一个int变量yes,并初始化它的值是1。然后调用setsockopt()方法来设置socket的选项,这个方法的第一个参数要求是一个socket的描述符,这里是通过CFSocketGetNative()方法来得到我们的socket对象针对于这个ios平台的描述符;第二个是需要设置的选项定义的层次,这里是SOL_SOCKET(这方面的东西我并不了解,如果你想更多了解,可以参考这篇文章);第三个参数是我们要设置的选项的名字,这里是SO_REUSEADDR。(表示允许重用本地地址和端口,就是说充许绑定已被使用的地址(或端口号),缺省条件下,一个套接口不能与一个已在使用中的本地地址捆绑。但有时会需要“重用”地址。因为每一个连接都由本地地址和远端地址的组合唯一确定,所以只要远端地址不同,两个套接口与一个地址捆绑并无大碍。);第四个参数是一个指针,指向要设置的选项的选项值的缓冲区,这里是传入上面申请的int变量yes的地址,就是说我们把这个选项设为1;第五个参数是这个选项值数据缓冲区的大小,这里用sizeof得友yes的数据长度并传了进去。

      注释8,如果我们的protocolFamily是PF_INET6的话,我们对这个socket进行相应的配置。先是申请一个sockaddr_in6的结构变量addr6,这个结构是一个IPv6协议的地址结构,我们来看一下它的成员定义:           

   

复制代码
struct sockaddr_in6 {
          u_char           sin6_len;     
          u_char           sin6_family;  
          u_int16m_t       sin6_port;    
          u_int32m_t       sin6_flowinfo;
          struct in6_addr  sin6_addr;    
   }
复制代码

 

  我们来看,很明显这个sin6_len是这个结构的大小;sin6_family成员是指的协议;sin6_port指的是端口;sin6_flowinfo这个在微软MSDN上的说明只有一句话,IPv6的流信息;sin6_addr这是一个IN6_ADDR的结构,这个结构是真正存储我们的地址的。

      (对于这个sin6_flowinfo,我并不懂这些协议上的东西,所以我查阅资料上的解释是,sin6_flowinfo是与IPv6新增流标和流量类字段类相对应的一个选项,我们在编程时通常设为0。引自:此文)

      现在让我们来看看这里就做了什么吧,我们先用memset方法把这个刚申请的结构清零;然后把结构的大小赋给了结构成员sin6_len;把结构的协议族设为AF_INET6;这里把这个结构里的端口号设为0,那么在socket进行绑定操作的时候,系统会为我们分配一个任意可用的端口;这个sin6_flowinfo置为0就不说了;把这个地址结构的sin6_addr成员设置为in6addr_any,这里更清晰的解释还是请你查阅资料,这里可以简单理解为填上这个值系统会自动为我们填上一个可用的本地地址(这个一个可用的本地地址是说,有的机器可能会用多个网卡,会有多个地址);然后申请一个NSData变量address6把我们的地址结构addr6的信息进行拷贝存储。

     然后下面,调用CFSocketSetAddress方法把我们的witap_socket和上面刚设置好的地址address6进行绑定,这其实就是BSDSocket里的bind一样的。然后这里判断了绑定操作的执行结果,如果绑定失败的话,就进行相应的清理工作并返回失败(这些具体代码我们之前已经说过了),如果成功的话,就从这个绑定好的socket里拷贝出实际的地址并存储在addr6里,并把在TCPServer.h里定义的属性port设为系统为这个socket分配的实际的端口号,在后面发布NSNetService的时候需要用这个端口号。

     注释9,这个基本上和注释8是一样的,略微不同的是这个是基于IPv4的操作,小小的不同相信只要一看就明白了,我就不再重复说明了吧呵呵。

     注释10,这里申请了一个RunLoop的变量cfrl用来跟踪当前的RunLoop,通过CFRunLoopGetCurrent()方法得到当前线程正在运行的RunLoop,然后把它赋给cfrl;然后创建了一个RunLoop的输入源变量source,这里通过CFSocketCreateRunLoopSource()方法,这个方法的第一个参数是内存分配器,第二个就是我们想要做为输入源来监听的socket对象,第三个参数是代表在RunLoop中处理这些输入源事件时的优先级,数小的话优先级高。然后把这个创建的RunLoop输入源赋给变量source,接着把这个输入源source加入到当前RunLoop来进行监测,把输入源加入到RunLoop是通过CFRunLoopAddSource()这个方法实现的,这个方法的第一个参数就是我们希望加入的RunLoop,第二个参数是要加入到第一个参数里的输入源,这里是source,最后一个参数就是这个我们要加入的输入源要关联的模式,这里是传入的kCFRunLoopCommonModes。(这里的kCFRunLoopCommonModes需要说明,这个kCFRunLoopCommonModes它并不是一个模式,苹果称它为伪模式,它其实是几个模式的合集,kCFRunLoopDefaultMode必定是这个KCFRunLoopCommonModes的一个子集。你可以自己加入一些其它的模式到这个KCFRunLoopCommonModes里,这个通俗点解释怎么说呢,比如说这个KCFRunLoopCommonModes里有两个子集,即有两个模式,我们假设是模式1和模式2,那么当我们把输入源关联到模式的时候传入KCFRunLoopCommonModes的话,这个输入源就会和这两个模式,模式1和模式2,都进行关联,这样不管我们的RunLoop是以模式1运行的还是以模式2运行的,它都会监测我们的这个输入源);加入了输入源之后RunLoop就自动保持了这个输入源,我们现在就可以释放这个输入源了。最后返回操作成功。

 最黑暗的已经过去,准备迎接光明

       终于这个巨无霸方法start告一段落了,剩下的都没有这么长的了,可以稍微放松一下了,来看看简单的stop方法:

复制代码
- (BOOL)stop {
      [self disableBonjour];   //1

        if (witap_socket) {     //2
                CFSocketInvalidate(witap_socket);
                CFRelease(witap_socket);
                witap_socket = NULL;
        }       
    return YES;

}
复制代码

 

  看过了start的庞大,再看看这个stop是不是有点小清新的感觉哈哈哈哈。首先看看它是一个返回BOOL型变量的方法,不过它在任何情况下都返回YES,其实这个返回值就没什么意义了。

     注释1,这是调用它的disableBonjour方法来停止它的NSNetService服务的。(后面我们会讲这个方法)

     注释2,判断这个用来监听网络连接的socket是否有效,如果为真,就先把这个socket设为无效,再释放这个sockt资源,并把它置为NULL。

     让我们看一个stop方法里调用的disableBonjour方法又做了什么:

复制代码
- (void) disableBonjour
{
        if (self.netService) {
                [self.netService stop];
                [self.netService removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
                self.netService = nil;
        }
}
复制代码

 

  它也是很简单的,先判断这个发布的netService服务是不是有效,如果是就先停止这个服务,然后把它从RunLoop里移除使其不再被监听(后面会看到NSNetService也是需要加入RunLoop来进行监测的),然后把这个netService置为NULL。

      下面我们再来看一个比较关键的方法:enableBonjourWithDomain方法:

复制代码
- (BOOL) enableBonjourWithDomain:(NSString*)domain applicationProtocol:(NSString*)protocol name:(NSString*)name
{

        if(![domain length])    //1
                domain = @""; //Will use default Bonjour registration doamins, typically just ".local"
        if(![name length])
                name = @""; //Will use default Bonjour name, e.g. the name assigned to the device in iTunes       

        if(!protocol || ![protocol length] || witap_socket == NULL)   //2
                return NO; 

       self.netService = [[NSNetService alloc] initWithDomain:domain type:protocol name:name port:self.port];   //3
        if(self.netService == nil)
                return NO;       

        [self.netService scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];  //4
        [self.netService publish];
        [self.netService setDelegate:self];
        return YES;
}
复制代码

 

  这个方法也用一个BOOL的返回值表示操作的成功或失败。并且这个方法有三个参数,它们都是我们用来发布NSNetService服务时要用的参数。第一个参数是发布服务用的域,第二个参数是我们要发布的网络服务的类型信息,第三个参数是用来表示我们这个服务的名字。

     注释1,如果我们的参数domain和name如果字符长度是0的话,就把它们设为@“”。(等下会解释这个@“”在这里的意义)

     注释2,如果参数protocol,即服务的协议,如果它不存在,或者它的字符长度为0,又或者witap_socket为NULL(也就是说我们这个用来监听的socket无效的话),直接返回失败。

     注释3,这里是初始化一个NSNetService服务,并把它赋给在TCPServer.h里定义的netService属性。这个NSNetService的初始化方法用到了4个参数,前3个就是我们现在正在解释的这个方法的3个参数,最后一个是我们之前得到的端口号。第一个参数domain,它代表我们发布服务用的域,本地域是用@"local."来表示的,但是当我们想用本地域的时候,是不用直接传这个@"local."字符串进去的,我们只要传@""进去就行了,系统会自己把它按成本地域来使用的;第二个参数这个网络服务的类型,这个类型必需包含服务类型和传输层信息(传输层概念请参考TCP/IP协议),这个服务类型的名字和传输层的名字都要有“_”字符作为前缀。比如这个例子的服务类型的完整的名字其实是@"_witap._tcp.",看到了吧,它们都有前缀"_",这里还有一点是要强调的,在这字符串结尾的"."符号是必需的,它表示这个域名字是绝对的;第三个参数是这个服务的名字,这个名字必需是唯一的,如果这个名字是@""的话,系统会自动把设备的名字作为这个服务的名字;第四个参数就是端口号了,这是我们发布这个服务用的。这个端口号必须是在应用程序里为这个服务获得的,这里就是witap_socket在绑定时我们获得的那个端口号,获得之后赋给了port属性,所以这里传入的是self.port。初始化之后,把这个初始化过的NSNetService赋给.h文件里定义的netService属性,接着通过判断这个netService属性是否有效来判断这个NSNetService的初始化是否成功。如果初始化失败的话,直接返回操作失败。

      注释4,对这个netService属性调用scheduleInRunLoop:forMode:方法,从名字也能看得出来,这是把这个netService加入到当前RunLoop中,并关联到相应的模式。这部分内容在前面的start方法的注释10中已经有了详细的讲述,这里就不啰嗦了。然后对这个netService调用publish方法,这个方法是真正的发布我们的服务的。接着又设置这个netService的委托为这个TCPService类本身,netService是一个NSNetService类,这个类的委托是一个符合NSNetServiceDelegate的通用类型,在TCPService类的interface声明部分,我们看到这个类是声明自己符合NSNetServiceDelegate这个协议的,所以这里可以把netService的委托设为这个TCPService类本身。最后,返回操作成功。

柳暗花明

      到了这一步,这个类剩下的内容就很明了了,让我们看看NSNetServiceDelegate协议的两个方法吧:

复制代码
- (void)netServiceDidPublish:(NSNetService *)sender
{
    if (self.delegate && [self.delegate respondsToSelector:@selector(serverDidEnableBonjour:withName:)])
                [self.delegate serverDidEnableBonjour:self withName:sender.name];
}

- (void)netService:(NSNetService *)sender didNotPublish:(NSDictionary *)errorDict
{
        if (self.delegate && [self.delegate respondsToSelector:@selector(server:didNotEnableBonjour:)])
                [self.delegate server:self didNotEnableBonjour:errorDict];
}
复制代码

  

  这是NSNetServiceDelegate协议的其中两个方法,一个会在NSNetService发布成功时被调用,一个会在发布失败时会调用。它们都有一个参数,这个参数是触发这个回调的NSNetService本身。两个方法的内容都很简单。

      发布成功方法中先判断self的委托是否有效,如果有效的话它接着判断这个委托是不是响应serverDidEnableBonjour:withName方法,如果响应的话,就对它的委托调用这个方法。在这个系列的第一部分的setup方法的注释4中,我们已经解释过了这里的self的委托被设置为AppController了,所以这里是判断AppController是不是响应这个serverDidEnableBonjour:withName方法,如果响应就对它调用这个方法。(关于这个被调用的方法我们在以后讲回到AppController时再讲)

     发布失败方法和这个成功方法基本上一样,不同的只是对应的方法,也不再重复说明了。

     最后是两个辅助性的方法: 

复制代码
- (NSString*) description
{
        return [NSString stringWithFormat:@"<%@ = 0x%08X | port %d | netService = %@>", [self class], (long)self, self.port, self.netService];
}

+ (NSString*) bonjourTypeFromIdentifier:(NSString*)identifier
 {
    if (![identifier length])
         return nil; 
  
    return [NSString stringWithFormat:@"_%@._tcp.", identifier];
}
复制代码

   description方法是帮助我们调试程序时方便用的,它把这个类的信息返回给我们,方便我们输出到控制台进行查看。它把这个TCPService类对象的类名,地址,port属性,netService属性都返回了,非常方便。

      bonjourTypeFromIdentifier:这个方法真是一个纯粹的辅助方法,它在这个例子中的用途就是通过传入在AppController.m中定义的宏kGameIdentifier,然后返回一个完整的事实上的NSNetService的初始化方法中用的网络服务的类型。

黎明即将到来

     至此,这个TCPService类我们介绍完了,这篇文章就到这里,后面的内容会在后续文章中继续讲述。


http://www.cnblogs.com/dingwenjie/archive/2012/04/12/2439991.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值