最近公司項(xiàng)目需要用到推送,但是公司的開發(fā)者賬號(hào)沒有申請(qǐng)好,無法使用遠(yuǎn)程推送失都,加上后臺(tái)大大說用第三方推送不方便調(diào)試,于是一起商定使用socket實(shí)現(xiàn)消息推送幸冻。
實(shí)現(xiàn)原理
利用socket與遠(yuǎn)程服務(wù)器建立長連接進(jìn)行消息通訊嗅剖,服務(wù)端通過檢測(cè)與客戶端的連接狀態(tài)進(jìn)行消息推送,客戶端收到數(shù)據(jù)后給服務(wù)器發(fā)送應(yīng)答消息讓服務(wù)器知道消息的投遞情況嘁扼。優(yōu)點(diǎn):調(diào)試方便,使用靈活黔攒。缺點(diǎn):無法實(shí)現(xiàn)App后臺(tái)接收消息趁啸。
主要實(shí)現(xiàn)代碼如下
- 創(chuàng)建socket
// 屬性定義
// 超時(shí)等待時(shí)長
static NSUInteger timeout = 45;
// socket
@property(nonatomic,strong)GCDAsyncSocket *socket;
// 定時(shí)器
@property(nonatomic,strong)NSTimer *timer;
// 時(shí)間計(jì)數(shù)
@property(nonatomic,assign)NSUInteger count;
// 連接狀態(tài)標(biāo)識(shí)
@property(nonatomic,assign)BOOL isConnect;
// 登錄狀態(tài)標(biāo)識(shí)
@property(nonatomic,assign)BOOL isLogin;
// 重連計(jì)數(shù)
@property(nonatomic,assign)NSUInteger reconnectCount;
// 懶加載方法
- (GCDAsyncSocket *)socket {
if (_socket == nil) {
_socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
}
return _socket;
}
- (NSTimer *)timer {
if (_timer == nil) {
_timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
[_timer setFireDate:[NSDate dateWithTimeIntervalSinceNow:0]];
[[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
}
return _timer;
}
- 建立連接
- (void)startConnectWithURLStr:(NSString *)urlStr {
// 開啟定時(shí)器
self.timer.fireDate = [NSDate dateWithTimeIntervalSinceNow:0];
// 處理URL字符串
NSRange range = [urlStr rangeOfString:@":"];
NSString *host = [urlStr substringToIndex:range.location];
NSString *port = [urlStr substringFromIndex:range.location+range.length];
// 連接(如果已經(jīng)連接則調(diào)用登錄方法)
if (self.socket.isConnected == NO) {
[self.socket connectToHost:host onPort:port.integerValue error:nil];
[self timer];
NSLog(@"開始連接主機(jī):%@ 端口號(hào):%@",host,port);
}
else {
[self startLogin];
}
}
- 向服務(wù)器發(fā)起登錄請(qǐng)求
// 登錄方法
- (void)startLogin {
NSLog(@"開始登錄强缘。。不傅。");
// 獲取登錄報(bào)文數(shù)據(jù)
NSData *data = [self getLoginData];
// 發(fā)送數(shù)據(jù)
NSLog(@"發(fā)送登錄數(shù)據(jù):%@",data);
[self.socket writeData:data withTimeout:timeout tag:0];
// 啟動(dòng)socket讀數(shù)據(jù)等待(必須調(diào)用此方法才能收到服務(wù)器傳輸?shù)臄?shù)據(jù))
[self.socket readDataWithTimeout:timeout tag:0];
// 重置定時(shí)器計(jì)數(shù)
_count = 0;
}
- 登錄成功定時(shí)發(fā)送心跳包
// 定時(shí)器回調(diào)方法
- (void)timerAction {
// 計(jì)時(shí)遞增
_count += 1;
if (_isConnect) { //連接成功
if (_isLogin) { //登錄成功
if (_count >= 40) { //每40秒進(jìn)行一次操作
// 發(fā)送心跳包
[self sendHeartBeatData];
_count = 0;
}
}
else { //登錄失敗
if (_count > timeout) {
// 重登錄
[self startLogin];
NSLog(@"socket重新登錄!");
_count = 0;
}
}
}
else { //連接失敗
if (_count >= timeout) {
// 重連
[self startConnect];
_reconnectCount+=1;
NSLog(@"socket重新連接!--%zd次-",_reconnectCount);
_count = 0;
}
}
}
- 代理方法(GCDAsyncSocketDelegate)
// 即將連接到主機(jī)
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
NSLog(@"--socket--連接成功");
// 重置重連接計(jì)數(shù)
_reconnectCount = 0;
// 置位連接狀態(tài)標(biāo)識(shí)
_isConnect = YES;
// 重設(shè)置定時(shí)計(jì)數(shù)
_count = 0;
// 啟動(dòng)登錄
[self startLogin];
}
// socket連接斷開
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
if (err.code == 4) {
// 讀取超時(shí)
NSLog(@"獲取socket服務(wù)器回應(yīng)超時(shí)");
// 手動(dòng)斷開并重連
[self stopConnect];
[self startConnect];
return;
}
else if (err.code == 60) {
NSLog(@"socket連接超時(shí)");
// 手動(dòng)斷開并重連
[self stopConnect];
[self startConnect];
}
else {
NSLog(@"--socket--連接失敗--err:%@",err);
if (err != nil) {
// 重置標(biāo)識(shí)位,定時(shí)重連
_isConnect = NO;
_isLogin = NO;
}
}
}
// 讀取到服務(wù)端發(fā)送的數(shù)據(jù)
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
NSLog(@"--socket--Read:%@",data);
// 調(diào)用數(shù)據(jù)解析方法解析數(shù)據(jù)
[self syntacticReadData:data];
// 進(jìn)行下一次數(shù)據(jù)讀取
[self.socket readDataWithTimeout:timeout tag:0];
}
- 手動(dòng)斷開連接
// 手動(dòng)斷開連接方法
- (void)stopConnect {
// 停止定時(shí)器
self.timer.fireDate = [NSDate distantFuture];
// 釋放socket連接
[self.socket disconnect];
// 設(shè)置檢查標(biāo)識(shí)
_isConnect = NO;
_isLogin = NO;
// 重設(shè)重連計(jì)數(shù)
_reconnectCount = 0;
_count = 0;
NSLog(@"斷開socket連接");
}
個(gè)人總結(jié)
第一次使用GCDAsyncSocket進(jìn)行開發(fā),剛開始以為創(chuàng)建socket之后設(shè)置好代理就可以從代理方法中讀取到服務(wù)器的回應(yīng)數(shù)據(jù)屋讶,可是一直都是可以發(fā)送數(shù)據(jù)給服務(wù)器而沒有收到服務(wù)器給的回應(yīng)數(shù)據(jù)(讀取數(shù)據(jù)的哪個(gè)代理方法根本就不走)怀酷,于是經(jīng)過一番百度查找才知道原來GCDAsyncSocket是需要手動(dòng)調(diào)用讀數(shù)據(jù)的方法并設(shè)定超時(shí)等待才能讀取到數(shù)據(jù)的,個(gè)人建議在發(fā)送數(shù)據(jù)的時(shí)候就調(diào)用一次讀數(shù)據(jù)的方法并設(shè)定好合適的超時(shí)等待時(shí)間崖疤。