開始使用ZMQ
安裝大坑跳過之後,接下來的是另外一個大坑,先說ZMQ的所有請求都是阻塞式的,因此使用你必須開一個線程來管理,再者ZMQ沒有Success跟Fail的Call back,完全靠伺服器回給你的參數自己判斷是成功還是失敗,所以理所當然的也不會有HTTP Status Code,第三就是ZMQ沒有連線逾時的判斷,他的重連機制會一直連一直連,直到連上為止,已Socket來說這是一個非常方便的功能,但是正如前言所說,連登入都要使用ZMQ了,如果一直重連的話,不就卡死了嗎?所以針對ZMQ流程我自己想出了以下規劃,希望有更好方法的網友也能提出來大家一起學習研究:
既然沒有Success跟Fail的Call Back,那我們只好自己動手做:
先舉例有一個測試的API叫做SeverTime,我們首先在Model的.h檔加入:

沒錯~非常眼熟,跟AFNetworking一模一樣。
再來.m檔一樣加入:

到這邊我們前置作業就算完成了
接著是關於Req/Rep模式,相當於一般的網路請求,送出Req,拿到Rep:
- (void)severTime {
ZMQContext *ctx = [[ZMQContext alloc] initWithIOThreads:1]; //似乎可以指定線程,但我沒試過
NSString *endpoint = 這裡是伺服器IP位置;
ZMQSocket *requester = [ctx socketWithType:ZMQ_REQ]; //Socket模式為Req
BOOL didConnect = [requester connectToEndpoint:endpoint];
if (!didConnect) {
NSLog(@"*** Failed to connect to endpoint [%@].", endpoint);
}
int kMaxRequest = 1;
NSString *severTimeRequest = [NSString stringWithFormat:@"{\"__api\":\"ServerTime\"}"]; //請求的參數
NSData *request = [severTimeRequest dataUsingEncoding:NSUTF8StringEncoding];
for (int request_nbr = 0; request_nbr < kMaxRequest; ++request_nbr) {
[requester sendData:request withFlags:0];
NSLog(@"Sending request %d.", request_nbr);
NSData *reply = [requester receiveDataWithFlags:0];
NSMutableDictionary *jsondata = [NSJSONSerialization JSONObjectWithData:reply options:0 error:nil]; //拿到reply就把他轉成NSMutableDictionary來使用
/*
這邊成功會回傳
{
success = 1;
severTime = XXXXXXXXX;
}
失敗則是
{
success = 0;
errMsg = "";
}
*/
NSNumber *success = jsondata[@"success"]; //所以我們可以透過success來判斷
switch (success.integerValue) {
case 0:
{
self.failBlock(jsondata); //0就塞進失敗的Block
}
break;
case 1:
{
self.finishBlock(jsondata); //1就塞進成功的Block
}
break;
default:
break;
}
}
[ctx closeSockets]; 使用完後就關閉ZMQ Socket
}
一般使用也是跟AFNetworking一樣:
[[SeverTime requestWithFinishBlock:^(id object) {
NSLog(@"successPbject : %@",object);
}failBlock:^(id failObject) {
NSLog(@"failObject : %@",failObject);
}] severTime];
但正如我之前所說的,他是阻塞式的請求,而且沒有Timeout,因此我們的流程要變成這樣:
先寫一個Timeout的方法,裡面時間到就把執行序關掉,因為必須做到手動控制,所以請使用NSThread。
登入的Button按下後會有一個透明淺灰的BlockView把整個畫面擋住不讓使用者做任何動作,接著開啟一個延時執行的Timer,時間到就把它關掉,然後開啟執行序,執行上面包裝好的請求方法,不管成功或是失敗,都要先把請求的執行序關掉,然後回到主線程:
[self performSelectorOnMainThread:@selector(dismissBlockView) withObject:nil waitUntilDone:YES];
關掉延時執行的Timer跟dismiss BlockView,當然時間到了也是一樣。
這樣就模擬出一個完整的Req/Rep請求了。
再來是重頭戲,也就是Pub/Sub登場,這邊才是ZMQ Socket的精華,所謂的Pub跟Sub就是訂閱模式,ZMQ Socket的channel會一直打資料,想要接到這個channel的資料,我們就必須透過一個subIP跟subTopic去對這個Chananl做訂閱,前著是我們透過Req/Rep請求之後,伺服器會回給我們:
{
success = 1;
subIP = XXX.XXX.XX.XXX;
subTopic = XXXXXXXXXXXXXX;
}
然後我們就把supIP跟subTopic拿出來使用:
subIP = jsondata[@"subIP"];
subTopic = jsondata[@"subTopic"];
前面的Req/Rep都一樣,就不寫了,不一樣的是,這邊成功我們才進行下一步驟,失敗就跟Req/Rep的失敗處理一模一樣,所以成功我們不能把線程砍掉,因為要繼續接著做Sub的動作:
ZMQContext *subctx = [[ZMQContext alloc] initWithIOThreads:1];
ZMQSocket *subrequester = [subctx socketWithType:ZMQ_SUB]; //Socket模式為sub
BOOL subdidConnect = [subrequester connectToEndpoint:subIP]; //這裡放subip
BOOL didSub = [subrequester subscribe:subTopic]; //這裡放subTopic
if (!subdidConnect) {
NSLog(@"*** Failed to connect to endpoint [%@].", subIP);
}
if (!didSub) {
NSLog(@"*** Failed to subing");
}
//用一個無限迴圈來接收Socket傳來的資料
while (1) {
NSData *reply = [subrequester receiveDataWithFlags:0];
NSMutableDictionary *subjsondata = [NSJSONSerialization JSONObjectWithData:reply options:0 error:nil];
NSLog(@"Received reply %@", subjsondata);
}
至於停止的方法,正常來說是直接關掉執行緒就好,但是我這個案子上有多一個API是主動停止,一樣是在開一條序去做Req/Rep請求,成功後再把兩個線程(Sub線程還有剛剛開的Req/Rep請求)殺掉就可以了。
也就是說我們的流程是,Req/Rep成功後把supIP跟subTopic撈出來,再開啟sub訂閱channel跟接收資料,至於使用Socket刷新UI,使用Notification的內部通知就可以了,記得要回到主線程再更新UI,NSThread停止線程後,請記得一定要 thread = nil; 。