前言
寫(xiě)上一篇文章之前完全是想總結(jié)一下自己對(duì)socket通訊流程的總結(jié)班巩,加深自己的印象,沒(méi)有想到會(huì)有很多人關(guān)注這一塊,再說(shuō)自己是個(gè)socket的新手,寫(xiě)出來(lái)的文章沒(méi)有多少人去看鹰椒,所以上一篇文章寫(xiě)得不是很詳細(xì),但是發(fā)現(xiàn)還是有很多人關(guān)注券坞,所以我就趁今天是個(gè)周末,花個(gè)2,3小時(shí)來(lái)總結(jié)一下本周socket通訊的進(jìn)程,供想要了解的朋友參考寺渗。
正文
首先提一下網(wǎng)上CocoaAsyncSocket
框架主要包括AsyncSocket
和GCDSocket
,我使用的是后者兰迫,就是多線程Socket信殊,主要區(qū)別就是前者基于NSRunLoop
,后者是在多線程進(jìn)行逮矛,據(jù)我項(xiàng)目目前的情況來(lái)看鸡号,GCDSocket
內(nèi)至少開(kāi)辟了5個(gè)線程转砖。
自己項(xiàng)目目前的進(jìn)度是將socket單獨(dú)封裝成一個(gè)單獨(dú)的類须鼎,也就是寫(xiě)成一個(gè)單例類鲸伴,這樣寫(xiě)的好處顯而易見(jiàn),這樣我們建立通訊連接晋控,數(shù)據(jù)請(qǐng)求就方便了很多汞窗,因?yàn)槲覀儾豢赡苋ッ恳粋€(gè)需要數(shù)據(jù)的界面去創(chuàng)建socket進(jìn)行連接,想辦法把問(wèn)題簡(jiǎn)單化使我們程序員必須做的重要一部分赡译。想必網(wǎng)上關(guān)于socket
的文章大多數(shù)都是大家你抄我我抄你而來(lái)仲吏,寫(xiě)一下socket創(chuàng)建,建立連接蝌焚,實(shí)現(xiàn)代理方法裹唆,收發(fā)數(shù)據(jù),沒(méi)有更深一步的文章只洒。其次大家有沒(méi)有這樣的一個(gè)疑惑许帐,網(wǎng)上為什么沒(méi)有開(kāi)源的關(guān)于socket通訊的集成好的第三方框架供我使用呢?我直接收發(fā)數(shù)據(jù)就好了毕谴,還要那么麻煩建立連接成畦,一堆問(wèn)題去處理。像普通的網(wǎng)絡(luò)數(shù)據(jù)請(qǐng)求涝开,網(wǎng)上有封裝好的AFNetworking
循帐,為什么socket沒(méi)有!Rㄎ洹拄养!那我來(lái)告訴你基于socket的TCP的長(zhǎng)連接往往數(shù)據(jù)傳輸協(xié)議是自定義的,所以這個(gè)不可能有現(xiàn)成的框架來(lái)用银舱,必須根據(jù)自己的定義類型來(lái)收發(fā)數(shù)據(jù)衷旅,否則就無(wú)法解析。舉個(gè)例子纵朋,我收數(shù)據(jù)需要這樣的格式[^1^2]
,那當(dāng)我收到數(shù)據(jù)data
我必須得按這種格式校驗(yàn)柿顶,否則我就無(wú)法收到。上面首先將socket的邏輯說(shuō)清楚操软,下面我們上代碼嘁锯,首先說(shuō)明一點(diǎn),我這個(gè)單例封裝的我個(gè)人覺(jué)得比較完美聂薪,跟普通網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)那種格式一某一樣家乘,最大的不同我是采用代理的方式回傳數(shù)據(jù),而不是block
藏澳。
#import "GCDAsyncSocket.h"
@protocol GPSSocketServeDelegate <NSObject>
/*** 連接服務(wù)器成功以后回調(diào) */
- (void)connectSeverSucess:(NSString *)sucess;
/*** 登錄返回判斷 */
- (void)ClickIsSucess:(BOOL)isSucess StrParam2:(NSString *)strParam2;
/*** 返回請(qǐng)求數(shù)據(jù) */
- (void)ClintReceCommData:(NSMutableArray *)data StrDataType:(NSString *)strDataType strParam2:(NSString *)strParam2;
@end
@class GPSSocketServeDelegate;
@interface GPSSocketServe : NSObject <GCDAsyncSocketDelegate>
@property (nonatomic,weak) id<GPSSocketServeDelegate>delegate;
@property (nonatomic,strong) GCDAsyncSocket *socket;
//在.h文件里面我給出了以下6個(gè)接口仁锯,建立連接,斷開(kāi)連接翔悠,其他的就是請(qǐng)求數(shù)據(jù)接口的封裝
/*** 獲取本類對(duì)象 */
+ (GPSSocketServe *)sharedSocketServe;
/*** socket連接 */
- (void)startConnectSocket;
/*** 斷開(kāi)socket開(kāi)始連接 */
- (void)disConnectSocket;
/**
* 登錄接口
*
* @param username 用戶名
* @param password 用戶密碼
*/
- (void)userClick:(NSString *)username UserPassword:(NSString *)password;
/**
* 用戶登錄調(diào)第一集部門(mén)表
*
* @param p_strManagerCode 為用戶的最高部門(mén)code
* @param p_strWGLoginName 用戶名稱
*/
- (void)requestManagerDep:(NSString *)p_strManagerCode P_strWGLoginName:(NSString *)p_strWGLoginName;
/**
* 用戶調(diào)第二級(jí)以及以后的部門(mén)表
*
* @param p_strManagerCode 為當(dāng)前級(jí)的部門(mén)code
* @param p_strCurrentDepName 為當(dāng)前級(jí)的部門(mén)名稱
*/
- (void)requestSencondManagerDep:(NSString *)p_strCurrentDepCode P_strCurrentDepName:(NSString *)p_strCurrentDepName;
/**
* 調(diào)查詢部門(mén)下的車輛列表
*
* @param p_strManagerCode 為當(dāng)前級(jí)的部門(mén)code
* @param p_strCurrentDepName 為當(dāng)前級(jí)的部門(mén)名稱
*/
- (void)requestCarsOfDep:(NSString *)p_strCurrentDepCode P_strCurrentDepName:(NSString *)p_strCurrentDepName;
GPSSocketServe.m文件业崖,接口的實(shí)現(xiàn)
//創(chuàng)建單例對(duì)象野芒,重寫(xiě)allocWithZone方法,保證這個(gè)對(duì)象在內(nèi)存中只有一份
+ (GPSSocketServe *)sharedSocketServe{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
socketServe = [[self alloc] init];
});
return socketServe;
}
+(id)allocWithZone:(NSZone *)zone
{
@synchronized(self)
{
if (socketServe == nil)
{
socketServe = [super allocWithZone:zone];
return socketServe;
}
}
return nil;
}
- (void)startConnectSocket
{
//這個(gè)里面主要就是創(chuàng)建socket對(duì)象双炕,建立連接狞悲,代碼就不考了,可以參照上一篇
}
然后我說(shuō)說(shuō)我主要遇到的問(wèn)題吧
1.原始的收到數(shù)據(jù)和我發(fā)送數(shù)據(jù)是在兩個(gè)不同的方法里面妇斤,我們?nèi)绾位貍鲾?shù)據(jù)摇锋?
2.當(dāng)我調(diào)用一級(jí)部門(mén)表的時(shí)候,發(fā)現(xiàn)收不到回傳數(shù)據(jù)站超,然后一步一步的調(diào)荸恕,發(fā)現(xiàn)我發(fā)送的數(shù)據(jù)少5個(gè)字符長(zhǎng)度,這個(gè)哪里出了問(wèn)題死相?
關(guān)于第一個(gè)問(wèn)題戚炫,其實(shí)我第一個(gè)感覺(jué)就是使用block,這樣呢回傳數(shù)據(jù)感覺(jué)很方便媳纬,例如當(dāng)我發(fā)送登陸双肤,就可以收到回調(diào)成功然后進(jìn)行跳轉(zhuǎn),當(dāng)時(shí)我已經(jīng)成功實(shí)現(xiàn)了block回傳數(shù)據(jù)钮惠,大概寫(xiě)一下實(shí)現(xiàn)的方法
//定義一個(gè)全局的block
//原因:發(fā)送請(qǐng)求和收到數(shù)據(jù)的不在一個(gè)方法里面
//定義一個(gè)block茅糜,申明一個(gè)屬性,這個(gè)block帶兩個(gè)參數(shù)素挽,一個(gè)是類型的字符串蔑赘,另一個(gè)便是回傳數(shù)據(jù)
typedef void(^requestDataBlock)(NSString *string,NSMutableArray *data);
@property (nonatomic,copy) requestDataBlock dataBlock;
//發(fā)送數(shù)據(jù)的同一接口
- (void)ClintSendCommData:(short)intDataType strDataType:(NSString *)strDataType stSetType:(NSString *)stSetType strSetSN:(NSString *)strSetSN strSetSN1:(NSString *)strSetSN1 strAlmComType:(NSString *)strAlmComType strHisType:(NSString *)strHisType strPosType:(NSString *)strPosType strFadeType:(NSString *)strFadeType strRecogType:(NSString *)strRecogType strRecogType1:(NSString *)strRecogType1 StrParam1:(NSString *)strParam1 StrParam2:(NSString *)strParam2 StrParam3:(NSString *)strParam3 StrParam4:(NSString *)strParam4 StrParam5:(NSString *)strParam5 StrParam6:(NSString *)strParam6 StrParam7:(NSString *)strParam7 StrParam8:(NSString *)strParam8
//在這個(gè)方法后面添加上block `success:(^requestDataBlock)(NSString *string,NSMutableArray *data)`
//在這個(gè)方法里面self.dataBlock = requestDataBlock;
//再接收數(shù)據(jù)的方法里面回調(diào)block
self.dataBlock(參數(shù)一,返回?cái)?shù)據(jù));
這樣的話就實(shí)現(xiàn)了block回調(diào)數(shù)據(jù)预明,比較容易缩赛,前提是你對(duì)block足夠的了解。
可是為什么我放棄了這個(gè)方法了撰糠,因?yàn)?.代碼的冗余率太多酥馍,因?yàn)槭嵌嗑€程,當(dāng)我更新UI我必須回到主線程阅酪,這個(gè)代碼得多大一塊旨袒;而且2.可擴(kuò)展性不好,這個(gè)登錄接口需要回傳一個(gè)參數(shù)术辐,調(diào)用需要回傳多個(gè)參數(shù)砚尽,這樣共用性不好,所以我就想到了代理辉词,不同的接口我調(diào)用不同的代理方法必孤,以后再有新的類型回傳數(shù)據(jù),我大不了再寫(xiě)一個(gè)回傳接口而已瑞躺》筇拢回調(diào)函數(shù)請(qǐng)看上面兴想。
//這個(gè)就是后臺(tái)提供給我的登錄接口,這寫(xiě)也是用Java寫(xiě)的购啄,不過(guò)我已經(jīng)將他們改成oc的方法
ClintSendCommData(1105, "0002", "", "", "", "", "", "", "", "", "", p_strWGLoginName,p_strWGPassword, "", "", "", "", "", "");
大家一看這調(diào)用接口襟企,需要傳的參數(shù)就兩個(gè)嘱么,所以你們想到了什么狮含?反正我想到的是再封裝一層
- (void)userClick:(NSString *)username UserPassword:(NSString *)password
{
[self ClintSendCommData:1105 strDataType:@"0002" stSetType:@"" strSetSN:@"" strSetSN1:@"" strAlmComType:@"" strHisType:@"" strPosType:@"" strFadeType:@"" strRecogType:@"" strRecogType1:@"" StrParam1:username StrParam2:password StrParam3:@"" StrParam4:@"" StrParam5:@"" StrParam6:@"" StrParam7:@"" StrParam8:@""];
}
這樣我就調(diào)用這個(gè)方法就OK,其他的調(diào)用數(shù)據(jù)的方法類似曼振。
第二個(gè)問(wèn)題出在什么地方呢几迄?其實(shí)還是跟上一篇文章編碼有關(guān),后臺(tái)服務(wù)器采用的是GB2312
冰评,我將它轉(zhuǎn)換為UTF-8
了映胁,在發(fā)送數(shù)據(jù)的時(shí)候
//自定義發(fā)送數(shù)據(jù)接口,底層其實(shí)是調(diào)用發(fā)送數(shù)據(jù)的方法甲雅,writedata:解孙,我只不過(guò)封裝了一層
[self SendData:intDataType CharDatahead:(char *)[strData cStringUsingEncoding:enc] DataLen:intDataLen];
前面提到,我發(fā)送時(shí)候少了5個(gè)字符抛人,這是跟我發(fā)送的里面包含了漢字弛姜,漢字的一般占用2個(gè)字符,而我們普通計(jì)算這個(gè)長(zhǎng)度length當(dāng)做一個(gè)字符來(lái)算妖枚,所以intDataLen
計(jì)算是不對(duì)的廷臼,登錄接口之所以對(duì),那是因?yàn)闆](méi)有漢字绝页,我首先在計(jì)算長(zhǎng)度的NSString的方法里面沒(méi)有找到相應(yīng)的方法荠商,不知道有沒(méi)有朋友找到,有找到的可以留言給我续誉,非常感謝莱没!那我說(shuō)說(shuō)我在網(wǎng)上找到的計(jì)算包含漢字的方法(其實(shí)這個(gè)方法是經(jīng)過(guò)我改造過(guò)的方法)
/**
* 計(jì)算包含中文的字符的字符串長(zhǎng)度
*/
-(int)lengthOfStringContainChinese:(NSString*)c{
int strlength = 0;
char* p = (char*)[c cStringUsingEncoding:NSUnicodeStringEncoding];
for (int i=0 ; i<[c lengthOfBytesUsingEncoding:NSUnicodeStringEncoding] ;i++) {
if (*p) {
p++;
strlength++;
}
else {
p++;
}
}
return strlength;
}
這樣計(jì)算就正確了。
以上是我在解決的主要問(wèn)題酷鸦,其實(shí)我在真機(jī)調(diào)試的時(shí)候郊愧,發(fā)現(xiàn)了一個(gè)比較嚴(yán)重的問(wèn)題,在網(wǎng)絡(luò)請(qǐng)求的時(shí)候井佑,網(wǎng)絡(luò)不好属铁,會(huì)出現(xiàn)嚴(yán)重的內(nèi)存暴增,程序就會(huì)閃退躬翁,不過(guò)這個(gè)問(wèn)題我已經(jīng)解決掉了焦蘑,通過(guò)的是調(diào)試工具,這個(gè)下一期我會(huì)教大家調(diào)試的方法盒发,首先科普一下例嘱,手機(jī)內(nèi)存一般達(dá)到30M的話就會(huì)自動(dòng)閃退狡逢,有遇到這個(gè)問(wèn)題的朋友可以仔細(xì)研究研究。