[深入淺出Cocoa]ios網(wǎng)絡(luò)編程之Socket

一.iOS網(wǎng)絡(luò)編程層次模型

在前文《深入淺出的Cocoa之Bonjour網(wǎng)絡(luò)編程》中我介紹了如何在Mac系統(tǒng)下進(jìn)行Bonjour編程,在那篇文章中也介紹過Cocoa中網(wǎng)絡(luò)編程層次結(jié)構(gòu)分為三層,雖然那篇演示的是Mac系統(tǒng)的例子,其實(shí)對(duì)iOS系統(tǒng)來說也是一樣的。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訪問,NSStream,Bonjour,GameKit等,這是大多數(shù)情況下我們常用的API。Cocoa層是基于Core Foundation實(shí)現(xiàn)的。

Core Foundation層:因?yàn)橹苯邮褂胹ocket需要更多的編程工作,所以蘋果對(duì)OS層的socket進(jìn)行簡單的封裝以簡化編程任務(wù)。該層提供了CFNetwork和CFNetServices,其中CFNetwork又是基于CFStream和CFSocket。

OS層:最底層的BSD socket提供了對(duì)網(wǎng)絡(luò)編程最大程度的控制,但是編程工作也是最多的。因此,蘋果建議我們使用Core Foundation及以上的API進(jìn)行編程。

本文將介紹如何在iOS系統(tǒng)下使用最底層的socket進(jìn)行編程,這和在window系統(tǒng)下使用C/C++進(jìn)行socket編程并無多大區(qū)別。

本文源碼:https://github.com/kesalin/iOSSnippet/tree/master/KSNetworkDemo

二.BSD socket API簡介

BSD socket API和winsock API接口大體差不多,下面將列出比較常用的API:

API接口

int socket(int addressFamily, int type, int protocol)

socket創(chuàng)建并初始化socket,返回該socket的文件描述符,如果描述符為-1表示創(chuàng)建失敗。通常參數(shù)addressFamily是IPv4(AF_INET)或IPv6(AF_INET6)。type表示socket的類型,通常是流stream(SOCK_STREAM)或數(shù)據(jù)報(bào)文datagram(SOCK_DGRAM)。protocol參數(shù)通常設(shè)置為0,以便讓系統(tǒng)自動(dòng)為選擇我們合適的協(xié)議,對(duì)于stream socket來說會(huì)是TCP協(xié)議(IPPROTO_TCP),而對(duì)于datagram來說會(huì)是UDP協(xié)議(IPPROTO_UDP)。

int close(int socketFileDescriptor)

close關(guān)閉socket

int bind(int socketFileDescriptor, sockaddr *addressToBind, int addressStructLength)

將socket與特定主機(jī)地址與端口號(hào)綁定,成功綁定返回0,失敗返回-1。成功綁定之后,根據(jù)協(xié)議(TCP/UDP)的不同,我們可以對(duì)socket進(jìn)行不同的操作:

UDP:因?yàn)閁DP是無連接的,綁定之后就可以利用UDP socket傳輸數(shù)據(jù)了。

TCP:而TCP是需要建立端到端連接的,為了建立TCP連接服務(wù)器必須調(diào)用listen(int socketFileDescriptor, int backlogSize)來設(shè)置服務(wù)器的緩沖區(qū)隊(duì)列以接收客戶端的連接請(qǐng)求,backlogSize表示客戶端連接請(qǐng)求緩沖區(qū)隊(duì)列的大小。當(dāng)調(diào)用listen設(shè)置之后,服務(wù)器等待客戶端請(qǐng)求,然后調(diào)用下面的accept來接受客戶端的連接請(qǐng)求。

int accept(int socketFileDescriptor, sockaddr* clientAddress, int clientAddressStructLength)

接受客戶端連接請(qǐng)求并將客戶端的網(wǎng)絡(luò)地址信息保存到clientAddress中。當(dāng)客戶端連接請(qǐng)求被服務(wù)端接受之后,客戶端和服務(wù)端之間的鏈路就建立好了,兩者就可以通信了。

int connect(int socketFileDescriptor, sockaddr* serverAddress, int serverAddressLength)

客戶端向特定網(wǎng)絡(luò)地址的服務(wù)器發(fā)送連接請(qǐng)求,連接成功返回0,失敗返回-1。當(dāng)服務(wù)器建立好之后,客戶端通過調(diào)用該接口向服務(wù)器發(fā)起建立連接的請(qǐng)求。對(duì)于UDP來說,該接口是可選的,如果調(diào)用了該接口,表明設(shè)置了該UDP socket默認(rèn)的網(wǎng)絡(luò)地址。對(duì)TCP socket來說這就是傳說中三次握手建立連接發(fā)生的地方。注意:該接口調(diào)用會(huì)阻塞當(dāng)前線程,直到服務(wù)器返回。

hostent* gethostbyname(char *hostname)

使用DNS查找特定主機(jī)名字對(duì)應(yīng)的IP地址。如果找不到對(duì)應(yīng)的IP地址則返回NULL。

int send(int socketFileDescriptor, char *buffer, int bufferLength, int flags)

通過socket發(fā)送數(shù)據(jù),發(fā)送成功返回成功發(fā)送的字節(jié)數(shù),否則返回-1。一旦連接建立之后,就可以通過send/receive接口發(fā)送或接收數(shù)據(jù)了。注意調(diào)用connect設(shè)置了默認(rèn)網(wǎng)絡(luò)地址的UDP socket也可以調(diào)用該接口來接收數(shù)據(jù)。

int receive(int socketFileDescriptor, char *buffer, int bufferLength, int flags)

從socket中讀取數(shù)據(jù),讀取成功返回成功讀取的字節(jié)數(shù),否則返回-1。一旦連接建立好之后,就可以通過send/receive接口發(fā)送或接收數(shù)據(jù)了。注意調(diào)用connect設(shè)置了默認(rèn)網(wǎng)絡(luò)地址的UDP socket也可以調(diào)用該接口來發(fā)送數(shù)據(jù)。

int sendto(int socketFileDescriptor, char *buffer, int bufferLength, int flags, sockaddr *destinationAddress, int destinationAddressLength)

通過UDP socket 發(fā)送數(shù)據(jù)到特定的網(wǎng)絡(luò)地址,發(fā)送成功返回成功發(fā)送的字節(jié)數(shù),否則返回-1. 由于UDP可以向多個(gè)網(wǎng)絡(luò)地址發(fā)送數(shù)據(jù),所以可以指定特定網(wǎng)絡(luò)地址,以向其發(fā)送數(shù)據(jù)。

int recvfrom(int socketFileDescriptor, char *buffer, int bufferLength, int flags, sockaddr *fromAddress, int fromAddressLength)

從UDP socket中讀取數(shù)據(jù),并保存發(fā)送者的網(wǎng)絡(luò)地址信息,讀取成功返回成功讀取的字節(jié)數(shù),否則返回-1;由于UDP可以接收啦自多個(gè)網(wǎng)絡(luò)地址的數(shù)據(jù),所以需要提供額外的參數(shù),以保存該數(shù)據(jù)的發(fā)送者身份。

三.服務(wù)器工作流程

有了上面的socket API講解,下面來總結(jié)一下服務(wù)器的工作流程。

1.服務(wù)器調(diào)用socket(...)創(chuàng)建socket

2.服務(wù)器調(diào)用listen(...)設(shè)置緩沖區(qū)

3.服務(wù)器通過accept(...)接受客戶端請(qǐng)求建立連接

4.服務(wù)器與客戶端建立連接之后,就可以通過send(...)/receive(...)向客戶端發(fā)送或從客戶端接收數(shù)據(jù);

5.服務(wù)器調(diào)用close關(guān)閉socket

由于iOS設(shè)備通常是作為客戶端,因此在本文中不會(huì)用代碼來演示如何建立一個(gè)iOS服務(wù)器,但可以參考前文:《深入淺出Cocoa之Bonjour網(wǎng)絡(luò)編程》看看如何在Mac系統(tǒng)下建立桌面服務(wù)器。

四. 客戶端工作流程

由于iOS設(shè)備通常是作為客戶端,下文將演示如何編寫客戶端代碼。先來總結(jié)一下客戶端工作流程。

1.客戶端調(diào)用socket(...)創(chuàng)建socket;

2.客戶端調(diào)用connect(...)向服務(wù)器發(fā)起連接請(qǐng)求以建立連接;

3.客戶端與服務(wù)器建立連接之后,就可以通過send(...)/receive(...)向客戶端發(fā)送或從客戶端接收數(shù)據(jù);

4.客戶端調(diào)用close關(guān)閉socket;

-(void)loadDataFromServerWithURL:(NSURL*)url

{

NSString *host=[url host];

NSNumber *port=[url port];

int socketFileDescriptor=socket(AF_INET, SOCK_STREAM, 0);

if(socketFileDescriptor==-1){

NSLog(@"Failed to create socket.");

return;

}

struct hostent *remoteHostEnt=gethostbyname([host UTF8String]);

if(NULL==remoteHostEnt){

close(socketFileDescriptor);

[self networkFailedWithErrorMessage:@"Unable to resolve the hostname of the ware house server."];

return;

}

struct in_addr *remoteInAddr=(struct in_addr*)remoteHostEnt->h_addr_list[0];

struct sockaddr_in socketParameters;

socketParameters.sin_family=AF_INET;

socketParameters.sin_addr=remoteInAddr;

socketParameters.sin_port=htons([port intValue]);

int ret=connect(socketFileDescripter, (struct sockaddr*)&socketParameters, sizeof(socketParameters));

if(ret==-1){

close(socketFileDescriptor);

NSString *errorInfo=[NSString stringWithFormat:@">>failed to connect to %@:%@", host, port];

[self networkFailedWithErrorMessage:errorInfo];

return;

}

NSLog(@">>Successfully connected to %@:%@, host, port");

NSMutableData *data=[[NSMutableData alloc] init];

BOOL waitingForData=YES;

int maxCount = 5;

int i=0;

while(waitingForData && I<maxCount){

const char *buffer[1024];

int length = sizeof(buffer);

int result=recv(socketFileDescriptor, &buffer, length, 0);

if(result>0){

[data appendBytes:buffer length:result];

}else{

waitingForData=NO;

}

++i;

}

close(socketFileDescriptor);

[self networkSucceedWithData:data];

}

前面說過,connect/recv/send 等接口都是阻塞式的,因此我們需要將這些操作放在非UI線程中進(jìn)行。如下所示:

NSThread *backgroundThread=[[NSThread alloc] initWithTarget:self selector:@selector(loadDataFromServerWithURL:) object:url];?

[backgroundThread start];

同樣,在獲取到數(shù)據(jù)或者網(wǎng)絡(luò)異常導(dǎo)致任務(wù)失敗,我們需要更新UI,這也要回到UI 線程中去做這個(gè)事情。如下所示:

-(void)networkFailedWithErrorMessage:(NSString*)message{

[[NSOperationQueue mainQueue] addOperationWithBlock:^{

NSLog(@"%@",message);

self.receiveTextView.text=message;

self.connectButton.enabled=YES;

[self.networkActivityView stopAnimating];

}];

}

-(void)networkSucceedWithData:(NSData*)data{

[[NSOperationQueue mainQueue] addOperationWithBlock:^{

NSString *resultsString=[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

NSLog(@">> Received string:'%@'",resultsString);

self.receiveTextView.text=resultsString;

self.connectButton.enabled=YES;

[self.networkActivityView stopAnimating];

}];

}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,791評(píng)論 6 545
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,795評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,943評(píng)論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,057評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,773評(píng)論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,106評(píng)論 1 330
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,082評(píng)論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,282評(píng)論 0 291
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,793評(píng)論 1 338
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,507評(píng)論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,741評(píng)論 1 375
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,220評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,929評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,325評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,661評(píng)論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,482評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,702評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容

  • 計(jì)算機(jī)網(wǎng)絡(luò)概述 網(wǎng)絡(luò)編程的實(shí)質(zhì)就是兩個(gè)(或多個(gè))設(shè)備(例如計(jì)算機(jī))之間的數(shù)據(jù)傳輸。 按照計(jì)算機(jī)網(wǎng)絡(luò)的定義,通過一定...
    蛋炒飯_By閱讀 1,241評(píng)論 0 10
  • Socket編程 1基礎(chǔ)知識(shí) 協(xié)議 端口號(hào)(辨別不同應(yīng)用) TCP/IP協(xié)議 是目前世界上應(yīng)用最廣泛的協(xié)議是以TC...
    __豆約翰__閱讀 1,103評(píng)論 0 3
  • 網(wǎng)絡(luò)編程 網(wǎng)絡(luò)編程對(duì)于很多的初學(xué)者來說,都是很向往的一種編程技能,但是很多的初學(xué)者卻因?yàn)楹荛L一段時(shí)間無法進(jìn)入網(wǎng)絡(luò)編...
    程序員歐陽閱讀 2,041評(píng)論 1 37
  • 研究IPv6 socket編程原因: Supporting IPv6 in iOS 9 WWDC2015蘋果宣布在...
    li大鵬閱讀 7,369評(píng)論 7 15
  • 一、概念 首先,理清一些概念 TCP/IP和UDP,HTTP協(xié)議,Socket 1.TCP/IP和UDP,是網(wǎng)絡(luò)中...
    _AJH閱讀 4,250評(píng)論 0 18