iOS搭建Socket服务器的相关方法

iOS网络编程层次

iOS网络编程层次结构也分为三层:

  • Cocoa层:NSURLBonjourGame KitWebKit
  • Core Foundation层:基于 C 的 CFNetworkCFNetServices
  • OS层:基于 C 的 BSD socket

Cocoa层:是最上层的基于 Objective-C 的 API,比如 URL访问,NSStream,Bonjour,GameKit等,这是大多数情况下我们常用的 API。Cocoa 层是基于 Core Foundation 实现的。

Core Foundation层:因为直接使用 socket 需要更多的编程工作,所以苹果对 OS 层的 socket 进行简单的封装以简化编程任务。该层提供了 CFNetwork 和 CFNetServices,其中 CFNetwork 又是基于 CFStream 和 CFSocket。

OS层:最底层的 BSD socket 提供了对网络编程最大程度的控制,但是编程工作也是最多的。因此,苹果建议我们使用 Core Foundation 及以上层的 API 进行编程。


socket server 实现

这里介绍两种ios上的socket server的实现方案:

1、第一种采用原始的socket方案,

实现逻辑如下图:

ios上可以直接使用基于c语言的BSD socket,也可以使用 Core Foundation层的CFNetwork

2、只用BSD socket 实现了绑定和监听,数据的读写直接使用的CFStream。

a、socket的绑定和监听

使用BSD Socket创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//ipv4
  struct sockaddr_in addr4;
  bzero(&addr4, sizeof(addr4));
  addr4.sin_len = sizeof(addr4);
  addr4.sin_family = AF_INET;
  addr4.sin_port = htons(port);
  addr4.sin_addr.s_addr = bindToLocalhost ? htonl(INADDR_LOOPBACK) : htonl(INADDR_ANY);
 //ipv6
  struct sockaddr_in6 addr6;
  bzero(&addr6, sizeof(addr6));
  addr6.sin6_len = sizeof(addr6);
  addr6.sin6_family = AF_INET6;
  addr6.sin6_port = htons(port);
  addr6.sin6_addr = bindToLocalhost ? in6addr_loopback : in6addr_any;
  int listeningSocket = socket(useIPv6 ? PF_INET6 : PF_INET, SOCK_STREAM, IPPROTO_TCP);
  int yes = 1;
  setsockopt(listeningSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
    
  bind(listeningSocket, addr, length);
  listen(listeningSocket, (int)maxPendingConnections) == 0);
  if (port == 0) {
    struct sockaddr_in addr;
    socklen_t addrlen = sizeof(addr);
    if (getsockname(listeningSocket, (struct sockaddr*)&addr, &addrlen) == 0) {
      port = ntohs(addr.sin_port);
    }
  }

上面的步骤实现了socket的绑定和监听,要实现socket数据的读写需要创建可读写的管道并连接到socket。

b、创建管道

创建管道的方式有很多种。CFNetWork中提供了CFReadStreamRefCFWriteStreamRef两种Stream,用于接收和写入数据。可以用以下方法来创建输入输出流。

1
void CFStreamCreatePairWithSocket(CFAllocatorRef alloc, CFSocketNativeHandle sock, CFReadStreamRef *readStream, CFWriteStreamRef *writeStream);

创建好的输入输出流需要登记要接收的流的相关事件,这里 writeStream 的kCFStreamEventCanAcceptBytes 事件表示可以写入数据了,readStream 的kCFStreamEventHasBytesAvailable 表示有数据需要读取。

1
2
3
4
5
CFStreamClientContext writectx = {0, (__bridge void *)(self), NULL, NULL, NULL};
CFWriteStreamSetClient(writeStream, kCFStreamEventErrorOccurred|kCFStreamEventEndEncountered|kCFStreamEventCanAcceptBytes, WriteStreamClientCallBack, &writectx);
    
CFStreamClientContext readctx = {0, (__bridge void *)(self), NULL, NULL, NULL};
CFReadStreamSetClient(readStream, kCFStreamEventErrorOccurred|kCFStreamEventEndEncountered|kCFStreamEventHasBytesAvailable, ReadStreamClientCallBack, &readctx);

WriteStreamClientCallBackReadStreamClientCallBack是用来接收相关事件的回调方法,CFStream中规定好了这两个回调函数格式。

1
2
typedef void (*CFReadStreamClientCallBack)(CFReadStreamRef stream, CFStreamEventType type, void *clientCallBackInfo);
typedef void (*CFWriteStreamClientCallBack)(CFWriteStreamRef stream, CFStreamEventType type, void *clientCallBackInfo);

具体实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
static void WriteStreamClientCallBack(CFWriteStreamRef stream, CFStreamEventType type, void *clientCallBackInfo)
{
    switch(type) {
        case kCFStreamEventCanAcceptBytes:
        //在这里写入数据
            break;
        case kCFStreamEventErrorOccurred:
            NSLog(@"kCFStreamEventErrorOccurred");
            break;
        case kCFStreamEventEndEncountered:
            NSLog(@"kCFStreamEventErrorOccurred");
            break;
        default:
            NSLog(@"WriteStreamClientCallBack default");
            break;
    }
}
static void ReadStreamClientCallBack(CFWriteStreamRef stream, CFStreamEventType type, void *clientCallBackInfo)
{
    switch(type) {
        case kCFStreamEventHasBytesAvailable:
            //在这里读取数据
            break;
        case kCFStreamEventErrorOccurred:
            NSLog(@"kCFStreamEventErrorOccurred");
            break;
        case kCFStreamEventEndEncountered:
            NSLog(@"kCFStreamEventErrorOccurred");
            break;
        default:
            NSLog(@"ReadStreamClientCallBack default");
            break;
    }
}

c、将writeStream和readStream 添加到runloop中,以便接收相关事件。

1
2
CFWriteStreamScheduleWithRunLoop(writeStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
CFWriteStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);

d、最后调用 CFReadStreamOpen 和 CFWriteStreamOpen打开Stream。

1
2
CFReadStreamOpen(readStream);
CFWriteStreamOpen(writeStream);

其他

  1. 代替CFSTream实现管道的其他方式
    读写Socket数据,GCD还提供了一种方式:

读取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void
dispatch_read(dispatch_fd_t fd,
size_t length,
dispatch_queue_t queue,
void (^handler)(dispatch_data_t data, int error));
//对于BSDSocket
int socket = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP);
//对于CFNetWork
CFSocketRef socketRef = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketConnectCallBack, TCPServerConnectCallBack, NULL);
int socket = CFSocketGetNative(socketRef);
dispatch_read(socket , length, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(dispatch_data_t  _Nonnull data, int error) {
        
    });

写入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void
dispatch_write(dispatch_fd_t fd,
dispatch_data_t data,
dispatch_queue_t queue,
void (^handler)(dispatch_data_t _Nullable data, int error));
//对于BSDSocket
int socket = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP);
//对于CFNetWork
CFSocketRef socketRef = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketConnectCallBack, TCPServerConnectCallBack, NULL);
int socket = CFSocketGetNative(socketRef);
NSMutableData *data = [NSMutableData data];
    [data appendData:headerData];
    
dispatch_data_t buffer = dispatch_data_create(data.bytes, data.length, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [data self];  // Keeps ARC from releasing data too early
    });
dispatch_write(socket , buffer, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(dispatch_data_t  _Nullable data, int error) {
        @autoreleasepool {
            if (error == 0) {
                [self sendFileDataWithFileHandle:fileHandle];
            } else {
                NSLog(@"Error while writing to socket %i: %s (%i)", self.fileDescriptor, strerror(error), error);
            }
        }
    });

不论是读取数据和写入数据,当数据量较大时,都需要递归的调用 dispatch_readdispatch_write 来进行读写。

本文作者: ctinusdev
本文链接: https://ctinusdev.github.io/2017/08/13/BSDSocketServer/
转载请注明出处!

坚持原创技术分享,您的支持将鼓励我继续创作!