CFSocket
CFSocket
是BSD 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中;
相比底层socket
,CFSokcet
可以实现异步回调的形式读写数据、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
};
CFNetwork
CFNetworkd
网络框架结构层是构建在基于BSD sockets
的Core Foundataion
框架层中的CFSocket
及CFStream
,是一个低级但高性能的网络框架,提供CFSocketStrem
、CFFTP
、CFHTTP
及CFHTTPAuthentication
高级封装API;
相比原始的socket
,CFNetworkd
的优势就是被集成到系统级的设置及运行循环中,沿着框架层次越往上就越能获得更好的服务,比如必要时开启蜂窝无线及通过系统范围的VPN进行路由等。
SimplePing
Apple 的 SimplePing 封装了 ping 的功能,它利用 resolve host,create socket(send & recv data), 解析 ICMP 包验证 checksum 等实现了 ping 功能,并且支持 iPv4 和 iPv6。
通过CFHost
类来异步解析host地址,解析成功后创建数据报ICMP协议的socket
,并创建关联该socket
的CFSocket
对象,并将该对象添加到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
);
}
参考资料
《iOS网络高级编程》
Demo
https://github.com/FengyunSky/notes/blob/master/local/code/cfsocketdemo.tar
网友评论