iOS開發(fā)中可以使用開源庫CocoaAsyncSocket簡化socket開發(fā)
**1.連接socket **
//創(chuàng)建一個TCP服務 連接到服務器
- (void)createTcpSocket {
if (self.asyncSocket==nil) {
self.asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
}
if (self.asyncSocket.isConnected) {
} else {
NSError *error;
[self.asyncSocket connectToHost:_socketHost onPort:_socketPort withTimeout:-1 error:&error];
if (error) {
NSLog(@"%@",error);
}
}
}
2.實現(xiàn)socket代理方法
//已經連接到服務器
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
NSLog(@"連接成功");
}
// 連接斷開
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
NSLog(@"連接斷開");
}
//消息發(fā)送成功 代理函數(shù) 向服務器 發(fā)送消息
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag {
NSLog(@"消息發(fā)送成功");
}
//已經接收服務器返回來的數(shù)據(jù)
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
NSLog(@"接收到服務器返回的數(shù)據(jù)");
}
3.向服務器發(fā)送消息
[self.asyncSocket writeData:self.message withTimeout:-1 tag:0];
以上只是簡單的介紹CocoaAsyncSocket的使用,下面結合實際開發(fā)中的需求來講解開發(fā)中的關鍵點,比如 tag 用來干嘛???
在開發(fā)中前端和后端會約定一個固定的數(shù)據(jù)(消息)格式,按照這個格式來讀取數(shù)據(jù),就能把每組數(shù)據(jù)劃分出來,也就較好的解決了
粘包掉包的問題,數(shù)據(jù)不完整時也能獲知數(shù)據(jù)的缺失。
舉個栗子,如下表格是規(guī)定好的消息格式
| 起始符 | 目標地址 | 原地址 | 數(shù)據(jù)長度 | 發(fā)送/接收 的數(shù)據(jù) | 檢驗符 |
|---|---|---|---|---|---|
| 0x02(1Byte) | dest(2Byte) | src(2Byte) | dataLength(2Byte) | data (數(shù)據(jù)) | CRC校驗 (1Byte) |
注:以上的消息格式分成3塊,數(shù)據(jù)和校驗符在這里稱為身體部分
1.消息頭部:起始符 + 目標地址 + 原地址 + 數(shù)據(jù)長度 = 7Byte,(頭部包含了那么多信息,并且長度是固定的,所以我們接收消息的時候,要先從頭部開始入手)
2.消息體:要發(fā)送或者接收到的數(shù)據(jù),長度為dataLength
3.校驗符:用來檢驗接收的數(shù)據(jù)是否完整一致(開發(fā)中可能沒有)
** 為了能夠在收到消息時,先獲取到頭部信息 ,我們需要用到 tag **
//固定的頭部長度
//起始符(1Byte) + 目標地址(2byte) + 源地址(2byte) + 應用層數(shù)據(jù)長度(2byte) = 7Byte
#define KPacketHeaderLength 7
typedef NS_ENUM(NSInteger ,KReadDataType){
TAG_FIXED_LENGTH_HEADER = 10,//消息頭部tag
TAG_RESPONSE_BODY = 11//消息體tag
};
** 所以在socket連接成功之后應該這樣寫 **
//已經連接到服務器
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
NSLog(@"連接成功");
// -1不超時一直讀取 等待數(shù)據(jù),先讀取頭部信息長度為 KPacketHeaderLength
// tag為頭部消息tag,這個在接收到數(shù)據(jù)時,用來區(qū)分此次讀取的是頭部數(shù)據(jù)還是消息體數(shù)據(jù)
[self.asyncSocket readDataToLength:KPacketHeaderLength withTimeout:-1 tag:TAG_FIXED_LENGTH_HEADER];
}
** 在接受到服務器的數(shù)據(jù)時 **
//已經接收服務器返回來的數(shù)據(jù)
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
NSLog(@"接收到服務器返回的數(shù)據(jù)");
// 根據(jù)tag來做不同的操作
switch (tag) {
case TAG_FIXED_LENGTH_HEADER:
{
self.headData = data;
Byte *bytes = (Byte *)[data bytes];
//身體長度 = 消息體 + 1Byte的校驗碼
self.bodyLength = [self readShort:bytes location:5] + 1;
// 從數(shù)據(jù)緩沖區(qū)讀取完整的身體部分數(shù)據(jù),此時tag變成了TAG_RESPONSE_BODY
[self.asyncSocket readDataToLength:self.bodyLength withTimeout:-1 tag:TAG_RESPONSE_BODY];
}
break;
case TAG_RESPONSE_BODY:{
//如果當前讀取出來的數(shù)據(jù)長度沒有達到完整包身體的長度,則包不完整(則根據(jù)當前接收的數(shù)據(jù)長度,和身體長度比較,繼續(xù)讀取兩者相差的數(shù)據(jù)長度)
//讀取完身體數(shù)據(jù),開始校驗,校驗成功,則展示數(shù)據(jù)并且,開始等待下一次讀取數(shù)據(jù),tag變成TAG_FIXED_LENGTH_HEADER
[self.asyncSocket readDataToLength:KPacketHeaderLength withTimeout:-1 tag:TAG_FIXED_LENGTH_HEADER];
}
break;
default:
break;
}
}
- (short)readShort:(Byte *)bytes location:(int)location {
return OSReadLittleInt16(bytes, location);
}
這些只是開發(fā)中可能會遇到的點,實際中還有很多細節(jié)要處理