Protobuf的配置网上有很多教程,这里就不说了
这里主要讲 Socket 的使用
我用的是 CocoaAsyncSocket
首先用 Cocoapods 导入 CocoaAsyncSocket pod 'CocoaAsyncSocket'
然后就是初始化,连接服务器,这里的服务器地址和端口号问后台要
// 连接服务器
self.clientSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
NSError *error = nil;
self.connected = [self.clientSocket connectToHost:服务器地址 onPort:端口号 viaInterface:nil withTimeout:-1 error:&error];
if(self.connected)
{
[MBProgressHUD showMessage:@"客户端创建连接"];
}
else
{
self.connected = NO;
[MBProgressHUD showMessage:@"客户端未创建连接"];
}
接下来是用到的代理方法
//连接主机对应端口
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port{
// 连接上服务器
[MBProgressHUD showSuccess:@"连接成功"];
//这里是一个定时器,多久像后台发送一次心跳
[self addTimer];
// 连接后,可读取服务器端的数据
[self.clientSocket readDataWithTimeout:- 1 tag:0];
self.connected = YES;
}
// 客户端socket断开连接
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{
//断开连接后的处理
}
// 收到消息(收到消息这个地方用到了拆包,因为我们的消息比较多,有的好像是不需要的,这个也问后台人员)
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
[self.receiveData appendData:data];
//读取data的头部占用字节 和 从头部读取内容长度
//验证结果:数据比较小时头部占用字节为1,数据比较大时头部占用字节为2
int32_t headL = 0;
int32_t contentL = [self getContentLength:self.receiveData withHeadLength:&headL];
if (contentL < 1){
[sock readDataWithTimeout:-1 tag:0];
return;
}
//拆包情况下:继续接收下一条消息,直至接收完这条消息所有的拆包,再解析
if (headL + contentL > self.receiveData.length){
[sock readDataWithTimeout:-1 tag:0];
return;
}
//当receiveData长度不小于第一条消息内容长度时,开始解析receiveData
[self parseContentDataWithHeadLength:headL withContentLength:contentL];
[sock readDataWithTimeout:-1 tag:0];
}
下面这些是上面的一些调用的方法
拆包
/** 解析二进制数据:NSData --> 自定义模型对象 */
- (void)parseContentDataWithHeadLength:(int32_t)headL withContentLength:(int32_t)contentL{
NSRange range = NSMakeRange(0, headL + contentL); //本次解析data的范围
NSData *data = [self.receiveData subdataWithRange:range]; //本次解析的data
//GPBCodedInputStream是protobuf的类
GPBCodedInputStream *inputStream = [GPBCodedInputStream streamWithData:data];
// NSLog(@"_________%d",[inputStream readTag]);
NSError *error;
Message *obj = [Message parseDelimitedFromCodedInputStream:inputStream extensionRegistry:nil error:&error];
if (!error){
if (obj) [self saveReceiveInfo:obj]; //保存解析正确的模型对象
[self.receiveData replaceBytesInRange:range withBytes:NULL length:0]; //移除已经解析过的data
}
if (self.receiveData.length < 1) return;
//对于粘包情况下被合并的多条消息,循环递归直至解析完所有消息
headL = 0;
contentL = [self getContentLength:self.receiveData withHeadLength:&headL];
if (headL + contentL > self.receiveData.length) return; //实际包不足解析,继续接收下一个包
[self parseContentDataWithHeadLength:headL withContentLength:contentL]; //继续解析下一条
}
/** 获取data数据的内容长度和头部长度: index --> 头部占用长度 (头部占用长度1-4个字节) */
- (int32_t)getContentLength:(NSData *)data withHeadLength:(int32_t *)index{
int8_t tmp = [self readRawByte:data headIndex:index];
if (tmp >= 0) return tmp;
int32_t result = tmp & 0x7f;
if ((tmp = [self readRawByte:data headIndex:index]) >= 0) {
result |= tmp << 7;
} else {
result |= (tmp & 0x7f) << 7;
if ((tmp = [self readRawByte:data headIndex:index]) >= 0) {
result |= tmp << 14;
} else {
result |= (tmp & 0x7f) << 14;
if ((tmp = [self readRawByte:data headIndex:index]) >= 0) {
result |= tmp << 21;
} else {
result |= (tmp & 0x7f) << 21;
result |= (tmp = [self readRawByte:data headIndex:index]) << 28;
if (tmp < 0) {
for (int i = 0; i < 5; i++) {
if ([self readRawByte:data headIndex:index] >= 0) {
return result;
}
}
result = -1;
}
}
}
}
return result;
}
/** 处理解析出来的信息 */
- (void)saveReceiveInfo:(Message *)obj{
//...
}
/** 读取字节 */
- (int8_t)readRawByte:(NSData *)data headIndex:(int32_t *)index{
if (*index >= data.length) return -1;
*index = *index + 1;
return ((int8_t *)data.bytes)[*index - 1];
}
定时器
- (void)addTimer
{
// NSLog(@"定时器开启时间%@",[self getCurrentSecond]);
// 长连接定时器
self.connectTimer = [NSTimer scheduledTimerWithTimeInterval:30.0 target:self selector:@selector(longConnectToSocket) userInfo:nil repeats:YES];
// 把定时器添加到当前运行循环,并且调为通用模式
[[NSRunLoop currentRunLoop] addTimer:self.connectTimer forMode:NSRunLoopCommonModes];
}
// 心跳连接
- (void)longConnectToSocket{
NSLog(@"心跳发送%s",__func__);
//这个Message就是我自己创建的Protobuf,大家用自己的就好
Message *msg = [[Message alloc] init];
SocketModel *model = [SocketModel new];
model.msg = @"cbf0524e-5da9-4385-89ed-3cfc25df5dd9";
msg.data_p = [model mj_JSONString];
msg.headType = 3;
//注意这个data数据的生成方式
NSData *data = [msg delimitedData];
[self.clientSocket writeData:data withTimeout:- 1 tag:0];
}
差不多全贴出来了,以上这些拿过来应该就可以直接用的
网友评论