iOS精选

iOS网络编程

2020-04-11  本文已影响0人  FengyunSky

CFSocket

CFSocketBSD socket的抽象和封装,提供了几乎所有socket的功能,并与run loop集成,用来实现多线程网络编程和网络事件监听;基于 CFSocket可以实现各种类型的 socket编程,包括stream-based 的sockets(如tcp)和packet-based 的sockets(如udp)。需要注意的是在iOS中CFSocket接口在需要时不自动激活设备的 cellular modem或on-demand VPN。

具体的使用接口如下:

/**** 创建 ******/
//创建cfsocket对象
//allocator,分配对象内存的类型,默认NULL或者kCFAllocatorDefault 选择默认分配类型,其他还有kCFAllocatorSystemDefault kCFAllocatorMalloc等
//protocolFamily, 协议族类型,PF_INET(默认) 或者PF_INET6,如果为0或者负数,则为默认
//socketType, socket类型,SOCK_STREAM(默认)或者SOCK_DGRAM,负数或者0,则默认
//protocol, socket协议类型,IPPROTO_TCP(默认)或者IPPROTO_UDP,负数或者0,则默认
//callBackTypes,回调触发类型,见CFSocketCallBackType,可以使用位或(|)组合类型,如kCFSocketConnectCallBack|kCFSocketAcceptCallBack
//callout,回调函数
//context, 保存CFSocket对象上下文信息的结构。函数将信息从结构中复制出来,因此上下文所指向的内存不需要在函数调用之外持久化。可以为空(NULL)
/*
typedef struct {
    CFIndex version; //版本号必须位0
    void *  info;//指向程序定义数据的任意指针,该数据可以在创建时与CFSocket对象关联。这个指针被传递给上下文中定义的所有回调
    const void *(*retain)(const void *info);//可为NULL
    void    (*release)(const void *info);//可为NULL
    CFStringRef (*copyDescription)(const void *info);//可为NULL
} CFSocketContext;
*/
CFSocketRef CFSocketCreate(CFAllocatorRef allocator, 
                           SInt32 protocolFamily, 
                           SInt32 socketType, 
                           SInt32 protocol, 
                           CFOptionFlags callBackTypes, 
                           CFSocketCallBack callout, 
                           const CFSocketContext *context);
                                                                                                //返回CFSocketRef对象或者NULL(失败)
//该接口根据一个包含通讯协议和地址的CFSocketSignature结构来创建一个CFSocket对象
/*
和CFSocketCreate()函数类似,只不过使用const CFSocketSignature *signature参数来代替:protocolFamily、socketType、protocol
typedef struct {
    SInt32  protocolFamily;
    SInt32  socketType;
    SInt32  protocol;
    CFDataRef   address;//用于标示socket的地址,通过sockaddr转换的CFDataRef对象
} CFSocketSignature
*/
CFSocketRef CFSocketCreateWithSocketSignature(CFAllocatorRef allocator, const CFSocketSignature *signature, CFOptionFlags callBackTypes, CFSocketCallBack callout, const CFSocketContext *context)
//该接口在创建一个CFSocket对象的同时还与一个远端主机进行连接
CFSocketRef CFSocketCreateConnectedToSocketSignature()
//该接口通过封装一个存在的 BSD socket来创建一个CFSocket对象
CFSocketRef CFSocketCreateWithNative()

//绑定套接字地址并监听,默认256监听连接数
CFSocketError   CFSocketSetAddress(CFSocketRef s, CFDataRef address);
//指定连接服务器并连接,若设定超时时间>0并且创建socket时指定了kCFSocketConnectCallBack,则会以回调的形式返回结果;否则会
CFSocketError   CFSocketConnectToAddress(CFSocketRef s, CFDataRef address, CFTimeInterval timeout);
//停止socket数据接收/发送,但不会释放socket对象;若指定了socket source,则会runloop source失效;
//若创建sokcet时指定CFSocketContext 中的release回调,则会主动调用该回调;
//默认调用该函数会关闭管理的底层socket,
//若CFSocketSetSocketFlags函数指定了kCFSocketCloseOnInvalidate,需要手动去关闭socket
void        CFSocketInvalidate(CFSocketRef s);
//获取socket是否失效状态
Boolean CFSocketIsValid(CFSocketRef s);
//复制本地连接地址
CFDataRef   CFSocketCopyAddress(CFSocketRef s);
//复制远端连接地址
CFDataRef   CFSocketCopyPeerAddress(CFSocketRef s);
//获取socket上下文
void        CFSocketGetContext(CFSocketRef s, CFSocketContext *context);
//获取系统socket描述符
CFSocketNativeHandle    CFSocketGetNative(CFSocketRef s);
//创建runloop socket source,需要使用CFRunLoopAddSource添加到runloop中
CFRunLoopSourceRef  CFSocketCreateRunLoopSource(CFAllocatorRef allocator, CFSocketRef s, CFIndex order);
//获取socket选项
CFOptionFlags   CFSocketGetSocketFlags(CFSocketRef s);
void        CFSocketSetSocketFlags(CFSocketRef s, CFOptionFlags flags);
void        CFSocketDisableCallBacks(CFSocketRef s, CFOptionFlags callBackTypes);
void        CFSocketEnableCallBacks(CFSocketRef s, CFOptionFlags callBackTypes);
//指定连接地址发送数据(若socket已连接,则置为null),超时时间为正数才会生效
CFSocketError   CFSocketSendData(CFSocketRef s, CFDataRef address, CFDataRef data, CFTimeInterval timeout);

注意:kCFSocketAcceptCallBack指定接受连接回调函数,accept返回的关联底层socket为data参数,且该参数需要copy复制,避免野指针;客户端/服务端都需要添加cfsocket runloop source 到runloop中;

相比底层socketCFSokcet可以实现异步回调的形式读写数据、connect连接、accept接收连接等事件回调;

CFStream

读写流,提供一种简单的方法进行媒体数据的交换,与设备无关。你可以为内存中、文件中或者网络中的数据创建流,并且你可以在不把数据加载到内存中的情况下使用流。流是一个字节序列串行传输的通信路径,流是单向的,通常情况下,为了双向通信,需要输入(CFReadStream)、输出流(CFWriteStream)。除了基于文件的流,你不能寻找一个流,一旦数据流被提供或者被消耗,就不能从流中重新取出。

CFReadStreamRef rdstream = NULL;
//创建关联socket的读写流
CFStreamCreatePairWithSocket(kCFAllocatorDefault, sockfd, &rdstream, NULL);
//打开流
CFReadStreamOpen(rdstream);
//异步事件通知读取写流
CFStreamClientContext context = {0, NULL, NULL, NULL, NULL};//持有上下文信息用于回调,可传递self
CFReadStreamSetClient(rdstream, kCFStreamEventHasBytesAvailable, _readStream, &context);
//将流添加到runloop循环中
CFReadStreamScheduleWithRunLoop(rdstream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);

提供了关联socket的读写流,且可通过CFReadStreamSetClient或者CFWriteStreamSetClient异步观察流的回调事件机制,不过需要调用CFReadStreamScheduleWithRunLoop添加到runloop中;

相比dispatch source观察底层socket读写事件block形式,回调的形式容易使代码不够紧凑,不过流的形式封装了各种事件,如下:

typedef CF_OPTIONS(CFOptionFlags, CFStreamEventType) {
    kCFStreamEventNone = 0,
    kCFStreamEventOpenCompleted = 1,
    kCFStreamEventHasBytesAvailable = 2,
    kCFStreamEventCanAcceptBytes = 4, 
    kCFStreamEventErrorOccurred = 8,
    kCFStreamEventEndEncountered = 16
};

iOS Socket(3) —— CFSocket的使用

CFNetwork

CFNetworkd网络框架结构层是构建在基于BSD socketsCore Foundataion框架层中的CFSocketCFStream,是一个低级但高性能的网络框架,提供CFSocketStremCFFTPCFHTTPCFHTTPAuthentication高级封装API;

相比原始的socketCFNetworkd的优势就是被集成到系统级的设置及运行循环中,沿着框架层次越往上就越能获得更好的服务,比如必要时开启蜂窝无线及通过系统范围的VPN进行路由等。

image.png

SimplePing

Apple 的 SimplePing 封装了 ping 的功能,它利用 resolve host,create socket(send & recv data), 解析 ICMP 包验证 checksum 等实现了 ping 功能,并且支持 iPv4 和 iPv6。

通过CFHost类来异步解析host地址,解析成功后创建数据报ICMP协议的socket,并创建关联该socketCFSocket对象,并将该对象添加到runloop中,以实现异步回调的形式recvfrom读取套接字,并通过sendto发送组装ICMP协议的数据报;

socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);//不能使用原始套接字,需要root权限,苹果提供了基于数据报的ICMP协议socket

核心代码如下:

//解析host地址
{
    Boolean             success;
    CFHostClientContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
    CFStreamError       streamError;
    //创建cfhost
    self.host = (CFHostRef)CFAutorelease(CFHostCreateWithName(NULL, (__bridge CFStringRef) self.hostName));
    if (self.host == NULL) {
        // host NULL; do nothing.
        return;
    }
    //关联cfhost到client context并设置回调
    CFHostSetClient(self.host, HostResolveCallback, &context);
    //添加到runloop中
    CFHostScheduleWithRunLoop(self.host, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
    //解析host地址
    success = CFHostStartInfoResolution(self.host, kCFHostAddresses, &streamError);
    if (!success) {
        [self didFailWithHostStreamError:streamError];
    }
}

//创建socket及关联的cfsocket对象,观察
{
    //创建socket
    fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
    
    CFSocketContext         context = {0, (__bridge void *)(self), NULL, NULL, NULL};
    CFRunLoopSourceRef      rls;
    id<PingFoundationDelegate>  strongDelegate;

    // Wrap it in a CFSocket and schedule it on the runloop.
    self.socket = (CFSocketRef) CFAutorelease( CFSocketCreateWithNative(NULL, fd, kCFSocketReadCallBack, SocketReadCallback, &context) );

    // The socket will now take care of cleaning up our file descriptor.
    fd = -1;
    rls = CFSocketCreateRunLoopSource(NULL, self.socket, 0);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
    CFRelease(rls);
}
//读取socket数据
{
    recvfrom(CFSocketGetNative(self.socket), buffer, kBufferSize, 0, (struct sockaddr *) &addr, &addrLen);
    //校验数据
    [self validatePingResponsePacket:packet sequenceNumber:&sequenceNumber];
    //回调给代理
    [strongDelegate pingFoundation:self didReceivePingResponsePacket:packet sequenceNumber:sequenceNumber];
}
//发送数据
{
  //组装icmp协议数据
  
  //sendto发送
  sendto(CFSocketGetNative(self.socket),packet.bytes,packet.length,0,self.hostAddress.bytes,
    (socklen_t) self.hostAddress.length
  );
}

参考资料

CFNetwork Programming Guide

CFSocket

CFSokcet.c

《iOS网络高级编程》

Demo

https://github.com/FengyunSky/notes/blob/master/local/code/cfsocketdemo.tar

上一篇下一篇

猜你喜欢

热点阅读