CFSocket
CFSocket是对底层BSD Socket的封装,使用CFSocket的API需要引入以下头文件:
#import <CoreFoundation/CoreFoundation.h>
#include <sys/socket.h>
#include <netinet/in.h>
复制代码
CFSocket客户端
-
CFSocket创建一个Socket
- 使用CFSocketCreate()或者CFSocketCreateWithSocketSignature()
1、CFSocketRef CFSocketCreate(CFAllocatorRef allocator, SInt32 protocolFamily, SInt32 socketType, SInt32 protocol, CFOptionFlags callBackTypes, CFSocketCallBack callout, const CFSocketContext *context);
参数说明:
allocator: 对象内存的分配类型,一般设置为NULL或者kCFAllocatorDefault使用默认分配类型。
protocolFamily:协议族。如果传递负数或0,默认为PF_INET(ip4),ip6是PF_INET6
socketType:socket类型,TCP用流式:SOCK_STREAM,UDP用报文式:SOCK_DGRAM,如果protocolFamily为PF_INET, socketType为负数或0,则默认为SOCK_STREAM.
protocol:socket传输协议,如果之前用的是流式套接字类型:PPROTO_TCP,如果是报文式:IPPROTO_UDP。如果protocolFamily为PF_INET,协议为负或0,那么如果socketType为SOCK_STREAM,套接字协议默认为IPPROTO_TCP;如果socketType为SOCK_DGRAM,套接字协议默认为IPPROTO_UDP
callBackTypes:回调事件触发类型:
typedef CF_OPTIONS(CFOptionFlags, CFSocketCallBackType) {
kCFSocketNoCallBack = 0,
kCFSocketReadCallBack = 1,
kCFSocketAcceptCallBack = 2,
kCFSocketDataCallBack = 3,
kCFSocketConnectCallBack = 4,
kCFSocketWriteCallBack = 8
}
可以使用位或(|)组合类型。
callout:回调函数,当callBackTypes对应的活动之一发生时调用的函数。
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,当创建错误时,返回NULL。
例子:
void ServerConnectCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void * data, void *info) {
if (data != NULL) {
NSLog(@"connect\n");
} else {
NSLog(@"connect success\n");
}
}
CFSocketContext sockContext = {0,(__bridge void *)(self),NULL,NULL,NULL};
_socketRef = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketConnectCallBack,ServerConnectCallBack, &sockContext);
2、CFSocketRef CFSocketCreateWithSocketSignature(CFAllocatorRef allocator, const CFSocketSignature *signature, CFOptionFlags callBackTypes, CFSocketCallBack callout, const CFSocketContext *context)
参数说明:
和CFSocketCreate()函数类似,只不过使用const CFSocketSignature *signature参数来代替:protocolFamily、socketType、protocol
typedef struct {
SInt32 protocolFamily;
SInt32 socketType;
SInt32 protocol;
CFDataRef address;//用于标示socket的地址,通过sockaddr转换的CFDataRef对象
} CFSocketSignature
例子:
CFSocketSignature signature;
signature.protocolFamily = AF_INET;
signature.socketType = SOCK_STREAM;
signature.protocol = IPPROTO_TCP;
_socketRef = CFSocketCreateWithSocketSignature(kCFAllocatorDefault, &signature, kCFSocketConnectCallBack, ServerConnectCallBack, &sockContext);
复制代码
- 使用CFSocketCreateWithNative()通过已经存在的BSD socket来创建一个socket。
CFSocketRef CFSocketCreateWithNative(CFAllocatorRef allocator, CFSocketNativeHandle sock, CFOptionFlags callBackTypes, CFSocketCallBack callout, const CFSocketContext *context);
参数说明:
其中allocator、callBackTypes、callout、context参数和CFSocketCreate()一样。
sock:现存的BSD socket。
例子:
CFSocketNativeHandle sock = socket(AF_INET, SOCK_STREAM, 0);
CFSocketContext sockContext = {0,(__bridge void *)(self),NULL,NULL,NULL};
CFSocketCreateWithNative(kCFAllocatorDefault, sock, kCFSocketConnectCallBack, ServerConnectCallBack, &sockContext);
复制代码
- 使用CFSocketCreateConnectedToSocketSignature(),创建一个socket并且连接远程主机。
CFSocketRef CFSocketCreateConnectedToSocketSignature(CFAllocatorRef allocator, const CFSocketSignature *signature, CFOptionFlags callBackTypes, CFSocketCallBack callout, const CFSocketContext *context, CFTimeInterval timeout);
例子:
struct sockaddr_in addr;
//清空指向的内存中的存储内容,因为分配的内存是随机的
memset(&addr, 0, sizeof(addr));
// loc_addr.sin_len = sizeof(struct sockaddr_in);
//设置协议族
addr.sin_family = AF_INET;
//设置端口
addr.sin_port = htons(8080);
//设置IP地址
addr.sin_addr.s_addr = inet_addr(@"127.0.0.1".UTF8String);
CFDataRef dataRef = CFDataCreate(kCFAllocatorDefault,(UInt8 *)&addr, sizeof(addr));
CFSocketSignature signature;
signature.protocolFamily = AF_INET;
signature.socketType = SOCK_STREAM;
signature.protocol = IPPROTO_TCP;
signature.address = dataRef;
_socketRef = CFSocketCreateConnectedToSocketSignature(kCFAllocatorDefault, &signature, kCFSocketConnectCallBack, ServerConnectCallBack, &sockContext);
复制代码
-
连接、接收数据
CFSocketConnectToAddress() 客户端使用,用于连接服务端。
CFSocketError CFSocketConnectToAddress(CFSocketRef s, CFDataRef address, CFTimeInterval timeout);
参数说明:
s: 创建的sokcet
address: 要连接的地址
timeout: 超时时间
返回值:CFSocketError
typedef CF_ENUM(CFIndex, CFSocketError) {
kCFSocketSuccess = 0,//成功
kCFSocketError = -1L,//错误
kCFSocketTimeout = -2L//超时
};
例子:
struct sockaddr_in addr;
//清空指向的内存中的存储内容,因为分配的内存是随机的
memset(&addr, 0, sizeof(addr));
//设置协议族
addr.sin_family = AF_INET;
//设置端口
addr.sin_port = htons(8080);
//设置IP地址
addr.sin_addr.s_addr = inet_addr(@"127.0.0.1".UTF8String);
CFDataRef dataRef = CFDataCreate(kCFAllocatorDefault,(UInt8 *)&addr, sizeof(addr));
CFSocketError sockError = CFSocketConnectToAddress(_socketRef,dataRef,20);
接收数据:
- (void)recvData {
char buffer[512];
long readData;
while((readData = recv(CFSocketGetNative(_socketRef), buffer, sizeof(buffer), 0))) {
//接收到的数据
NSString *content = [[NSString alloc] initWithBytes:buffer length:readData encoding:NSUTF8StringEncoding];
}
}
复制代码
如果设置回调参数为kCFSocketConnectCallBack,并且设置了回调函数,需要添加到runloop
// 加入循环中
// 获取当前线程的RunLoop
CFRunLoopRef runLoopRef = CFRunLoopGetCurrent();
// 把Socket包装成CFRunLoopSource,最后一个参数是指有多个runloopsource通过同一个runloop时候顺序,如果只有一个source通常为0
CFRunLoopSourceRef sourceRef = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socketRef, 0);
// 加入运行循环,第三个参数表示
CFRunLoopAddSource(runLoopRef, //运行循环管
sourceRef, // 增加的运行循环源, 它会被retain一次
kCFRunLoopCommonModes //用什么模式把source加入到run loop里面,使用kCFRunLoopCommonModes可以监视所有通常模式添加source
);
CFRunLoopRun();
CFRelease(sourceRef);
接收数据:
void ServerConnectCallBack ( CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info )
{
//这个是创建socket是,context上下文参数的info
ViewController *vc = (__bridge ViewController *)(info);
if (data != NULL) {
}else {
[vc performSelectorInBackground:@selector(recvData) withObject:nil];
}
}
- (void)recvData {
char buffer[512];
long readData;
while((readData = recv(CFSocketGetNative(_socketRef), buffer, sizeof(buffer), 0))) {
//接收到的数据
NSString *content = [[NSString alloc] initWithBytes:buffer length:readData encoding:NSUTF8StringEncoding];
}
}
复制代码
-
发送数据,和BSD Socket类似
NSString *sendMsg = @"";
const char* data = [sendMsg UTF8String];
int sendData = send(CFSocketGetNative(_socketRef), data, strlen(data) + 1, 0);
复制代码
CFSocket服务端
-
创建Socket对象、允许重用本地地址和端口
- (void)creatSocket {
CFSocketContext sockContext = {0,(__bridge void *)(self),NULL,NULL,NULL};
_socketRef = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack,SocketAcceptCallBack, &sockContext);
if (_socketRef != NULL) {
NSLog(@"socket 创建成功");
[self bind];
}else {
NSLog(@"socket 创建失败");
}
/*
使用的函数:
int setsockopt(int sock,
int level,
int optname,
const void *optval,
socklen_t optlen
);
用于任意类型、任意状态套接口的设置选项值。尽管在不同协议层上存在选项,但本函数仅定义了最高的“套接口”层次上的选项
参数说明:
1. level指定控制套接字的层次.可以取三种值:
SOL_SOCKET:通用套接字选项.(常用)
IPPROTO_IP:IP选项.
IPPROTO_TCP:TCP选项. 
2. optname指定控制的方式(选项的名称)
SO_REUSERADDR- 允许重用本地地址和端口- int
3. optval指针,指向存放选项待设置的新值的缓冲区;
4. optlen 是指上面optval长度
5. 返回值:
EBADF:sock不是有效的文件描述词
EFAULT:optval指向的内存并非有效的进程空间
EINVAL:在调用setsockopt()时,optlen无效
ENOPROTOOPT:指定的协议层不能识别选项
ENOTSOCK:sock描述的不是套接字
*/
//允许重用本地地址和端口
BOOL reused = YES;
setsockopt(CFSocketGetNative(_socketRef), SOL_SOCKET, SO_REUSEADDR, (const void *)&reused, sizeof(reused));
}
复制代码
-
绑定地址、加入RunLoop循环监听
- (void)bind {
struct sockaddr_in addr;
//清空指向的内存中的存储内容,因为分配的内存是随机的
memset(&addr, 0, sizeof(addr));
//设置协议族
addr.sin_family = AF_INET;
//设置端口
addr.sin_port = htons(_loc_port.intValue);
//设置IP地址
addr.sin_addr.s_addr = inet_addr(_loc_ipAdr.UTF8String);
CFDataRef dataRef = CFDataCreate(kCFAllocatorDefault,(UInt8 *)&addr, sizeof(addr));
//将CFSocket绑定到指定IP地址
CFSocketError sockError = CFSocketSetAddress(_socketRef, dataRef);
if (sockError == kCFSocketSuccess) {
NSLog(@"socket 绑定成功");
}else if(sockError == kCFSocketError) {
NSLog(@"socket 绑定失败");
}else if(sockError == kCFSocketTimeout) {
NSLog(@"socket 绑定超时");
}
//获取当前线程的CFRunLoop
CFRunLoopRef cfRunLoop = CFRunLoopGetCurrent();
//将_socket包装成CFRunLoopSource
CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socketRef, 0);
//为CFRunLoop对象添加source
CFRunLoopAddSource(cfRunLoop, source, kCFRunLoopCommonModes);
//运行当前线程的CFRunLoop
CFRunLoopRun();
CFRelease(source);
}
复制代码
-
接收消息
void SocketAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void * data, void *info) {
//如果有客户端Socket连接进来
if (kCFSocketAcceptCallBack == type) {
//获取本地Socket的Handle
CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;
//创建一组可读/写的CFStream
_readStreamRef = NULL;
_writeStreamRef = NULL;
//创建一个和Socket对象相关联的读取数据流
CFStreamCreatePairWithSocket(kCFAllocatorDefault, //内存分配器
nativeSocketHandle, //准备使用输入输出流的socket
&_readStreamRef, //输入流
&_writeStreamRef);//输出流
if (_readStreamRef && _writeStreamRef) {
//打开输入流和输出流
CFReadStreamOpen(_readStreamRef);
CFWriteStreamOpen(_writeStreamRef);
CFStreamClientContext context = {0,NULL,NULL,NULL};
/**
指定客户端的数据流,当特定事件发生的时候,接受回调
Boolean CFReadStreamSetClient ( CFReadStreamRef stream, 需要指定的数据流
CFOptionFlags streamEvents, 具体的事件,如果为NULL,当前客户端数据流就会被移除
CFReadStreamClientCallBack clientCB, 事件发生回调函数,如果为NULL,同上
CFStreamClientContext *clientContext 一个为客户端数据流保存上下文信息的结构体,为NULL同上
);
返回值为TRUE就是数据流支持异步通知,FALSE就是不支持
*/
if (!CFReadStreamSetClient(_readStreamRef,
kCFStreamEventHasBytesAvailable,
readStream,
&context)) {
exit(1);
}
// ----将数据流加入循环
CFReadStreamScheduleWithRunLoop(_readStreamRef,
CFRunLoopGetCurrent(),
kCFRunLoopCommonModes);
}else {
// 如果失败就销毁已经连接的Socket
close(nativeSocketHandle);
}
}
}
复制代码
-
发送数据
- (IBAction)sendMsg:(id)sender {
if (self.sendTF.stringValue.length) {
NSString *sendMsg = self.sendTF.stringValue;
const char* data = [sendMsg UTF8String];
CFIndex sendLen = CFWriteStreamWrite(_writeStreamRef, data, strlen(data) + 1);
if (sendLen > 0) {
NSLog(@"发送成功");
}else{
NSLog(@"发送失败");
}
}
}
复制代码
Demo
- 下载地址:github.com/namesubai/S…
- 请先编译服务端CFSocketMac项目,再编译客户端CFSocketiOS项目
- 效果图: