简介:
tcp+protobuf概念介绍
关于tcp和Google Protocol Buffer其它博客一搜一大堆,在这里我就不再搬抄了。
准备:
Protobuf安装:
- 检查是否安装Homebrew:
如果没有安装:brew -v
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
- 安装Protobuf Compiler
brew install automake brew install libtool brew install protobuf
- 安装Objective-C扩展
git clone https://github.com/alexeyxo/protobuf-objc.git cd protobuf-objc ./scripts/build.sh
- 使用CocoaPods集成到项目(这个不做过多介绍)
pod "Protobuf" # 注意不是 pod "ProtocolBuffers"
Protobuf使用:
创建 .proto 文件根据前后台项目需求制定协议例如:
syntax = "proto3";
// 1-15之间的标识码应给常用的字段使用
// 约定如下
// 1 -> command
// 2 -> uid
// 3 -> openid
// 4 -> kuocode
// 5 -> status
// 5 -> status
// 6 -> create_at
// 7 -> update_at
enum Commands {
Request = 0;
Reply = 1;
Update = 2;
}
message GetUser {
Commands command = 1;
string openid = 3;
}
message UserProfile {
Commands command = 1;
string nickname = 18;
int32 uid = 2;
string openid = 3;
int32 status = 5;
float refresh_at = 19;
repeated UserProfile friends = 20;
}
具体protobuf协议格式可以参照官网 (ps:protobuf 3.0语法格式和3.0以前有很大改动,参照网上搜来的 .proto 文件时需要注意不同点,然后修改)
将生成的 .pbobjc 文件拖入项目中(但是这样不好,不利于管理。个人认为最好的方式是管理一个git并集成到项目中,里面存放项目所用到的.proto文件,前后台共同维护,在xcode项目编译前添加一个脚本使用脚本语言自动生成 .pbobjc 文件在项目中。这个具体看后面如果有时间,然后大家都有需求的话再写一篇专门介绍这部分,这里就不细说了)
使用protobuf好处:
Protobuf和XML相比同是数据交换协议,不过Protobuf更小、更快、也更简单。可以通过定义自己的数据结构,然后使用Protobuf的代码生成器生成代码,用生成的代码来读写这个数据结构。Protobuf具有如下几个优点:
- “向后”兼容性好。不必担心因为消息结构的改变而造成的大规模的代码重构或者迁移的问题,因为添加新的消息中的字段并不会引起已经发布的程序的任何改变。
- 语义更清晰。Protobuf使用
.proto
文件描述数据交换的格式,然后Protobuf编译器会将.proto
文件编译生成对应的数据访问类以对Protobuf
数据进行序列化、反序列化操作),无需解释器之类的东西。
- 简单易学。使用Protobuf无需学习复杂的文档对象模型,它拥有良好的文档和示例,对于喜欢简单事物的人们而言,Protobuf比其他的技术更加有吸引力。
CocoaAsyncSocket添加到项目中
下载
CocoaAsyncSocket后将GCDAsyncSocket文件及GCDAsyncUdpSocket(如果用到udp)拖入项目中
然后自己根据需求写一个管理类,例如:KLSocketManager
.h文件如下:
#import <Foundation/Foundation.h>
/** socket通讯数据接收 */
@protocol KLSocketManagerDelegate <NSObject>
/** 接收数据成功 */
- (void)socketRequstBackResultData:(NSData *)resultData;
/** 连接失败 */
- (void)socketRequstFail;
/** 获取data */
- (NSData *)getData;
@end
@interface KLSocketManager : NSObject
+ (KLSocketManager *)shareInstance;
/**
socket连接
*/
- (void)socketConnectHost;
/**
socket断开 (被视为用户主动断开socket,不会自动重连)
*/
- (void)cutOffSocket;
/**
向服务器发送数据 并指定代理
*/
-(void)socketWriteData:(NSData *)data andDelegate:(id <KLSocketManagerDelegate>)delegate;
@end
.m文件如下:
#import "KLSocketManager.h"
#import "GCDAsyncSocket.h"
#import "GCDAsyncUdpSocket.h"
#import "Test.pbobjc.h"
#define KLSocketHost @"你的socket地址"
#define KLSocketPort 8080
#define KLSocketTimerTime 60
#define KLSocketTimerTag 200
/** socket断开状态 */
typedef enum : NSUInteger {
/** 超时 */
KLSocketOffLineOutTime,
KLSocketOffLineByUser,
KLSocketOffLineHome,
} KLSocketetOffLineType;
@interface KLSocketManager () <GCDAsyncSocketDelegate>
/** socket */
@property (nonatomic, strong) GCDAsyncSocket *clientSocket;
/** 服务器地址 */
@property (nonatomic, strong) NSString *socketHost;
/** 端口号 */
@property (nonatomic, assign) uint16_t socketPort;
/** 心跳计时器 */
@property (nonatomic, strong) NSTimer *socketTimer;
/** socket状态 */
@property (nonatomic, assign) KLSocketetOffLineType offlineType;
/** socket回调标识 */
@property (nonatomic, assign) NSInteger socketTag;
/** socket重连次数限定 */
@property (nonatomic, assign) NSInteger reconnectCount;
/** socket回调存储 */
@property (nonatomic, strong) NSMutableDictionary *socketDic;
/**
心跳连接
*/
- (void)socketTimerConnectSocket;
- (void)startTimer;
@end
@implementation KLSocketManager
+ (KLSocketManager *)shareInstance{
static KLSocketManager *manager = nil;
static dispatch_once_t onceSocketToken;
dispatch_once(&onceSocketToken, ^{
manager = [[KLSocketManager alloc] init];
});
return manager;
}
- (instancetype)init
{
self = [super init];
if (self) {
self.socketHost = KLSocketHost;
self.socketPort = KLSocketPort;
self.socketTag = 1;
self.offlineType = KLSocketOffLineOutTime;
self.reconnectCount = 1;
// [self socketConnectHost];
}
return self;
}
// 连接
- (void)socketConnectHost{
if (self.clientSocket.isConnected) {
return;
}
[self cutOffSocket];
self.offlineType = KLSocketOffLineOutTime;
// 连接ip 端口
NSError *error;
[self.clientSocket connectToHost:self.socketHost onPort:self.socketPort viaInterface:nil withTimeout:-1 error:&error];
NSLog(@"%@", error);
}
// 断开
- (void)cutOffSocket{
[self.clientSocket disconnectAfterReadingAndWriting];
if (self.socketTimer) {
[self.socketTimer invalidate];
self.socketTimer = nil;
}
}
// 心跳
- (void)socketTimerConnectSocket {
GetUser *user = [[GetUser alloc] init];
user.openid = @"1";
[self.clientSocket writeData:[user data] withTimeout:-1 tag:KLSocketTimerTag];
}
// 开始心跳
- (void)startTimer {
if (self.socketTimer) {
[self.socketTimer invalidate];
self.socketTimer = nil;
}
self.socketTimer = [NSTimer scheduledTimerWithTimeInterval:KLSocketTimerTime target:self selector:@selector(socketTimerConnectSocket) userInfo:nil repeats:YES];
[self.socketTimer fire];//执行
[[NSRunLoop currentRunLoop] addTimer:self.socketTimer forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] run];
}
/**
向服务器发送数据
*/
- (void)socketWriteData:(NSData *)data andDelegate:(id <KLSocketManagerDelegate>)delegate {
if (!self.clientSocket.isConnected) {
[delegate socketRequstFail];
return;
}
self.socketTag = self.socketTag > 1000000 ? 1 : self.socketTag + 1;
NSLog(@"%zd", data.length);
[self.clientSocket writeData:data withTimeout:-1 tag:self.socketTag];
}
#pragma mark - GCDAsyncSocketDelegate
/** 已经连接 */
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
NSLog(@"已经连接 %@", host);
self.reconnectCount = 1;
if (self.socketDic.count > 0) {
for (NSString *key in self.socketDic.allKeys) {
id <KLSocketManagerDelegate>delegate = self.socketDic[key];
if ([delegate respondsToSelector:@selector(getData)]) {
NSData *data = [delegate getData];
[self.clientSocket writeData:data withTimeout:-1 tag:key.integerValue];
}
}
}
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(startTimer) object:nil];
[self performSelector:@selector(startTimer) withObject:nil afterDelay:KLSocketTimerTime];
}
/** 连接断开 */
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(nullable NSError *)err {
// 先判断网络,网络不好情况下不重连
// 不是用户主动断开就重连
if (self.offlineType != KLSocketOffLineByUser) {
if (self.reconnectCount > 64) {
self.offlineType = KLSocketOffLineOutTime;
[self.socketDic removeAllObjects];
} else {
self.reconnectCount *= 2;
[self socketConnectHost];
}
}
}
/** 读取服务器数据 */
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
NSError *error;
UserProfile *pro = [[UserProfile alloc] initWithData:data error:&error];
// 继续监听
[self.clientSocket readDataWithTimeout:-1 tag:KLSocketTimerTag];
}
/** 写入完成的回调 */
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag {
[self.clientSocket readDataWithTimeout:-1 tag:tag];
}
#pragma mark - 获取当前时间
- (NSData *)currentDatedata {
NSDate *currentDate = [NSDate date];//获取当前时间,日期
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
//设定时间格式,这里可以设置成自己需要的格式
[dateFormatter setDateFormat:@"YYYY/MM/dd hh:mm:ss"];
NSString *dateString = [dateFormatter stringFromDate:currentDate];
return [dateString dataUsingEncoding:NSUTF8StringEncoding];
}
- (GCDAsyncSocket *)clientSocket {
if (!_clientSocket) {
_clientSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
}
return _clientSocket;
}
- (NSMutableDictionary *)socketDic {
if (!_socketDic) {
_socketDic = [[NSMutableDictionary alloc] init];
}
return _socketDic;
}
ps:这个管理类只是暂时写的,大家还是要根据自己项目需求写自己的管理类。
这里接收到的数据流转换为protobuf的model,但是还是要新建项目中实际用到的model,最好不要用protobuf的model用在项目中,只做网络传输协议就好。
UserProfile *pro = [[UserProfile alloc] initWithData:data error:&error];
关于协议制定还需和后台商议,如协议头怎么传,用不用界定符等等。
老规矩,git
小例子
完。
转载请标明出处,谢谢!