iOS網(wǎng)絡(luò)編程層次
iOS網(wǎng)絡(luò)編程層次結(jié)構(gòu)也分為三層:
- Cocoa層:NSURL熙掺,Bonjour寝杖,Game Kit跛璧,WebKit
- Core Foundation層:基于 C 的 CFNetwork 和 CFNetServices
- OS層:基于 C 的 BSD socket
Cocoa層:是最上層的基于 Objective-C 的 API烧给,比如 URL訪(fǎng)問(wèn)兄渺,NSStream趋厉,Bonjour寨闹,GameKit等,這是大多數(shù)情況下我們常用的 API君账。Cocoa 層是基于 Core Foundation 實(shí)現(xiàn)的繁堡。
Core Foundation層:因?yàn)橹苯邮褂?socket 需要更多的編程工作,所以蘋(píng)果對(duì) OS 層的 socket 進(jìn)行簡(jiǎn)單的封裝以簡(jiǎn)化編程任務(wù)乡数。該層提供了 CFNetwork 和 CFNetServices椭蹄,其中 CFNetwork 又是基于 CFStream 和 CFSocket。
OS層:最底層的 BSD socket 提供了對(duì)網(wǎng)絡(luò)編程最大程度的控制瞳脓,但是編程工作也是最多的塑娇。因此,蘋(píng)果建議我們使用 Core Foundation 及以上層的 API 進(jìn)行編程劫侧。
socket server 實(shí)現(xiàn)
這里介紹兩種ios上的socket server的實(shí)現(xiàn)方案:
1埋酬、第一種采用原始的socket方案,
實(shí)現(xiàn)邏輯如下圖:
ios上可以直接使用基于c語(yǔ)言的BSD socket烧栋,也可以使用 Core Foundation層的CFNetwork写妥。
2、第二種只用BSD socket 實(shí)現(xiàn)了綁定和監(jiān)聽(tīng)审姓,數(shù)據(jù)的讀寫(xiě)直接使用的CFStream(這種方案最常用)珍特。
a、socket的綁定和監(jiān)聽(tīng)
使用BSD Socket創(chuàng)建
//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);
}
}
上面的步驟實(shí)現(xiàn)了socket的綁定和監(jiān)聽(tīng)魔吐,要實(shí)現(xiàn)socket數(shù)據(jù)的讀寫(xiě)需要?jiǎng)?chuàng)建可讀寫(xiě)的管道并連接到socket扎筒。
b、創(chuàng)建管道
創(chuàng)建管道的方式有很多種酬姆。CFNetWork中提供了CFReadStreamRef 和CFWriteStreamRef兩種Stream嗜桌,用于接收和寫(xiě)入數(shù)據(jù)〈巧可以用以下方法來(lái)創(chuàng)建輸入輸出流骨宠。
void CFStreamCreatePairWithSocket(CFAllocatorRef alloc, CFSocketNativeHandle sock, CFReadStreamRef *readStream, CFWriteStreamRef *writeStream);
創(chuàng)建好的輸入輸出流需要登記要接收的流的相關(guān)事件,這里 writeStream 的kCFStreamEventCanAcceptBytes 事件表示可以寫(xiě)入數(shù)據(jù)了,readStream 的kCFStreamEventHasBytesAvailable 表示有數(shù)據(jù)需要讀取层亿。
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是用來(lái)接收相關(guān)事件的回調(diào)方法桦卒,CFStream中規(guī)定好了這兩個(gè)回調(diào)函數(shù)格式。
typedef void (*CFReadStreamClientCallBack)(CFReadStreamRef stream, CFStreamEventType type, void *clientCallBackInfo);
typedef void (*CFWriteStreamClientCallBack)(CFWriteStreamRef stream, CFStreamEventType type, void *clientCallBackInfo);
具體實(shí)現(xiàn)如下:
static void WriteStreamClientCallBack(CFWriteStreamRef stream, CFStreamEventType type, void *clientCallBackInfo)
{
switch(type) {
case kCFStreamEventCanAcceptBytes:
//在這里寫(xiě)入數(shù)據(jù)
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:
//在這里讀取數(shù)據(jù)
break;
case kCFStreamEventErrorOccurred:
NSLog(@"kCFStreamEventErrorOccurred");
break;
case kCFStreamEventEndEncountered:
NSLog(@"kCFStreamEventErrorOccurred");
break;
default:
NSLog(@"ReadStreamClientCallBack default");
break;
}
}
c匿又、將writeStream和readStream 添加到runloop中方灾,以便接收相關(guān)事件。
CFWriteStreamScheduleWithRunLoop(writeStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
CFWriteStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
d琳省、最后調(diào)用 CFReadStreamOpen 和 CFWriteStreamOpen打開(kāi)Stream迎吵。
CFReadStreamOpen(readStream);
CFWriteStreamOpen(writeStream);
其他
- 代替CFSTream實(shí)現(xiàn)管道的其他方式
讀寫(xiě)Socket數(shù)據(jù),GCD還提供了一種方式:
讀取數(shù)據(jù)
void
dispatch_read(dispatch_fd_t fd,
size_t length,
dispatch_queue_t queue,
void (^handler)(dispatch_data_t data, int error));
//對(duì)于BSDSocket
int socket = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP);
//對(duì)于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) {
});
寫(xiě)入數(shù)據(jù)
void
dispatch_write(dispatch_fd_t fd,
dispatch_data_t data,
dispatch_queue_t queue,
void (^handler)(dispatch_data_t _Nullable data, int error));
//對(duì)于BSDSocket
int socket = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP);
//對(duì)于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);
}
}
});
不論是讀取數(shù)據(jù)和寫(xiě)入數(shù)據(jù)针贬,當(dāng)數(shù)據(jù)量較大時(shí)击费,都需要遞歸的調(diào)用 dispatch_read 和 dispatch_write 來(lái)進(jìn)行讀寫(xiě)。
本文作者: ctinusdev
原文鏈接: https://ctinusdev.github.io/2017/08/13/BSDSocketServer/
轉(zhuǎn)載請(qǐng)注明出處桦他!