CFSocket是對底層BSD Socket的封裝,使用CFSocket的API須要引入如下頭文件:git
#import <CoreFoundation/CoreFoundation.h>
#include <sys/socket.h>
#include <netinet/in.h>
複製代碼
一、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);
二、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);
複製代碼
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);
複製代碼
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() 客戶端使用,用於鏈接服務端。github
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,而且設置了回調函數,須要添加到runloopbash
// 加入循環中
// 獲取當前線程的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];
}
}
複製代碼
NSString *sendMsg = @"";
const char* data = [sendMsg UTF8String];
int sendData = send(CFSocketGetNative(_socketRef), data, strlen(data) + 1, 0);
複製代碼
- (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));
}
複製代碼
- (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(@"發送失敗");
}
}
}
複製代碼