iOS Socket(3) —— CFSocket的使用

CFSocket

CFSocket是對底層BSD Socket的封裝,使用CFSocket的API須要引入如下頭文件:git

#import <CoreFoundation/CoreFoundation.h>
#include <sys/socket.h> 
#include <netinet/in.h>

複製代碼

CFSocket客戶端

  • CFSocket建立一個Socket

  1. 使用CFSocketCreate()或者CFSocketCreateWithSocketSignature()
一、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);

複製代碼
  1. 使用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);

複製代碼
  1. 使用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() 客戶端使用,用於鏈接服務端。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];
        
    }
}

複製代碼
  • 發送數據,和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選項.&emsp;

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項目
  • 效果圖:
    image
相關文章
相關標籤/搜索