iOS Socket(3) —— CFSocket的使用

CFSocket

CFSocket是对底层BSD Socket的封装,使用CFSocket的API需要引入以下头文件:

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

复制代码

CFSocket客户端

  • CFSocket创建一个Socket
  1. 使用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);

复制代码
  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() 客户端使用,用于连接服务端。


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选项.&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项目
  • 效果图:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值