iOS网络编程层次
iOS网络编程层次结构也分为三层:
- Cocoa层:NSURL,Bonjour,Game Kit,WebKit
- Core Foundation层:基于 C 的 CFNetwork 和 CFNetServices
- 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创建
//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中提供了CFReadStreamRef 和CFWriteStreamRef两种Stream,用于接收和写入数据。可以用以下方法来创建输入输出流。
void CFStreamCreatePairWithSocket(CFAllocatorRef alloc, CFSocketNativeHandle sock, CFReadStreamRef *readStream, CFWriteStreamRef *writeStream);
创建好的输入输出流需要登记要接收的流的相关事件,这里 writeStream 的kCFStreamEventCanAcceptBytes 事件表示可以写入数据了,readStream 的kCFStreamEventHasBytesAvailable 表示有数据需要读取。
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);
WriteStreamClientCallBack 和 ReadStreamClientCallBack是用来接收相关事件的回调方法,CFStream中规定好了这两个回调函数格式。
typedef void (*CFReadStreamClientCallBack)(CFReadStreamRef stream, CFStreamEventType type, void *clientCallBackInfo);
typedef void (*CFWriteStreamClientCallBack)(CFWriteStreamRef stream, CFStreamEventType type, void *clientCallBackInfo);
具体实现如下:
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中,以便接收相关事件。
CFWriteStreamScheduleWithRunLoop(writeStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
CFWriteStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
d、最后调用 CFReadStreamOpen 和 CFWriteStreamOpen打开Stream。
CFReadStreamOpen(readStream);
CFWriteStreamOpen(writeStream);
其他
- 代替CFSTream实现管道的其他方式
读写Socket数据,GCD还提供了一种方式:
读取数据
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) {
});
写入数据
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_read 和 dispatch_write 来进行读写。
本文作者: ctinusdev
原文链接: https://ctinusdev.github.io/2017/08/13/BSDSocketServer/
转载请注明出处!