美文网首页
iOS_Bonjour编程总结一

iOS_Bonjour编程总结一

作者: 面试小集 | 来源:发表于2017-05-03 08:26 被阅读482次

Bonjour 简介

Bonjour是这样的一种技术,设备可以通过它轻松探测并连接到相同网络中的其他设备,整个过程只需要很少的用户参与或是根本就不需要用户参与。典型的Bonjour应用有Remote应用,AirPrint等。建立一个Bonjour连接一般需要三个步骤,服务端发布服务,客户端浏览服务,客户端服务端交互。

发布服务

1. 创建socket

demo代码:

-(BOOL)setupListeningSocket
{
    CFSocketContext socketCtxt = {0,(__bridge void*)self, NULL, NULL, NULL};
    
    ipv4socket = CFSocketCreate(kCFAllocatorDefault, 
                                PF_INET, 
                                SOCK_STREAM, 
                                IPPROTO_TCP, 
                                kCFSocketAcceptCallBack, 
                                (CFSocketCallBack)&BonjourServerAcceptCallBack, 
                                &socketCtxt);
    
    if (ipv4socket == NULL) {
        if (ipv4socket) {
            CFRelease(ipv4socket);
        }
        ipv4socket = NULL;
        return NO;
    }
    
    int yes = 1;
    setsockopt(CFSocketGetNative(ipv4socket),
               SOL_SOCKET,
               SO_REUSEADDR,
               (void *)&yes,
               sizeof(yes));
    
    struct sockaddr_in addr4;
    memset(&addr4, 0, sizeof(addr4));
    addr4.sin_len = sizeof(addr4);
    addr4.sin_family = AF_INET;
    addr4.sin_port = htons(port);
    addr4.sin_addr.s_addr = htonl(INADDR_ANY);
    NSData *address4 = [NSData dataWithBytes:&addr4 length:sizeof(addr4)];
    
    if (kCFSocketSuccess != CFSocketSetAddress(ipv4socket, (__bridge CFDataRef)address4)) {
        NSLog(@"Error setting ipv4 socket address");
        if (ipv4socket) {
            CFRelease(ipv4socket);
        }
        ipv4socket = NULL;
        return NO;
    }
    
    if (port == 0) {
        NSData *addr = (__bridge NSData*)CFSocketCopyAddress(ipv4socket);
        memcpy(&addr4, [addr bytes], [addr length]);
        port = ntohs(addr4.sin_port);
    }
    
    CFRunLoopRef cfr1 = CFRunLoopGetCurrent();
    CFRunLoopSourceRef src4 = CFSocketCreateRunLoopSource(kCFAllocatorDefault, ipv4socket, 0);
    CFRunLoopAddSource(cfr1, src4, kCFRunLoopCommonModes);
    CFRelease(src4);
    
    return YES;
}

代码解析

CFSocketContext

是一个结构体,包含了自定义数据和回调函数,可以在其中操作CFSocket对象的具体行为。

typedef struct {
    CFIndex version;
    void *  info;
    const void *(*retain)(const void *info);
    void    (*release)(const void *info);
    CFStringRef (*copyDescription)(const void *info);
} CFSocketContext;

version: 必须是0, 结构体版本号。

info: 指向自定义数据的指针,它会在CFSocket创建的时候与之关联,这个指针会被传递给所有定义在context内的回调方法。

retain: 一个定义在info指针上的retain 回调。可以是NULL。

release: 一个定义在info指针上的relsease回调。可以是NULL。

copyDescription: 一个定义在info指针上的拷贝描述回调。可以是NULL。

CFSocketCreate

CFSocketCreate(CFAllocatorRef allocator, 
               SInt32 protocolFamily, 
               SInt32 socketType, 
               SInt32 protocol, 
               CFOptionFlags callBackTypes, 
               CFSocketCallBack callout, 
               const CFSocketContext *context);

创建一个指定协议和类型的CFSocket对象。

allocater: 分配器是用来为新对象分配内存的,传递NULL或者KCFAllocatorDefault 使用当前默认的分配器。

protocolFamily: socket的协议族,如果为负数或者0,则socket默认为PE_INET。

socketType: 所创建的Socket的类型,如果protocolFamily是PE_INET并且socketType是负数或者0,socketType的默认值是SOCK_STREAM。

protocol: socket的协议。如果protocolFamily是PE_INET并且protocol是负数或者0,那么socket的protocol的默认值是IPPROTO_TCP。如果socketType是SOCK_STREAM或者SOCK_DGRAM那么默认为IPPROTO_UDP。

callBackTypes: 一个按位或结合的socket类型,会调起socket的callout.

typedef enum CFSocketCallBackType : CFOptionFlags {
    kCFSocketNoCallBack = 0,
    kCFSocketReadCallBack = 1,
    kCFSocketAcceptCallBack = 2,
    kCFSocketDataCallBack = 3,
    kCFSocketConnectCallBack = 4,
    kCFSocketWriteCallBack = 8
} CFSocketCallBackType;

callout: 当一种callBackTypes被激活时这个方法被调用。

context:一个保存着CFSocket对象上下文信息的结构体。函数将信息拷贝出结构体之外,所以上下文指向的内存不需要超出函数的调用,可以是NULL。

setsockopt^参考1^

int setsockopt(int s, 
               int level, 
               int optname, 
               const void * optval,
               socklen_toptlen);

用来设置参数 s 所指定的socket状态。参数 level 代表代表预设置的网络层。一般设置为SOL_SOCKET 以存取socket层。参数 optname 代表欲设置的选项:

​ SO_DEBUG 打开或者关闭排错模式。

​ SO_REUSEADDR 允许在bind ()过程中本地地址可重复使用

​ SO_TYPE 返回socket 形态.

​ SO_ERROR 返回socket 已发生的错误原因

​ SO_DONTROUTE 送出的数据包不要利用路由设备来传输.

​ SO_BROADCAST 使用广播方式传送

​ SO_SNDBUF 设置送出的暂存区大小

​ SO_RCVBUF 设置接收的暂存区大小

​ SO_KEEPALIVE 期确定连线是否已终止.

​ SO_OOBINLINE 当接收到OOB 数据时会马上送至标准输入设备

​ SO_LINGER:确保数据安全且可靠的传送出去.

参数 optval 代表欲设置的值, 参数optlen 则为optval 的长度.

返回值:成功则返回0, 若有错误则返回-1, 错误原因存于errno.

CFSocketGetNative

返回系统原生socket, 如果返回值为-1,表示无效的socket

sockaddr_in6

struct sockaddr_in {
    __uint8_t   sin_len;
    sa_family_t sin_family;
    in_port_t   sin_port;
    struct  in_addr sin_addr;
    char        sin_zero[8];
};

sin_family: 指协议族,在socket编程中只能是AF_INET。

sin_port: 存储端口号,使用网络字节顺序。

size_zero: 是为了让sockaddr与sockadrr_in 两个数据结构保持大小相同而保留的空字节。

sin_addr: 网络地址。

sin_len: 根据《UNIX Network Programming Volume 1》3.1节中的说法,我们可以不关注这个细节(即可以认为这个sin_len字段存在与否对我们的应用程序是透明的)。这个字段不是每种Linux版本都提供,且POSIX标准中对struct sockaddr_in的定义是否需包含该字段不做要求。

2. 发布Bonjour服务

-(void)publicBonjour {
    service = [[NSNetService alloc] 
               initWithDomain:@"" 
               type:@"_riverli._tcp." 
               name:@"riverliBonjour" 
               port:port];
    if (service == nil) {
        NSLog(@"NSNetService create failed!");
        return ;
    }
    service.delegate = self;
    [service publish];
}

#pragma mark  NSNetServiceDelegate
- (void)netServiceWillPublish:(NSNetService *)sender {
    NSLog(@"netServiceWillPublish");
}

- (void)netServiceDidPublish:(NSNetService *)sender {
    NSLog(@"netServiceDidPublish");
}

- (void)netService:(NSNetService *)sender didNotPublish:(NSDictionary<NSString *, NSNumber *> *)errorDict {
    NSLog(@"didNotPublish");
}

- (void)netServiceDidStop:(NSNetService *)sender {
    port = 0;
    CFRelease(ipv4socket);
    NSLog(@"netServiceDidStop");
}

3. 接受socket 回调

这部分可能为三个步骤:

  1. 在第一步创建的CFSocketCallBack对象中有接收到socket消息的回调函数BonjourServerAcceptCallBack,我们在这个回调函数中拿到当前的Bonjour服务。
  2. 如果调用类型是kCFSocketAcceptCallBack,表示接受到了一个新的连接,在这里我们创建NSStream的读写对象。
  3. 在NSStream的读写对象里,我们接受客户的信息,并将信息发送给客户端。(关于NSStream的介绍可以参考这里)
static void BonjourServerAcceptCallBack (CFSocketRef socket, 
                                         CFSocketCallBackType type, 
                                         CFDataRef address, 
                                         const void *data, 
                                         void *info) {
    
    Bonjour *server = (__bridge Bonjour*)info;
    if (type == kCFSocketAcceptCallBack) { 
        // AcceptCallBack: data is pointer to a CFSocketNativeHandle
        CFSocketNativeHandle socketHandle 
            = *(CFSocketNativeHandle *)data;

        CFReadStreamRef readStream = NULL;
        CFWriteStreamRef writeStream = NULL;
        CFStreamCreatePairWithSocket(kCFAllocatorDefault, 
                                     socketHandle, 
                                     &readStream, 
                                     &writeStream);
        
        if (readStream && writeStream) {
            CFReadStreamSetProperty
                (readStream, 
                 kCFStreamPropertyShouldCloseNativeSocket, 
                 kCFBooleanTrue);
            
            CFWriteStreamSetProperty
                (writeStream, 
                 kCFStreamPropertyShouldCloseNativeSocket, 
                 kCFBooleanTrue);
            
            NSInputStream *is = (__bridge NSInputStream*)readStream;
            NSOutputStream *os = (__bridge NSOutputStream*)writeStream;
            [server handleNewConnectionWithInputStream:is
                                          outputStream:os];
        } else {
            // encountered failure
            // no need for socket anymore
            close(socketHandle);
        }
        // clean up
        if (readStream) {
            CFRelease(readStream);
        }
        if (writeStream) {
            CFRelease(writeStream);
        }
    }
}

- (void)handleNewConnectionWithInputStream:(NSInputStream*)istr 
                              outputStream:(NSOutputStream*)ostr {
    inputStream = istr;
    outputStream = ostr;
    
    inputStream.delegate = self;
    outputStream.delegate = self;
    
    [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] 
                           forMode:NSDefaultRunLoopMode];
    // output stream is scheduled in the runloop when it is needed

    
    if (inputStream.streamStatus == NSStreamStatusNotOpen) {
        [inputStream open];
    }
    
    if (outputStream.streamStatus == NSStreamStatusNotOpen) {
        [outputStream open];
    }
}

#pragma mark - NSStreamDelegate
- (void)stream:(NSStream *)aStream 
   handleEvent:(NSStreamEvent)eventCode {
    
    switch (eventCode) {
        case NSStreamEventHasBytesAvailable:
            if (aStream == inputStream) {
                //接收数据
            }
            break;
        
        case NSStreamEventHasSpaceAvailable: {
            if (aStream == outputStream) {
               //发送数据
            }
            break;
        }
        case NSStreamEventOpenCompleted:
            if (aStream == inputStream) {
                NSLog(@"Input Stream Opened");
            } else {
                NSLog(@"Output Stream Opened");
            }
            break;
            
        case NSStreamEventEndEncountered: {
            [aStream close];
            [aStream removeFromRunLoop:[NSRunLoop currentRunLoop] 
                               forMode:NSDefaultRunLoopMode];
            break;
        }
        
        case NSStreamEventErrorOccurred:
            if (aStream == inputStream) {
                NSLog(@"Input error: %@", [aStream streamError]);
            } else {
                NSLog(@"Output error: %@", [aStream streamError]);
            }
            break;
            
        default:
            if (aStream == inputStream) {
                NSLog(@"Input default error: %@", [aStream streamError]);
            } else {
                NSLog(@"Output default error: %@", [aStream streamError]);
            }
            break;
    }
}

参考资料

参考一:c语言中文网

参考二:slvher的博客

参考三:Apple : Stream Programming Guide

参考四:iOS网络高级编程

交流群

移动开发交流群:264706196

相关文章

  • iOS_Bonjour编程总结二

    简介 本文紧接上文iOS_Bonjour编程总结一,进一步描述Bonjour编程中客户端编写部分。 1. 浏览服务...

  • iOS_Bonjour编程总结一

    Bonjour 简介 Bonjour是这样的一种技术,设备可以通过它轻松探测并连接到相同网络中的其他设备,整个过程...

  • iOS 图形编程总结

    iOS 图形编程总结 iOS 图形编程总结

  • iOS 图形编程总结

    图形编程总结

  • Spring Aop初认识(04)

    Spring AOP编程 一,概述: 1.1 手动实现aop编程 UserDao.java 分析总结: 如何分离?...

  • 9.25 c++ 总结

    Windows编程基础 总结: 25号开始学习Windows编程基础,MFC编程。Windows和MFS中的字符,...

  • 编程学习总结<一>

    1,空杯心态,头脑开放 打破之前的认知,保持谦虚和求知欲。一个特定的功能实现的方式可以是很多种,不妨暂时抛弃自己想...

  • 10个超棒的用于数学的编程语言。

    总结的10个超棒的用于数学的编程语言。 总结的目标在于多样化,为大家提供广泛的编程和问题建模方法。 编程语言描述摘...

  • Python 高级 16 数据库Pymysql

    数据库编程概述、pymysql基本操作方法总结、参数化列表防止SQL注入总结 2.6 Python数据库编程 学习...

  • 编程思想总结

    1、编程中思想 OOP:面向对象编程,用程序总结归纳生活中一切事物。 封装,继承,多态。 BOP:面向Bean编程...

网友评论

      本文标题:iOS_Bonjour编程总结一

      本文链接:https://www.haomeiwen.com/subject/yhcktxtx.html