今天想深刻了解下CFNetworking,而後就去看了Apple的API,又結合了網絡上前人的經驗,發現了這麼一段話。html
Sockets 是網絡通信的最基本一層。一個 socket 起的做用相似與一個電話線接口,它可使你鏈接到另外一個 socket 上(不管是本地的仍是網絡另外一端的),而且向那個 socket 發送數據。
ios
最多見的 socket 抽象概念就是 BSD sockets,而 CFSocket 則是 BSD sockets 的抽象。CFSocket 中包含了少數開銷,它幾乎能夠提供 BSD sockets 所具備的一切功能,而且把 socket 集成進一個「運行循環」當中。CFSocket 並不只僅限於基於流的 sockets (好比 TCP),它能夠處理任何類型的 socket。
數據庫
你能夠利用 CFSocketCreate
功能從頭開始建立一個 CFSocket 對象,或者利用 CFSocketCreateWithNative
函數從 BSD socket 建立。而後,須要利用函數 CFSocketCreateRunLoopSource
建立一個「運行循環」源,並利用函數CFRunLoopAddSource
把它加入一個「運行循環」。這樣不論 CFSocket 對象是否接收到信息, CFSocket 回調函數均可以運行。
編程
很清晰明瞭的解釋了CFSocket的使用,具體的能夠百度下代碼。安全
主要函數:服務器
第一步:建立網絡
CFSocketRef CFSocketCreate(多線程
CFAllocatorRef allocator, //內存分配類型通常爲默認KCFAllocatorDefault併發
SInt32 protocolFamily, //協議族,通常爲Ipv4:PF_INET,(Ipv6,PF_INET6)框架
SInt32 socketType, //套接字類型TCP:SOCK_STREAM
UDP:SOCK_DGRAM
SInt32 protocol, //套接字協議TCP:IPPROTO_TCP
UDP:IPPROTO_UDP;
CFOptionFlags callBackTypes, //回調事件觸發類型
Enum CFSocketCallBACKType{
KCFSocketNoCallBack = 0,
KCFSocketReadCallBack =1,
KCFSocketAcceptCallBack = 2,(經常使用)
KCFSocketDtatCallBack = 3,
KCFSocketConnectCallBack = 4,
KCFSocketWriteCallBack = 8
}
CFSocketCallBack callout, // 觸發時調用的函數
Const CFSocketContext *context // 用戶定義數據指針
)
假設_socket = CFSocketCreate(….);
第二步:初始化
int yes = 1 ;
setsocketopt(
CFSocketGetNative(_socket),//返回系統原生套接字,補齊缺省
SOL_SOCKET,
SO_REUSEADDR,
(void*)&yes,
sizeof(yes)
) //對socket進行定義設置
第三步:地址
uint16_t port = 12345;
struct sockaddr_in addr4; // 定義監聽地址以及端口
memset(&addr4 , 0, sizeof (addr4));
addr4.sin_len = sizeof (addr4);
addr4.sin_family = AF_INET;
addr4.sin_port =htons(port)
addr4.sin_addr.s_addr = htonl(INADDR_ANY);
CFData Ref address =CFDataCreate(
kCFAllocatorDefault,
(UInt8 *)& addr4,
sizeof (addr4),
)
int rst = CFSocketSetAddress(_socket ,&addr4);
//將設置數據設入socket
If ( rst != KCFSocketSuccess ){…}
第四步:執行
CFRunLoopRef cfrl = CFRunLoopGetCurrent();
//獲取當前的運行循環
CFRunLoopSourceRef sourceRef = CFSoceketCreateRunLoopSource(KCFAllocatorDefault,_socket,0);//建立一個運行循環源對象
CFRunLoopSource(cfrl , sourceRef, KCFRunLoopCommonModes);//以該對象運行到當前運行循環中
CFRelease(sourceRef);
服務端響應
CFSocketCallBack callout, // 觸發時調用的函數,該函數會在接收到客戶端請求鏈接時觸發:
ServerAcceptCallBack( //名字能夠任意取,但參數是固定的
CFSoceketRef socket ,
CFSocketCallBackType callbacktype,
CFDataRef address,
const void * data, //與回調函數有關的特殊數據指針,
對於接受鏈接請求事件,這個指針指向該socket的句柄,
對於鏈接事件,則指向Sint32類型的錯誤代碼
void *info) //與套接字關聯的自定義的任意數據
{ //實現函數
If(kCFSocketAcceptCallBack = = type ){
CFSocketNativeHandle nativeSocketHandle = (CFSocketNativeHandle*)data;
//////////////////////如下片斷用於輸出來訪者地址
Uint8_t name[SOCK_MAXADDRLEN]
Socklen_t namelen = sizeof(name);
If(0 != getpeername(nativeSocketHandle ,(struct sockaddr_in*)name,&namelen)) //獲取地址
{
exit(1)
}
Printf(「%s connected\n」,inet_ntoa((struct sockaddr_in *)name)->sin_addr);
//////////////////////
CFReadStreamRef iStream;
CFWriteStreamRef oStream;
CFStreamCreatePairWithSocket( // 建立一個可讀寫的socket鏈接
kCFAllocatorDefault,
nativeSocketHandle,
&iStream,
&oStream);
If(iStream && oStream){
CFStreamClinetContext streamCtxt = {0,NULL, NULL, NULL, NULL};
If(!CFReadStreamSetClient(
iStream,
kCFStreamEventHasBytesAvailable //有可用數據則執行
readStream, //設置讀取時候的函數
&steamCtxt))
{exit(1);}
If(!CFWriteStreamSetClient( //爲流指定一個在運行循環中接受回調的客戶端
oStream,
kCFStreamEventCanAcceptBytes, //輸出流準備完畢,可輸出
writeStream, //設置寫入時候的函數
&steamCtxt))
{exit(1);}
}
}
}
讀取流操做(觸發式,被動技能)
readStream(CFReadStreamRef stream,CFStreamEventType eventType, void *client CallBackInfo)
{
UInt8 buff[255];
CFReadStreamRead(stream,buff,255); //將輸入流中數據存入buff
Printf(「received %s」,buff);
}
CFWriteStreamRef outputStream = NULL; //輸出流
寫入流操做(仍然被動技能,在輸出流準備好的時候調用)
writeStream (CFWriteStreamRef stream, CFStreamEventType eventType, void *clientCallBackInfo)
{
outputStream = stream; //輸出流被指定
}
//主動輸出,在輸出流準備好以後才能調用
FucForWrite()
{
UInt8 buff[] = 「Hunter21,this is Overlord」;
If(outputStream != NULL)
{
CFWriteStreamWrite(outputStream,buff,strlen(buff)+1);
}
}
------------------------------------------------------------------------------------------
CFSocketRef _socket;
-(void)Connect
{
//////////////////////建立套接字//////////////
CFSocketContext CTX = {0,NULL,NULL,NULL,NULL};
_socket = CFSocketCreate(
kCFAllocatorDefault,
PF_INET,
SOCK_STREAM,
IPPROTO_TCP,
kCFSocketConnectCallBack, // 類型,表示鏈接時調用
ServerConnectCallBack, // 調用的函數
)
////////////////////////////設置地址///////////////////
NSString *serverAddr = @"192.168.0.110";
struct sockaddr_in addr
memset(&addr , 0,sizeof(addr));
addr.sin_len = sizeof(addr);
addr.sin_family = AF_INET;
addr.sin_port = htons(12345);
addr.sin_addr.s_addr = inet_addr([serverAddr UTF8String]);
CFDataRef address = CFDataCreate(
kCFAllocatorDefault,
(UInt8*)&addr,
sizeof(addr));
/////////////////////////////執行鏈接/////////////////////
CFSocketConnectToAddress(_socket,address,-1);
CFRunLoopRef cfrl = CFRunLoopGetCurrent(); // 獲取當前運行循環
CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault,_socket,0);//定義循環對象
CFRunLoopAddSource(cfrl,source,kCFRunLoopCommonModes); //將循環對象加入當前循環中
CFRelease(source);
}
static void ServerConnectCallBack(
CFSocketRef socket,
CFSocketCallBackType type,
CFDataRef address,
const void *data,
void * info)
{
if(data != NULL)
{
printf("connect").//服務器那邊已經提過,鏈接事件時該指針用於存放報錯
}
else
{
printf("connect success");
}
}
///////////////////監聽來自服務器的信息///////////////////
-(void)ReadStream
{
char buffer[255];
while(recv( CFSocektGetNative(_socket),buffer,sizeof(buffer),0))
{
printf(buffer);
}
}
/////////////////////////發送信息給服務器////////////////////////
- (void) sendMessage
{
NSString *stringToSend = @"Overlord,this is Hunter21";
const char *data = [stringToSend UTF8String];
send(CFSocketGetNative(_socket), data, strlen(data) + 1, 0);
}
有關CFSocket能夠看下面的內容:
首先看張圖:
這是MAC OS X系統中CFNetwork和其他軟件層的結構圖。
CFNetwork位於底層,但高效地處理協議棧的操做。BSD套接字提供了一些標準對象來方便操做,如與FTP和HTTP服務器通訊,解析DNS主機地址。而CFNetwork就是以BSD套接字爲基礎。
相似,一些cocoa類,如NSURL,使用標準網絡協議與服務器通訊,就是以CFNetwork爲基礎。
除此以外,Web Kit是一些cocoa類,顯示窗口中的網絡內容。而NSURL和Web Kit都是高層之上,要自行處理網絡協議。所以,結構如上圖。
下圖是CFNetwork框架與Core Foundation框架的結構圖:
CFSocket API和CFStream API是CFNetwork的基礎。套接字是網絡通信的基礎,套接字能夠鏈接到網絡或是本地的另外一個套接字,並容許數據傳送。最一般的套接字抽象就是BSD Socket。CFSocket又是BSD Socket的抽象。CFSocket幾乎包含BSD Socket的全部功能,並且將Socket融入run-loop中。CFSocket能夠處理任何socket,甚至stream-based socket。
CFStream API提供了輕鬆的與設備無關的讀寫數據的能力。你能夠爲內存,文件,網絡(使用套接字)的數據創建stream,可使用stream而沒必要當即把全部數據都寫入到內存中。
stream,流,是一個在搭建的通信通道里連續傳送的字節序列。steam是單向的,全部有必要創建input(read) stream和output(write) stream。除了基於文件的stream,不然,stream中的數據一經取出消耗,就沒法找到。
CFStream就是對這些stream的抽象,並提供兩種CFType類型:CFReadStream 和 CFWriteStream,他們都符合Core Foundation API的規範。
由圖能夠看出,CFStream是基於CFSocket,並且CFStream是CFFTP和CFHTTP的基礎。而CFStream卻不是CFNetwork的一部分,而是Core Foundation的一部分。
CFNetwork API簡介:
CFNetwork API能夠拆成許多獨立的API,能夠獨立使用,能夠聯合使用。
CFFTP API
CFHTTP API
CFHTTPAuthentication API
CFHost API
CFNetServices API
CFNetDiagnostics API
對流的讀寫操做使咱們能夠以一種設備無關的方式在各類媒體之間交換數據。你能夠爲內存、文件或者網絡(經過sockets)裏面的數據建立流。另外在操做流的時候,全部數據能夠分次加載。
數據流本質上是在通訊通道中串行傳輸的一個字節序列,它是單向的,因此若是須要雙向傳輸的話必須操做一個輸入流(讀操做)和一個輸出流(寫操做)。除了基於文件的流之外,其餘流都是不可搜索的,也就是說:在流數據被提供或者接收以後,就不能再從這個流當中獲取數據了。
CFStream API 用兩個新的 CFType 對象提供了對這些流的一個抽象:CFReadStream 和 CFWriteStream。兩個類型的流都遵循常見的核心基礎 API 慣例。有關核心基礎類型的更多信息,請參考設計概念。
CFStream 的構建基於 CFSocket,同時也是 CFHTTP 和 CFFTP 的基礎。在圖 1-2 中你能夠看到,儘管CFStream 並非 CFNetwork的正式成員,它倒是幾乎全部 CFNetwork 成員的基礎。
你幾乎能夠用操做 UNIX 文件描述符的方式對流進行讀寫操做。首先,實例化流對象的時候須要指定流的類型(內存、文件或者socket)而且設置任何一個可選項。而後,打開流並能夠進行任意次的讀寫操做。當流還存在的時候,你能夠經過流的屬性獲取有關它的信息。流屬性包括有關流的任何信息,好比它的數據源或者目標,這些都不屬於被讀寫的實際數據範疇以內。當你再也不須要一個流的時候,須要關閉並把它丟棄。
CFStream 的函數若是不能進行至少一個字節數據的讀寫操做的話,它們可能會暫停或者阻塞當前的進程。爲了不在阻塞的時候從一個流讀數據或者向一個流寫數據,可使用這些函數的異步操做版本,而且把有關這個流的操做放入一個循環當中。當能夠從流中讀寫數據的時候,你的回調函數就會被調用。
另外,CFStream 還內置了對安全 Sockets 層 (SSL) 協議的支持。你能夠創建一個包含流的 SSL 信息的字典,其中的信息包括須要的安全級別或者自簽署的認證。而後把這些信息看成 kCFStreamPropertySSLSettings
屬性傳遞給流,這樣一個流就被轉換成了一個 SSL 流。
要建立一個客戶定製的 CFStream 是不可能的。好比,若是你想要對客戶數據庫文件當中的對象進行數據流操做,那麼僅僅但願經過建立具備本身風格的 CFStream 對象是辦不到這一點的,而只有經過定製 NSStream 的子類(利用 Objective-C)才能夠作到。因爲 NSStream 對象能夠很容易的被轉換爲 CFStream 對象,因此你建立的 NSStream 子類能夠被用在任何須要 CFStream 的地方。任何有關 NSStream 所屬類的信息,請參考Cocoa 流編程指南。
"有關流的操做"一章描述瞭如何進行讀寫流操做。
既然上面有提到Socket的使用是放在runloop裏面,那麼就須要瞭解下runloop和多線程的知識。
iPhone中的線程應用並非無節制的,官方給出的資料顯示iPhone OS下的主線程的堆棧大小是1M,第二個線程開始都是512KB。而且該值不能經過編譯器開關或線程API函數來更改。只有主線程有直接修改UI的能力。
一.線程概述
有些程序是一條直線,起點到終點;有些程序是一個圓,不斷循環,直到將它切斷。直線的如簡單的Hello World,運行打印完,它的生命週期便結束了,像曇花一現那樣;圓如操做系統,一直運行直到你關機。
一個運行着的程序就是一個進程或者叫作一個任務,一個進程至少包含一個線程,線程就是程序的執行流。Mac和iOS中的程序啓動,建立好一個進程的同時, 一個線程便開始運行,這個線程叫主線程。主線程在程序中的地位和其餘線程不一樣,它是其餘線程最終的父線程,且全部界面的顯示操做即AppKit或 UIKit的操做必須在主線程進行。
系統中的每個進程都有本身獨立的虛擬內存空間,而同一個進程中的多個線程則共用進程的內存空間。每建立一個新的線程,都須要一些內存(如每一個線程有本身的Stack空間)和消耗必定的CPU時間。另外當多個線程對同一個資源出現爭奪的時候須要注意線程安全問題。
二.建立線程
建立一個新的線程就是給進程增長了一個執行流,執行流總得有要執行的代碼吧,因此新建一個線程須要提供一個函數或者方法做爲線程的入口。
NSThread提供了建立線程的途徑,還能夠提供了檢測當前線程是不是主線程的方法。 使用NSThread建立一個新的線程有兩種方式:
1.建立一個NSThread的對象,調用其start方法。對於這種方式的NSThread對象的建立,可使用一個目標對象的方法初始化一個NSThread對象,或者建立一個繼承NSThread類的子類,實現其main方法,而後在直接建立這個子類的對象。
2.使用 detachNewThreadSelector:toTarget:withObject:這個類方法建立一個線程,這個比較直接了,直接使用目標對象的方法做爲線程啓動入口。
其實NSObject直接就加入了多線程的支持,容許對象的某個方法在後臺運行。如:
1
|
<ol
class
=
"dp-cpp"
><li
class
=
"alt"
>[myObj performSelectorInBackground:
@selector
(doSomething) withObject:nil]; </li></ol>
|
因爲Mac和iOS都是基於Darwin系統,Darwin系統的XUN內核,是基於Mach和BSD的,繼承了BSD的POSIX接口,因此能夠直接使用POSIX線程的相關接口來使用線程。
建立線程的接口爲 pthread_create,固然在建立以前能夠經過相關函數設置好線程的屬性。如下爲POSIX線程使用簡單的例子。
1
|
<ol
class
=
"dp-cpp"
><li
class
=
"alt"
>
// // main.c // pthread // // Created by Lu Kejin on 1/27/12. // Copyright (c) 2012 Taobao.com. Al </li></ol>
|
不少時候咱們使用多線程,須要控制線程的併發數,畢竟線程也是消耗系統資源的,當程序中同時運行的線程過多時,系統必然變慢。 因此不少時候咱們會控制同時運行線程的數目。
NSOperation能夠封裝咱們的操做,而後將建立好的NSOperation對象放到NSOperationQueue中,OperationQueue便開始啓動新的線程去執行隊列中的操做,OperationQueue的併發度是能夠經過以下方式進行設置:
1
|
<ol
class
=
"dp-cpp"
><li
class
=
"alt"
>- (
void
)setMaxConcurrentOperationCount:(NSInteger)count </li></ol>
|
GCD是Grand Central Dispatch的縮寫,是一系列的BSD層面的接口,在Mac 10.6 和iOS4.0之後才引入的,且如今NSOperation和NSOperationQueue的多線程的實現就是基於GCD的。目前這個特性也被移植到 FreeBSD上了,能夠查看libdispatch這個開源項目。
好比一個在UIImageView中顯示一個比較大的圖片
1
|
<ol
class
=
"dp-cpp"
><li
class
=
"alt"
>dispatch_queue_t imageDownloadQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
0
); dispatch_async(imageDownloa </li></ol>
|
固然,GCD除了處理多線程外還有不少很是好的功能,其創建在強大的kqueue之上,效率也可以獲得保障。
四.線程間通訊
線程間通訊和進程間通訊從本質上講是類似的。線程間通訊就是在進程內的兩個執行流之間進行數據的傳遞,就像兩條並行的河流之間挖出了一道單向流動長溝,使得一條河流中的水能夠流入另外一條河流,物質獲得了傳遞。
1.performSelect On The Thread
框架爲咱們提供了強制在某個線程中執行方法的途徑,若是兩個非主線程的線程須要相互間通訊,能夠先將本身的當前線程對象註冊到某個全局的對象中去,這樣相 互之間就能夠獲取對方的線程對象,而後就可使用下面的方法進行線程間的通訊了,因爲主線程比較特殊,因此框架直接提供了在出線程執行的方法。
1
|
<ol
class
=
"dp-cpp"
><li
class
=
"alt"
>
@interface
NSObject (NSThreadPerformAdditions) - (
void
)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUnti </li></ol>
|
2.Mach Port
在蘋果的Thread Programming Guide的Run Pool一節的Configuring a Port-Based Input Source 這一段中就有使用Mach Port進行線程間通訊的例子。 其實質就是父線程建立一個NSMachPort對象,在建立子線程的時候以參數的方式將其傳遞給子線程,這樣子線程中就能夠向這個傳過來的 NSMachPort對象發送消息,若是想讓父線程也能夠向子線程發消息的話,那麼子線程能夠先向父線程發個特殊的消息,傳過來的是本身建立的另外一個 NSMachPort對象,這樣父線程便持有了子線程建立的port對象了,能夠向這個子線程的port對象發送消息了。
固然各自的port對象須要設置delegate以及schdule到本身所在線程的RunLoop中,這樣來了消息以後,處理port消息的delegate方法會被調用,你就能夠本身處理消息了。
五.RunLoop
RunLoop從字面上看是運行循環的意思,這一點也不錯,它確實就是一個循環的概念,或者準確的說是線程中的循環。 本文一開始就提到有些程序是一個圈,這個圈本質上就是這裏的所謂的RunLoop,就是一個循環,只是這個循環里加入不少特性。
首先循環體的開始須要檢測是否有須要處理的事件,若是有則去處理,若是沒有則進入睡眠以節省CPU時間。 因此重點即是這個須要處理的事件,在RunLoop中,須要處理的事件分兩類,一種是輸入源,一種是定時器,定時器好理解就是那些須要定時執行的操做,輸 入源分三類:performSelector源,基於端口(Mach port)的源,以及自定義的源。編程的時候能夠添加本身的源。RunLoop還有一個觀察者Observer的概念,能夠往RunLoop中加入本身的 觀察者以便監控着RunLoop的運行過程,CFRunLoop.h中定義了全部觀察者的類型:
1
|
<ol
class
=
"dp-cpp"
><li
class
=
"alt"
>
enum
CFRunLoopActivity { kCFRunLoopEntry = (
1
<<
0
), kCFRunLoopBeforeTimers = (
1
<<
1
), kCFRunLoopBeforeSources = ( </li></ol>
|
若是你使用過select系統調用寫過程序你即可以快速的理解runloop事件源的概念,本質上講事件源的機制和select同樣是一種多路複用IO的 實現,在一個線程中咱們須要作的事情並不單一,如須要處理定時鐘事件,須要處理用戶的觸控事件,須要接受網絡遠端發過來的數據,將這些須要作的事情通通注 冊到事件源中,每一次循環的開始便去檢查這些事件源是否有須要處理的數據,有的話則去處理。 拿具體的應用舉個例子,NSURLConnection網絡數據請求,默認是異步的方式,其實現原理就是建立以後將其做爲事件源加入到當前的 RunLoop,而等待網絡響應以及網絡數據接受的過程則在一個新建立的獨立的線程中完成,當這個線程處理到某個階段的時候好比獲得對方的響應或者接受完 了網絡數據以後便通知以前的線程去執行其相關的delegate方法。因此在Cocoa中常常看到scheduleInRunLoop:forMode: 這樣的方法,這個即是將其加入到事件源中,當檢測到某個事件發生的時候,相關的delegate方法便被調用。對於CoreFoundation這一層而 言,一般的模式是建立輸入源,而後將輸入源經過CFRunLoopAddSource函數加入到RunLoop中,相關事件發生後,相關的回調函數會被調 用。如CFSocket的使用。 另外RunLoop中還有一個運行模式的概念,每個運行循環必然運行在某個模式下,而模式的存在是爲了過濾事件源和觀察者的,只有那些和當前 RunLoop運行模式一致的事件源和觀察者纔會被激活。
每個線程都有其對應的RunLoop,可是默認非主線程的RunLoop是沒有運行的,須要爲RunLoop添加至少一個事件源,而後去run它。通常狀況下咱們是沒有必要去啓用線程的RunLoop的,除非你在一個單獨的線程中須要長久的檢測某個事件。