iOS_Socket_使用

1灾前、AsyncSocket介紹

?如果需要在項目中像QQ微信一樣做到即時通訊寻咒,必須使用socket通訊烤低。

iOS中Socket編程的方式:

? ? BSD ?Socket :

BSD Socket 是?UNIX 系統(tǒng)中通用的網(wǎng)絡(luò)接口刑顺,它不僅支持各種不同的網(wǎng)絡(luò)類型柔纵,而且也是一種內(nèi)部進(jìn)程之間的通信機制缔杉。而iOS系統(tǒng)其實本質(zhì)就是?UNIX ,所以可以用搁料,但是比較復(fù)雜或详。


? ??CFSocket ?:

CFSocket是蘋果提供給我們的使用?Socket 的方式,但是用起來還是會不太順手郭计。當(dāng)然想使用的話霸琴,可以細(xì)細(xì)研究一下。


??AsyncSocket :

第三方開源庫昭伸,首選方式梧乘,也是在開發(fā)項目中經(jīng)常會用的。


? ? 選擇AsyncSocket的原因:

iphone 的 ?CFNetwork編程比較復(fù)雜庐杨。使用?AsyncSocket 開源庫來開發(fā)相對較簡單选调,幫助我們封裝了很多東西。

環(huán)境:

??下載AsyncSocket:

https://github.com/robbiehanson/CocoaAsyncSocket 類庫灵份,將?RunLoop 文件夾下的?AsyncSocket.h仁堪、AsyncSocket.m、??AsyncUdpSocket.h填渠、?AsyncUdpSocket.m 文件拷貝到自己的項目中弦聂,添加?CFNetwork.framework, ?再使用?socket 的文件頭

? ? #import <sys/socket.h>

? ??#import <netinet/in.h>

? ? #import <arpa/inet.h>

? ? #import <unistd.h>


2鸟辅、AsyncSocket詳解

在實際開發(fā)中,主要的任務(wù)是開發(fā)客戶端莺葫。所以下面主要詳解客戶端的整個連接建立過程匪凉,以及在說明時候回調(diào)哪些函數(shù)。

常用方法:

1捺檬、建立連接

- (int)connectServer:(NSString *)hostIP port:(int)hostPort


2再层、連接成功后,會回調(diào)的函數(shù)

- (void)onSocket:(AsyncSocket *)sockdidConnectToHost:(NSString *)host port:(UInt16)port


3欺冀、發(fā)送數(shù)據(jù)

- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;


4树绩、接受數(shù)據(jù)

-(void)onSocket:(AsyncSocket *)sockdidReadData:(NSData *)data withTag:(long)tag


5萨脑、斷開連接

- (void)onSocket:(AsyncSocket *)sockwillDisconnectWithError:(NSError *)err

- (void)onSocketDidDisconnect:(AsyncSocket *)sock


主要就是上述的幾個方法隐轩,只是說在真正開發(fā)當(dāng)中,很可能我們在收發(fā)數(shù)據(jù)的時候渤早,我們收發(fā)的數(shù)據(jù)并不僅僅是一個字符串包裝成?NSData 即可职车,我們很可能會發(fā)送結(jié)構(gòu)體等類型,這個時候我們就需要和服務(wù)器端的人員協(xié)作來開發(fā):定義怎樣的結(jié)構(gòu)體


3鹊杖、使用方法詳解

即時通訊最大的特點就是實時性悴灵,基本感覺不到延時或是掉線,所以必須對?socket 的連接進(jìn)行監(jiān)視與檢測骂蓖,在斷線時進(jìn)行重新連接积瞒,如果用戶退出登錄,要將?socket 手動關(guān)閉登下,否則對服務(wù)器會造成一定的負(fù)荷茫孔。

一般來說,一個用戶(對于iOS來說也就是我們的項目中)只能有一個正在連接的?socket被芳,所以這個?socket 變量必須是全局的缰贝,這里可以考慮使用單例或是?AppDelegate 進(jìn)行數(shù)據(jù)共享,首選使用單例畔濒。如果對一個已經(jīng)連接的?socket 對象再次進(jìn)行連接操作剩晴,會拋出異常(不可對已經(jīng)連接的socket進(jìn)行連接)程序崩潰,所以在連接?socket 之前要對?socket 對象的連接狀態(tài)進(jìn)行判斷侵状。


使用?socket 進(jìn)行即時通訊還有一個必須的操作赞弥,即時服務(wù)器發(fā)送心跳包,每隔一段時間對服務(wù)器發(fā)送長連接指令(指令不唯一趣兄,由服務(wù)器指定嗤攻,包括使用?socket 發(fā)送消息,發(fā)送的數(shù)據(jù)和格式都是由服務(wù)器指定)诽俯,如果沒有收到服務(wù)器的返回消息妇菱,?AsyncSocket 會得到失去連接的消息承粤,我們可以在失去連接的回調(diào)方法里進(jìn)行重新連接。


聲明socket變量:

@property (nonatomic, strong)?AsyncSocket?*socket; // socket

@property (nonatomic, copy ) NSString *socketHost; // socket的Host

@property (nonatomic, assign) UInt16 socketPort; // socket的prot


? ?連接(長連接)

-(void)socketConnectHost;// socket連接闯团,

連接時host與port都是由服務(wù)器指定辛臊。

?// socket連接

-(void)socketConnectHost{

self.socket = [[AsyncSocket?alloc] initWithDelegate:self];

NSError *error = nil;

[self.socket connectToHost:self.socketHost onPort:self.socketPort withTimeout:3 error:&error];

}


心跳

心跳通過計時器來實現(xiàn)?

@property (nonatomic, retain) NSTimer *connectTimer; // 計時器

實現(xiàn)連接成功回調(diào)的方法,并在此方法中初始化定時器房交,定時向服務(wù)器發(fā)送一次請求彻舰,保持連接

#pragma mark - 連接成功回調(diào)

-(void)onSocket:(AsyncSocket?*)sock didConnectToHost:(NSString *)host port:(UInt16)port {


NSLog(@"socket連接成功"); // 每隔30s像服務(wù)器發(fā)送心跳包

self.connectTimer = [NSTimer scheduledTimerWithTimeInterval:30 target:self selector:@selector(longConnectToSocket) userInfo:nil repeats:YES];

// 在longConnectToSocket方法中進(jìn)行長連接需要向服務(wù)器發(fā)送的訊息

[self.connectTimer fire];

?}


斷開連接:

失去連接由幾種情況,服務(wù)器斷開候味,用戶主動cut刃唤,還可能有如QQ其他設(shè)備登錄被掉線的情況,不管那種情況白群,我們都能收到?socket 回調(diào)方法返回給我們的訊息尚胞,如果是用戶退出登錄或是程序退出而需要手動cut,我們在cut前對?socket 的?userData 賦予一個值來標(biāo)記為用戶退出帜慢,這樣我們可以在收到斷開信息時判斷究竟是什么原因?qū)е碌牡艟€

在.h文件中聲明一個枚舉類型

enum{

SocketOfflineByServer,//服務(wù)器掉線笼裳,默認(rèn)為0

SocketOfflineByUser, //用戶主動cut

};

定義并實現(xiàn)斷開方法

-(void)cutOffSocket; // 斷開socket連接


// 切斷socket

-(void)cutOffSocket{

self.socket.userData = SocketOfflineByUser;// 聲明是由用戶主動切斷

[self.connectTimer invalidate];

[self.socket disconnect];

}

? ? 重連

實現(xiàn)代理方法

-(void)onSocketDidDisconnect:(AsyncSocket?*)sock {

? ? NSLog(@"sorry the connect is failure %ld",sock.userData);

? ? ? ? ? if (sock.userData == SocketOfflineByServer) {

? ? ? ? ? ? ? ?// 服務(wù)器掉線,重連

? ? ? ? ? ? ? ?[self socketConnectHost];

? ? ? ? ? ?} else if (sock.userData == SocketOfflineByUser) {

? ? ? ? ? ? ? // 如果由用戶斷開粱玲,不進(jìn)行重連

? ? ? ? ? ? ? ? return;

????? }

}


?發(fā)送數(shù)據(jù):

我們補充上文心跳連接未完成的方法

// 心跳連接


-(void)longConnectToSocket{

// 根據(jù)服務(wù)器要求發(fā)送固定格式的數(shù)據(jù)躬柬,假設(shè)為指令@"longConnect",但是一般不會是這么簡單的指令

NSString *longConnect = @"longConnect";

NSData *dataStream = [longConnect dataUsingEncoding:

NSUTF8StringEncoding];

[self.socket writeData:dataStream withTimeout:1 tag:1];

}


socket發(fā)送數(shù)據(jù)是以棧的形式存放抽减,所有數(shù)據(jù)放在一個棧中允青,存取時會出現(xiàn)粘包的現(xiàn)象,所以很多時候服務(wù)器在收發(fā)數(shù)據(jù)時是以先發(fā)送內(nèi)容字節(jié)長度卵沉,再發(fā)送內(nèi)容的形式颠锉,得到數(shù)據(jù)時也是先得到一個長度,再根據(jù)這個長度在棧中讀取這個長度的字節(jié)流偎箫,如果是這種情況木柬,發(fā)送數(shù)據(jù)時只需在發(fā)送內(nèi)容前發(fā)送一個長度,發(fā)送方法與發(fā)送內(nèi)容一樣淹办,假設(shè)長度是8


NSData *dataStream = [@8 dataUsingEncoding:NSUTF8StringEncoding];

[self.socket writeData:dataStream withTimeout:1 tag:1];


接收數(shù)據(jù):

為了能時刻接收?socket 的消息眉枕,我們在長連接方法中進(jìn)行讀取數(shù)據(jù)

[self.socket readDataWithTimeout:30 tag:0];

如果得到數(shù)據(jù),會調(diào)用回調(diào)方法


-(void)onSocket:(AsyncSocket?*)sock didReadData:(NSData *)data withTag:(long)tag {

// 對得到的data值進(jìn)行解析與轉(zhuǎn)換即可

[self.socket readDataWithTimeout:30 tag:0];

}


【備注】關(guān)于NSData對象

無論?SOCKET 收發(fā)都采用?NSData 對象怜森。

NSData 主要是帶一個?(id)data 指向的數(shù)據(jù)空間和長度?length速挑。?


NSString?轉(zhuǎn)換成NSData?對象

NSData* xmlData = [@"testdata" dataUsingEncoding:

NSUTF8StringEncoding];


NSData?轉(zhuǎn)換成NSString對象

NSData * data;

NSString *result = [[NSString alloc] initWithData:data? encoding:

NSUTF8StringEncoding];

出現(xiàn)粘包,半包 代碼處理辦法

- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag

{

??while(_readBuf.length >= 10)//因為頭部固定10個字節(jié)副硅,數(shù)據(jù)長度至少要大于10個字節(jié)姥宝,我們才能得到完整的消息描述信息

??{

????NSData *head = [_readBuf subdataWithRange:NSMakeRange(0, 10)];//取得頭部數(shù)據(jù)

????NSData *lengthData = [head subdataWithRange:NSMakeRange(6, 4)];//取得長度數(shù)據(jù)

????NSInteger length = [[[NSString alloc] initWithData:lengthData encoding:NSUTF8StringEncoding] integerValue];//得出內(nèi)容長度

????NSInteger complateDataLength = length + 10;//算出一個包完整的長度(內(nèi)容長度+頭長度)

????if(_readBuf.length >= complateDataLength)//如果緩存中數(shù)據(jù)夠一個整包的長度

????{

??????NSData *data = [_readBuf subdataWithRange:NSMakeRange(0, complateDataLength)];//截取一個包的長度(處理粘包)

??????[self handleTcpResponseData:data];//處理包數(shù)據(jù)

??????//從緩存中截掉處理完的數(shù)據(jù),繼續(xù)循環(huán)

??????_readBuf = [NSMutableData dataWithData:[_readBuf subdataWithRange:NSMakeRange(complateDataLength, _readBuf.length - complateDataLength)]];

????}

????else//如果緩存中的數(shù)據(jù)長度不夠一個包的長度,則包不完整(處理半包恐疲,繼續(xù)讀取)

????{

??????[_socket readDataWithTimeout:-1 buffer:_readBuf bufferOffset:_readBuf.length tag:0];//繼續(xù)讀取數(shù)據(jù)

??????return;

????}

??}

??//緩存中數(shù)據(jù)都處理完了腊满,繼續(xù)讀取新數(shù)據(jù)

??[_socket readDataWithTimeout:-1 buffer:_readBuf bufferOffset:_readBuf.length tag:0];//繼續(xù)讀取數(shù)據(jù)

}


來源:https://www.cnblogs.com/LiZeYuBlog/p/7545170.html;

https://www.jb51.net/article/105278.htm 粘包套么,半包處理

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市碳蛋,隨后出現(xiàn)的幾起案子胚泌,更是在濱河造成了極大的恐慌,老刑警劉巖肃弟,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件玷室,死亡現(xiàn)場離奇詭異,居然都是意外死亡笤受,警方通過查閱死者的電腦和手機穷缤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來箩兽,“玉大人津肛,你說我怎么就攤上這事”纫蓿” “怎么了快耿?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵囊陡,是天一觀的道長芳绩。 經(jīng)常有香客問我,道長撞反,這世上最難降的妖魔是什么妥色? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮遏片,結(jié)果婚禮上嘹害,老公的妹妹穿的比我還像新娘。我一直安慰自己吮便,他們只是感情好笔呀,可當(dāng)我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著髓需,像睡著了一般许师。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上僚匆,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天微渠,我揣著相機與錄音,去河邊找鬼咧擂。 笑死逞盆,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的松申。 我是一名探鬼主播云芦,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼俯逾,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了舅逸?” 一聲冷哼從身側(cè)響起纱昧,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎堡赔,沒想到半個月后识脆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡善已,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年灼捂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片换团。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡悉稠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出艘包,到底是詐尸還是另有隱情的猛,我是刑警寧澤,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布想虎,位于F島的核電站卦尊,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏舌厨。R本人自食惡果不足惜岂却,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望裙椭。 院中可真熱鬧躏哩,春花似錦、人聲如沸揉燃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽炊汤。三九已至正驻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間婿崭,已是汗流浹背拨拓。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留氓栈,地道東北人渣磷。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像授瘦,于是被迫代替她去往敵國和親醋界。 傳聞我的和親對象是個殘疾皇子竟宋,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,500評論 2 359

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