前言
寫上一篇文章之前完全是想總結(jié)一下自己對(duì)socket通訊流程的總結(jié),加深自己的印象,沒有想到會(huì)有很多人關(guān)注這一塊,再說自己是個(gè)socket的新手,寫出來的文章沒有多少人去看,所以上一篇文章寫得不是很詳細(xì),但是發(fā)現(xiàn)還是有很多人關(guān)注,所以我就趁今天是個(gè)周末,花個(gè)2,3小時(shí)來總結(jié)一下本周socket通訊的進(jìn)程,供想要了解的朋友參考。
正文
首先提一下網(wǎng)上CocoaAsyncSocket框架主要包括AsyncSocket和GCDSocket,我使用的是后者,就是多線程Socket,主要區(qū)別就是前者基于NSRunLoop,后者是在多線程進(jìn)行,據(jù)我項(xiàng)目目前的情況來看,GCDSocket內(nèi)至少開辟了5個(gè)線程。
自己項(xiàng)目目前的進(jìn)度是將socket單獨(dú)封裝成一個(gè)單獨(dú)的類,也就是寫成一個(gè)單例類,這樣寫的好處顯而易見,這樣我們建立通訊連接,數(shù)據(jù)請(qǐng)求就方便了很多,因?yàn)槲覀儾豢赡苋ッ恳粋€(gè)需要數(shù)據(jù)的界面去創(chuàng)建socket進(jìn)行連接,想辦法把問題簡單化使我們程序員必須做的重要一部分。想必網(wǎng)上關(guān)于socket的文章大多數(shù)都是大家你抄我我抄你而來,寫一下socket創(chuàng)建,建立連接,實(shí)現(xiàn)代理方法,收發(fā)數(shù)據(jù),沒有更深一步的文章。其次大家有沒有這樣的一個(gè)疑惑,網(wǎng)上為什么沒有開源的關(guān)于socket通訊的集成好的第三方框架供我使用呢?我直接收發(fā)數(shù)據(jù)就好了,還要那么麻煩建立連接,一堆問題去處理。像普通的網(wǎng)絡(luò)數(shù)據(jù)請(qǐng)求,網(wǎng)上有封裝好的AFNetworking,為什么socket沒有!??!那我來告訴你基于socket的TCP的長連接往往數(shù)據(jù)傳輸協(xié)議是自定義的,所以這個(gè)不可能有現(xiàn)成的框架來用,必須根據(jù)自己的定義類型來收發(fā)數(shù)據(jù),否則就無法解析。舉個(gè)例子,我收數(shù)據(jù)需要這樣的格式[^1^2],那當(dāng)我收到數(shù)據(jù)data我必須得按這種格式校驗(yàn),否則我就無法收到。上面首先將socket的邏輯說清楚,下面我們上代碼,首先說明一點(diǎn),我這個(gè)單例封裝的我個(gè)人覺得比較完美,跟普通網(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è)接口,建立連接,斷開連接,其他的就是請(qǐng)求數(shù)據(jù)接口的封裝
/*** 獲取本類對(duì)象 */
+ (GPSSocketServe *)sharedSocketServe;
/*** socket連接 */
- (void)startConnectSocket;
/*** 斷開socket開始連接 */
- (void)disConnectSocket;
/**
* 登錄接口
*
* @param username 用戶名
* @param password 用戶密碼
*/
- (void)userClick:(NSString *)username UserPassword:(NSString *)password;
/**
* 用戶登錄調(diào)第一集部門表
*
* @param p_strManagerCode 為用戶的最高部門code
* @param p_strWGLoginName 用戶名稱
*/
- (void)requestManagerDep:(NSString *)p_strManagerCode P_strWGLoginName:(NSString *)p_strWGLoginName;
/**
* 用戶調(diào)第二級(jí)以及以后的部門表
*
* @param p_strManagerCode 為當(dāng)前級(jí)的部門code
* @param p_strCurrentDepName 為當(dāng)前級(jí)的部門名稱
*/
- (void)requestSencondManagerDep:(NSString *)p_strCurrentDepCode P_strCurrentDepName:(NSString *)p_strCurrentDepName;
/**
* 調(diào)查詢部門下的車輛列表
*
* @param p_strManagerCode 為當(dāng)前級(jí)的部門code
* @param p_strCurrentDepName 為當(dāng)前級(jí)的部門名稱
*/
- (void)requestCarsOfDep:(NSString *)p_strCurrentDepCode P_strCurrentDepName:(NSString *)p_strCurrentDepName;
GPSSocketServe.m文件,接口的實(shí)現(xiàn)
//創(chuàng)建單例對(duì)象,重寫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ì)象,建立連接,代碼就不考了,可以參照上一篇
}
然后我說說我主要遇到的問題吧
1.原始的收到數(shù)據(jù)和我發(fā)送數(shù)據(jù)是在兩個(gè)不同的方法里面,我們?nèi)绾位貍鲾?shù)據(jù)?
2.當(dāng)我調(diào)用一級(jí)部門表的時(shí)候,發(fā)現(xiàn)收不到回傳數(shù)據(jù),然后一步一步的調(diào),發(fā)現(xiàn)我發(fā)送的數(shù)據(jù)少5個(gè)字符長度,這個(gè)哪里出了問題?
關(guān)于第一個(gè)問題,其實(shí)我第一個(gè)感覺就是使用block,這樣呢回傳數(shù)據(jù)感覺很方便,例如當(dāng)我發(fā)送登陸,就可以收到回調(diào)成功然后進(jìn)行跳轉(zhuǎn),當(dāng)時(shí)我已經(jīng)成功實(shí)現(xiàn)了block回傳數(shù)據(jù),大概寫一下實(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ù),我大不了再寫一個(gè)回傳接口而已?;卣{(diào)函數(shù)請(qǐng)看上面。
//這個(gè)就是后臺(tái)提供給我的登錄接口,這寫也是用Java寫的,不過我已經(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è)問題出在什么地方呢?其實(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:,我只不過封裝了一層
[self SendData:intDataType CharDatahead:(char *)[strData cStringUsingEncoding:enc] DataLen:intDataLen];
前面提到,我發(fā)送時(shí)候少了5個(gè)字符,這是跟我發(fā)送的里面包含了漢字,漢字的一般占用2個(gè)字符,而我們普通計(jì)算這個(gè)長度length當(dāng)做一個(gè)字符來算,所以intDataLen計(jì)算是不對(duì)的,登錄接口之所以對(duì),那是因?yàn)闆]有漢字,我首先在計(jì)算長度的NSString的方法里面沒有找到相應(yīng)的方法,不知道有沒有朋友找到,有找到的可以留言給我,非常感謝!那我說說我在網(wǎng)上找到的計(jì)算包含漢字的方法(其實(shí)這個(gè)方法是經(jīng)過我改造過的方法)
/**
* 計(jì)算包含中文的字符的字符串長度
*/
-(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ì)算就正確了。
以上是我在解決的主要問題,其實(shí)我在真機(jī)調(diào)試的時(shí)候,發(fā)現(xiàn)了一個(gè)比較嚴(yán)重的問題,在網(wǎng)絡(luò)請(qǐng)求的時(shí)候,網(wǎng)絡(luò)不好,會(huì)出現(xiàn)嚴(yán)重的內(nèi)存暴增,程序就會(huì)閃退,不過這個(gè)問題我已經(jīng)解決掉了,通過的是調(diào)試工具,這個(gè)下一期我會(huì)教大家調(diào)試的方法,首先科普一下,手機(jī)內(nèi)存一般達(dá)到30M的話就會(huì)自動(dòng)閃退,有遇到這個(gè)問題的朋友可以仔細(xì)研究研究。
