iOS之Socket的使用-AsyncSocket
iOS有原生的SOCKET,但AsyncSOCKET這個第三方庫,對socket的封裝比較好,本文就是基于AsyncSocket的使用介紹。

環(huán)境
下載AsyncSocket https://github.com/roustem/AsyncSocket類庫,將RunLoop文件夾下的AsyncSocket.h,AsyncSocket.m,AsyncUdpSocket.h,AsyncUdpSocket.m 文件拷貝到自己的project中。
添加CFNetwork.framework, 再使用socket的文件頭:
#import "AsyncSocket.h" #import "AsyncUdpSocket.h"
1
2
3
使用
一、socket 連接
即時通訊最大的特點就是實時性,基本感覺不到延時或是掉線,所以必須對socket的連接進(jìn)行監(jiān)視與檢測,在斷線時進(jìn)行重新連接,如果用戶退出登錄,要將socket手動關(guān)閉,否則對服務(wù)器會造成一定的負(fù)荷。
一般來說,一個用戶只能有一個正在連接的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)行重新連接。
先創(chuàng)建一個單例,命名為Singleton
// Singleton.h #import "AsyncSocket.h" #define DEFINE_SHARED_INSTANCE_USING_BLOCK(block) static dispatch_once_t onceToken = 0; __strong static id sharedInstance = nil; dispatch_once(&onceToken, ^{ sharedInstance = block(); }); return sharedInstance; @interface Singleton : NSObject + (Singleton *)sharedInstance; @end //Singleton.m + (Singleton *)sharedInstance { static Singleton *sharedInstace = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstace = [[self alloc] init]; }); return sharedInstace; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
連接(長連接)
在.h文件中聲明socket變量和方法,并聲明代理; 在.m中實現(xiàn),連接時host與port都是由服務(wù)器指定。
//.h @property (nonatomic, strong) AsyncSocket *socket; // socket @property (nonatomic, copy ) NSString *socketHost; // socket的Host @property (nonatomic, assign) UInt16 socketPort; // socket的prot - (void)socketConnectHost; //socket連接 //.m - (void)socketConnectHost { self.socket = [[AsyncSocket alloc] initWithDelegate:self]; NSError *error = nil; [self.socket connectToHost:self.socketHost onPort:self.socketPort withTimeout:3 error:&error]; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
心跳
心跳通過計時器來實現(xiàn):在singleton.h中聲明一個定時器;在singleton.m中實現(xiàn)連接成功回調(diào)方法,并在此方法中初始化定時器,發(fā)送心跳在后文向服務(wù)器發(fā)送數(shù)據(jù)時說明。
//singleton.h @property (nonatomic, retain) NSTimer *connectTimer; // 計時器 //singleton.m #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]; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
二、socket斷開連接與重連
斷開連接
失去連接有幾種情況,服務(wù)器斷開,用戶主動cut,還可能有如QQ其他設(shè)備登錄被掉線的情況,不管那種情況,都能收到socket回調(diào)方法返回訊息,如果是用戶退出登錄或是程序退出而需要手動cut,在cut前對socket的userData賦予一個值來標(biāo)記為用戶退出,這樣可以在收到斷開信息時判斷究竟是什么原因?qū)е碌牡艟€。
在.h文件中聲明一個枚舉類型和斷開連接方法,并在.m中實現(xiàn)
//.h enum{ SocketOfflineByServer,// 服務(wù)器掉線,默認(rèn)為0 SocketOfflineByUser, // 用戶主動cut }; - (void)cutOffSocket; // 斷開socket連接 //.m // 切斷socket - (void)cutOffSocket { self.socket.userData = SocketOfflineByUser;// 聲明是由用戶主動切斷 [self.connectTimer invalidate]; [self.socket disconnect]; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
重連
//實現(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; } }
1
2
3
4
5
6
7
8
9
10
11
12
三、socket發(fā)送與接收數(shù)據(jù)
發(fā)送數(shù)據(jù)
實現(xiàn)心跳連接未完成的方法
// 心跳連接 - (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]; }
1
2
3
4
5
6
7
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];
1
2
接收數(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]; }
1
2
3
4
5
6
7
8
9
四、簡單使用說明
在用戶登錄后的第一個界面進(jìn)行socket的初始化連接操作,在得到數(shù)據(jù)后,將所需要顯示的數(shù)據(jù)放在singleton中,對變量進(jìn)行監(jiān)聽后做出相應(yīng)的操作即可。
[Singleton sharedInstance].socketHost = @"192.186.100.21";// host設(shè)定 [Singleton sharedInstance].socketPort = 10045;// port設(shè)定 // 在連接前先進(jìn)行手動斷開 [Singleton sharedInstance].socket.userData = SocketOfflineByUser; [[Singleton sharedInstance] cutOffSocket]; // 確保斷開后再連,如果對一個正處于連接狀態(tài)的socket進(jìn)行連接,會出現(xiàn)崩潰 [Singleton sharedInstance].socket.userData = SocketOfflineByServer; [[Singleton sharedInstance] socketConnectHost];
1
2
3
4
5
6
7
8
9
10
iOS Socket編程
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。