iOS - CFSocket 的使用

1、CFSocket

  • 苹果对对底层 BSD Socket 进行轻量级的封装(纯 C)。

  • 主要使用的 API:CFSocekt 用于建立连接,CFStream 用于读写数据。

2、基本使用

2.1 Client 客户端

  • TCP 客户端

        // 包含头文件
        #import <sys/socket.h>
        #import <netinet/in.h>
        #import <arpa/inet.h>
    
        @property (weak, nonatomic) IBOutlet UITextField *desIPTF;
        @property (weak, nonatomic) IBOutlet UITextField *desPortTF;
    
        @property (weak, nonatomic) IBOutlet UITextView *recvTextView;
        @property (weak, nonatomic) IBOutlet UITextField *sendTF;
    
        @property (nonatomic, assign) CFSocketRef clientSockfd;
    
        #pragma mark - 创建 TCP 连接
    
        - (IBAction)connectTCPServer:(id)sender {
    
            [NSThread detachNewThreadSelector:@selector(connectTCPSer) toTarget:self withObject:nil];
        }
    
        - (void)connectTCPSer {
    
            BOOL success;
    
            // 创建 socket
            self.clientSockfd = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketConnectCallBack, ServerConnectCallBack, NULL);
            success = (self.clientSockfd != nil);
    
            NSString *logStr = nil;
    
            if (success == NO) {
    
                logStr = @"创建 socket 失败...\n";
                [self showMessage:logStr];
    
                return;
    
            } else {
    
                logStr = @"创建 socket 成功...\n";
                [self showMessage:logStr];
    
                // 服务器地址
                struct sockaddr_in ser_addr;
                memset(&ser_addr, 0, sizeof(ser_addr));
                ser_addr.sin_len         = sizeof(ser_addr);
                ser_addr.sin_family      = AF_INET;
                ser_addr.sin_port        = htons(_desPortTF.text.intValue);
                ser_addr.sin_addr.s_addr = inet_addr(_desIPTF.text.UTF8String);
    
                CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&ser_addr, sizeof((ser_addr)));
    
                // 连接
                CFSocketError result = CFSocketConnectToAddress(self.clientSockfd, address, 5);
                CFRelease(address);
                success = (result == kCFSocketSuccess);
            }
    
            if (success == NO) {
    
                logStr = @"socket 连接失败...\n";
                [self showMessage:logStr];
    
                return;
    
            } else {
    
                logStr = [NSString stringWithFormat:@"socket 连接 %@ 成功...\n", _desIPTF.text];
                [self showMessage:logStr];
    
                char buf[2048];
                do {
                    // 接收数据
                    ssize_t recvLen = recv(CFSocketGetNative(self.clientSockfd), buf, sizeof(buf), 0);
    
                    if (recvLen > 0) {
                        logStr = [NSString stringWithFormat:@"recv:%@\n", [NSString stringWithFormat:@"%s", buf]];
                        [self showMessage:logStr];
                    }
    
                } while (strcmp(buf, "exit") != 0);
    
                CFRunLoopRef cfrl = CFRunLoopGetCurrent();
                CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, self.clientSockfd, 0);
                CFRunLoopAddSource(cfrl, source, kCFRunLoopCommonModes);
                CFRelease(source);
                CFRunLoopRun();
            }
        }
    
        // 连接成功的回调函数
        void ServerConnectCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void * data, void *info) {
    
            if (data != NULL) {
                NSLog(@"connect\n");
            } else {
                NSLog(@"connect success\n");
            }
        }
    
        #pragma mark - 发送数据
    
        - (IBAction)btnClick:(id)sender {
    
            if (_sendTF.text.length == 0) {
                return;
            } else {
    
                // 发送数据
                ssize_t sendLen = send(CFSocketGetNative(self.clientSockfd), _sendTF.text.UTF8String, strlen(_sendTF.text.UTF8String), 0);
    
                if (sendLen > 0) {
                    NSString *logStr = [NSString stringWithFormat:@"send:%@\n", _sendTF.text];
                    [self showMessage:logStr];
                }
            }
        }
    
        // 显示信息
        - (void)showMessage:(NSString *)msg {
    
            dispatch_async(dispatch_get_main_queue(), ^{
    
                _recvTextView.text = [_recvTextView.text stringByAppendingString:msg];
            });
        }
    
        // 键盘回收
        - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
            [self.view endEditing:YES];
        }

    iOS - CFSocket 的使用

2.2 Server 服务端

  • TCP 服务端

        // 包含头文件
        #import <sys/socket.h>
        #import <netinet/in.h>
        #import <arpa/inet.h>
        #import <ifaddrs.h>
    
        static ViewController *selfClass = nil;
    
        @property (weak, nonatomic) IBOutlet UITextField *locIPTF;
        @property (weak, nonatomic) IBOutlet UITextField *locPortTF;
    
        @property (weak, nonatomic) IBOutlet UITextView *recvTextView;
        @property (weak, nonatomic) IBOutlet UITextField *sendTF;
    
        @property (nonatomic, assign) CFSocketRef serverSockfd;
    
        @property (nonatomic, assign) CFWriteStreamRef outputStream;
    
        - (void)viewDidLoad {
            [super viewDidLoad];
    
            NSString *ip_addr = [self getIPAddress];
            _locIPTF.text = ip_addr;
    
            // 函数指针指向本身
            selfClass = self;
        }
    
        #pragma mark - 创建 TCP 监听
    
        - (IBAction)createdTCPServer:(id)sender {
    
            [sender setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
    
            [NSThread detachNewThreadSelector:@selector(createdTCPSer) toTarget:self withObject:nil];
        }
    
        - (void)createdTCPSer {
    
            BOOL success;
    
            // 创建 socket
            self.serverSockfd = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, TCPServerAcceptCallBack, NULL);
            success = (self.serverSockfd != NULL);
    
            NSString *logStr = nil;
    
            if (success == NO) {
    
                logStr = @"创建 socket 失败...\n";
                [self showMessage:logStr];
    
                return;
    
            } else {
    
                logStr = @"创建 socket 成功...\n";
                [self showMessage:logStr];
    
                int optVal = 1;
                setsockopt(CFSocketGetNative(self.serverSockfd), SOL_SOCKET, SO_REUSEADDR, (void*)&optVal,sizeof(optVal));
    
                // 本地地址
                struct sockaddr_in loc_addr;
                memset(&loc_addr, 0, sizeof(loc_addr));
                loc_addr.sin_family      = AF_INET;
                loc_addr.sin_port        = htons(_locPortTF.text.intValue);
                loc_addr.sin_addr.s_addr = inet_addr(_locIPTF.text.UTF8String);
    
                CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8*)&loc_addr, sizeof(loc_addr));
    
                // 绑定
                CFSocketError err = CFSocketSetAddress(self.serverSockfd, address);
                CFRelease(address);
                success = (err == kCFSocketSuccess);
            }
    
            if (success == NO) {
    
                logStr = @"socket 绑定失败...\n";
                [self showMessage:logStr];
    
                CFRelease(self.serverSockfd);
                self.serverSockfd = NULL;
    
                return;
    
            } else {
    
                logStr = @"socket 绑定成功...\n";
                [self showMessage:logStr];
    
                CFRunLoopRef cfRunloop = CFRunLoopGetCurrent();
                CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, self.serverSockfd, 0);
                CFRunLoopAddSource(cfRunloop, source, kCFRunLoopCommonModes);
                CFRelease(source);
                CFRunLoopRun();
            }
        }
    
        // 客户端连接成功的回调函数
        void TCPServerAcceptCallBack(CFSocketRef socket ,CFSocketCallBackType type,CFDataRef address,const void *data, void *info) {
    
            // 客户端连接
            if (kCFSocketAcceptCallBack == type) {
    
                // data  the handle of socket
                CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;
    
                uint8_t name[SOCK_MAXADDRLEN];
                socklen_t nameLen = sizeof(name);
    
                // 获取对方 socket 信息,还有 getsocketname() 函数用于获取本程序所在 socket 信息
                if (getpeername(nativeSocketHandle, (struct sockaddr *)name, &nameLen) != 0) {
                    exit(1);
                }
    
                // 获取连接的客户端信息
                struct sockaddr_in * addr_in = (struct sockaddr_in *)name;
                char *ip_addr = inet_ntoa(addr_in->sin_addr);
                int ip_port   = addr_in->sin_port;
    
                NSString *logStr = [NSString stringWithFormat:@"已连接:%s,port:%d\n", ip_addr, ip_port];
                [selfClass showMessage:logStr];
    
                // 创建一对输入输出流用于读写数据
                CFReadStreamRef iStream;
                CFWriteStreamRef oStream;
    
                // 创建一组可读/写的 CFStreame
                CFStreamCreatePairWithSocket(kCFAllocatorDefault, nativeSocketHandle, &iStream, &oStream);
    
                if (iStream && oStream) {
    
                    // 打开输入流和输出流
                    CFReadStreamOpen(iStream);
                    CFWriteStreamOpen(oStream);
    
                    CFStreamClientContext streamContext = {0, NULL, NULL, NULL};
    
                    // if have data to read   call the readStream function
                    if (!CFReadStreamSetClient(iStream, kCFStreamEventHasBytesAvailable, readStream, &streamContext)) {
                        exit(1);
                    }
    
                    CFReadStreamScheduleWithRunLoop(iStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
    
                    selfClass.outputStream = oStream;
                }
            }
        }
    
        // 读取数据的回调函数
        void readStream(CFReadStreamRef iStream, CFStreamEventType eventType, void * clientCallBackInfo){
    
            char buf[2048];
            bzero(buf, sizeof(buf));
    
            do {
                // 接收数据
                CFIndex dex = CFReadStreamRead(iStream, (UInt8 *)buf, sizeof(buf));
    
                if (dex > 0) {
                    NSString *logStr = [NSString stringWithFormat:@"recv:%@\n", [NSString stringWithFormat:@"%s", buf]];
                    [selfClass showMessage:logStr];
                }
    
            } while (strcmp(buf, "exit") != 0);
        }
    
        #pragma mark - 发送数据
    
        - (IBAction)btnClick:(id)sender {
    
            if (_sendTF.text.length == 0) {
                return;
            } else {
    
                // 发送数据
                CFIndex dex = CFWriteStreamWrite(selfClass.outputStream, (UInt8 *)_sendTF.text.UTF8String, strlen(_sendTF.text.UTF8String)+1);
    
                if (dex > 0) {
                    NSString *logStr = [NSString stringWithFormat:@"send:%@\n", _sendTF.text];
                    [self showMessage:logStr];
                }
            }
        }
    
        #pragma mark - 获取本地 IP 地址
    
        - (NSString *)getIPAddress {
    
            NSString *address = @"error";
            struct ifaddrs *interfaces = NULL;
            struct ifaddrs *temp_addr = NULL;
            int success = 0;
    
            // retrieve the current interfaces - returns 0 on success
            success = getifaddrs(&interfaces);
    
            if (success == 0) {
    
                // Loop through linked list of interfaces
                temp_addr = interfaces;
    
                while (temp_addr != NULL) {
    
                    if (temp_addr->ifa_addr->sa_family == AF_INET) {
    
                        // Check if interface is en0 which is the wifi connection on the iPhone
                        if ([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"en0"]) {
    
                            // Get NSString from C String
                            address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)];
                        }
                    }
                    temp_addr = temp_addr->ifa_next;
                }
            }
    
            // Free memory
            freeifaddrs(interfaces);
            return address;
        }
    
        // 显示信息
        - (void)showMessage:(NSString *)msg {
    
            dispatch_async(dispatch_get_main_queue(), ^{
    
                _recvTextView.text = [_recvTextView.text stringByAppendingString:msg];
            });
        }
    
        // 键盘回收
        - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
            [self.view endEditing:YES];
        }

    iOS - CFSocket 的使用