刚做完一个项目,用到了socket通讯,在这里分享一下,新手初写,欢迎提供意见,有不对之处,请及时向作者反应,以免对大家产生误导
用到的是GCDAsyncSocket框架,包括 心跳处理 拆包合包 自动手动重连
AsyncSocket和GCDAsyncSocket一个是同步线程一个是异步线程
GCDAsyncSocket有自动手动的重连对象,但是我处理时候赋值是有问题(AsyncSocket就没有问题,可能是我处理有问题吧),所以就自己用了一个bool类型
心跳包的作用:socket有时候虽然没有断开,但是实际上是断开状态(Wi-Fi在中途没有连上互联网时)socket是不会进入“断开”的代理方法的,这个时候就需要心跳来判断
在连接上socket后,会不断的向服务器发送心跳包(心跳包的内容一般比较简单),服务器接到心跳包后,会有相应,接到响应,说明socket是可以正常通讯的,如果在一点的时间 内没有接到心跳响应,那么就说明socket断掉了,需要重新连接服务器
1.首先自然是创建一个单利类
+ (instancetype)stateConnectAction{
SocketConnect *socketConnect = [SocketConnect sharedManager];
return socketConnect;
}
+ (SocketConnect *)sharedManager
{
static SocketConnect *sharedSocketConnect = nil;
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
sharedSocketConnect = [[SocketConnect alloc] init];
});
return sharedSocketConnect;
}
2.初始化socket,由于我这边是要快速的连接服务器,所以,会查找上次登录过的服务器
- (instancetype)init
{
if (self = [super init]) {
self.clientSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
_server = CONF_GET(@"server");
_port = CONF_GET(@"port");
if (_server == nil || _port == nil) {
_server = [[self.serverArr firstObject] objectForKey:@"server"];
_port = [[self.serverArr firstObject] objectForKey:@"port"];
}
}
return self;
}
3.当然是连接服务器
//socket连接
-(void)creatSocket{
if (self.clientSocket == nil || [self.clientSocket isDisconnected]) {
//初始化 GCDAsyncSocket
self.clientSocket = [[GCDAsyncSocket alloc]initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
//如果能连接成功
NSError *error = nil;
if (![self.clientSocket connectToHost:_server onPort:[_port intValue] withTimeout:10 error:&error]) {
}
}
self.needConnect = YES;
}
4.连接服务器会几个有代理方法
4.1 socket断开连接。
当err是空的时候,说明服务器是可以连接的,但是服务器是断开状态,你可以选择重新连接此服务器
当err不是空的时候,会有错误信息的时候,说明服务器是连接有问题的,但是服务器连接不上,你这个时候就要重新选择其它的服务器了
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(nullable NSError *)err{
if (err == nil) {
NSLog(@"socket准备连接");
if (_delegate && [_delegate respondsToSelector:@selector(getConnect:)]) {
[_delegate getConnect:@"连接中..."];
}
[self creatSocket];
}else{
NSLog(@"socket准备切换服务器");
if (_delegate && [_delegate respondsToSelector:@selector(getConnect:)]) {
[_delegate getConnect:@"连接失败"];
}
//换掉服务器
[self changeServer];
}
}
4.2 socket连接成功
socket连接成功会调用此方法,这个时候你就要接受消息了
readDataWithTimeout:tag: Timeout是你接受消息的时长,-1表示一直接受消息
tag 个人理解为你要接受消息的标记,比如发一个消息tag为1,你就可以接受tag为1的信息
连接成功后,你可以发送心跳包了,完整代码,稍后会贴出的
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port{
NSLog(@"%@,%@",_server,_port);
//读取Socket通讯内容
[self.clientSocket readDataWithTimeout:-1 tag:0];
NSLog(@"连接成功");
CONF_SET(@"server", _server);
CONF_SET(@"port", _port);
if (_delegate && [_delegate respondsToSelector:@selector(getConnect:)]){
[_delegate getConnect:@"连接成功"];
}
}
5.socket接受到消息
一般的socket接收到之后会有如下处理
1.判断收到的信息是否完整,socket一般会发一串信息,前面的四个字节指定要发消息的长度,后面的就将收到的信息按长度处理
2.一条信息可能不是一次就能收到的,要分几次收
3.可能会有粘包的情况,反正我没见过
// 收到消息
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
//将得到的数据进行处理
[self hangleBufferData:data];
[self.clientSocket readDataWithTimeout:-1 tag:0];
}
- (void)hangleBufferData:(NSData *)newData{
[self.bufferData appendData:newData];//合包
//取前面四个字节的长度
int i = 1;
[_bufferData getBytes:&i length:4];
if (i == ([[_bufferData copy] length] - 4)) {//刚好
//处理接受的数据
[self checkIsHeart:i];
//将_bufferData初始化
_bufferData = [NSMutableData data];
}else if(i < [_bufferData length] - 4){//需要拆包
//处理接受的数据
[self checkIsHeart:i];
//将剩余的数据继续进行判断
NSData *buffer = [_bufferData subdataWithRange:NSMakeRange(i + 4, _bufferData.length- i - 4)];
_bufferData = [NSMutableData data];
[_bufferData appendData:buffer];
//继续进行判断处理
[self hangleBufferData:nil];
}
}
//处理接受的数据
- (void)checkIsHeart:(int)userDataLenght{
//截取有用的数据
NSData *contentData = [_bufferData subdataWithRange:NSMakeRange(4, userDataLenght)];
//对有用的数据进行接收(有些数据进行了一些加密措施)
NSString *strData = [NSString stringChangInfo:contentData];
//将得到的数据传递出去
if (_delegate && [_delegate respondsToSelector:@selector(getBackData:)]) {
[_delegate getBackData:strData];
}
}
完整代码如下
.h文件
#import <Foundation/Foundation.h>
#import "GCDAsyncSocket.h"
@protocol SocketConnectDelegate <NSObject>
- (void)getBackData:(NSString *)strData;
- (void)getConnect:(NSString *)connectState;
@end
@interface SocketConnect:NSObject
// 客户端socket
@property (strong, nonatomic) GCDAsyncSocket *clientSocket;
@property (nonatomic, weak) id<SocketConnectDelegate> delegate;
+ (instancetype)stateConnectAction;
- (void)disMissConnect;//手动断开连接,不需要重连
- (void)disMissNeedConnect;//手动断开连接,需要重连
- (void)sendMessage:(NSString *)message;
@end
.m文件
#import "SocketConnect.h"
#import "Golbal.h"
#import "HeartModel.h"
#import "NSString+ChangeFormat.h"
#import <UIKit/UIKit.h>
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>
#import <unistd.h>
@interface SocketConnect ()<GCDAsyncSocketDelegate>
@property(nonatomic,strong)SocketConnect * socketConnect;
@property(nonatomic,strong)NSMutableData * bufferData;//接收到的数据
@property (nonatomic, assign) BOOL needConnect; //需不需要重新连接
@property (nonatomic, strong) NSArray *serverArr; //服务器数组
@property (nonatomic, copy) NSString *server; //
@property (nonatomic, copy) NSString *port; //
@property (nonatomic, strong) NSTimer *beforTimer; // 计时器
@property (nonatomic, strong) NSTimer *heartTimer; // 心跳计时器
@end
@implementation SocketConnect
-(NSArray *)serverArr{
if (_serverArr == nil) {
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"serverInfo" ofType:@"plist"];
_serverArr = [[NSArray alloc] initWithContentsOfFile:filePath];
}
return _serverArr;
}
+ (SocketConnect *)sharedManager
{
static SocketConnect *sharedSocketConnect = nil;
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
sharedSocketConnect = [[SocketConnect alloc] init];
});
return sharedSocketConnect;
}
- (instancetype)init
{
if (self = [super init]) {
self.clientSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
_server = CONF_GET(@"server");
_port = CONF_GET(@"port");
if (_server == nil || _port == nil) {
_server = [[self.serverArr firstObject] objectForKey:@"server"];
_port = [[self.serverArr firstObject] objectForKey:@"port"];
}
}
return self;
}
//socket连接
-(void)creatSocket{
if (self.clientSocket == nil || [self.clientSocket isDisconnected]) {
//初始化 GCDAsyncSocket
self.clientSocket = [[GCDAsyncSocket alloc]initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
//如果能连接成功
NSError *error = nil;
if (![self.clientSocket connectToHost:_server onPort:[_port intValue] withTimeout:10 error:&error]) {
}
}
self.needConnect = YES;
}
//切换服务器
- (void)changeServer{
if (_serverArr == nil) {
[self serverArr];
}
for (int i = 0; i < _serverArr.count; i ++) {
NSDictionary *serverDic = _serverArr[i];
if ([_server isEqualToString:[serverDic objectForKey:@"server"]]) {
if ((i+1) >=_serverArr.count) {
_server = [_serverArr[0] objectForKey:@"server"];
_port = [_serverArr[0] objectForKey:@"port"];
}else{
_server = [_serverArr[i+1] objectForKey:@"server"];
_port = [_serverArr[i+1] objectForKey:@"port"];
}
[self creatSocket];
break;
}
}
}
//开始连接
+ (instancetype)stateConnectAction{
SocketConnect *socketConnect = [SocketConnect sharedManager];
[socketConnect creatSocket];
//将_bufferData初始化
socketConnect.bufferData = [NSMutableData data];
return socketConnect;
}
//手动断开连接,不需要重连
- (void)disMissConnect{
self.needConnect = NO;
[self.clientSocket disconnect];
}
//手动断开连接,需要重连
- (void)disMissNeedConnect{
self.needConnect = YES;
[self.clientSocket disconnect];
}
//发送数据
- (void)sendMessage:(NSString *)message{
NSMutableData *changeData = [NSString dataChangeString:message];
[self.clientSocket writeData:[changeData copy] withTimeout:-1 tag:0];
}
// 心跳连接
-(void)heartbeat{
// 根据服务器要求发送固定格式的数据,但是一般是简单的指令
NSLog(@"发送心跳包");
NSString *message = ......;
[self sendMessage:message];
[self.heartTimer invalidate];
self.heartTimer = nil;
if (self.heartTimer == nil) {
self.heartTimer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(heartCloce) userInfo:nil repeats:NO];
}
}
#pragma mark - GCDAsyncSocketDelegate
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(nullable NSError *)err{
[self.beforTimer invalidate];
self.beforTimer = nil;
[self.heartTimer invalidate];
self.heartTimer = nil;
NSLog(@"断开连接 err:%@",err);
if (!self.needConnect) {
return;
}
if (err == nil) {
NSLog(@"socket准备连接");
if (_delegate && [_delegate respondsToSelector:@selector(getConnect:)]) {
[_delegate getConnect:@"连接中..."];
}
[self creatSocket];
}else{
NSLog(@"socket准备切换服务器");
if (_delegate && [_delegate respondsToSelector:@selector(getConnect:)]) {
[_delegate getConnect:@"连接失败"];
}
//换掉服务器
[self changeServer];
}
}
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port{
NSLog(@"%@,%@",_server,_port);
//读取Socket通讯内容
[self.clientSocket readDataWithTimeout:-1 tag:0];
NSLog(@"连接成功");
CONF_SET(@"server", _server);
CONF_SET(@"port", _port);
if (_delegate && [_delegate respondsToSelector:@selector(getConnect:)]){
[_delegate getConnect:@"连接成功"];
}
// 每隔30s像服务器发送心跳包
if (self.beforTimer == nil) {
self.beforTimer = [NSTimer scheduledTimerWithTimeInterval:30 target:self selector:@selector(heartbeat) userInfo:nil repeats:YES];
}
[self.beforTimer fire];
}
- (void)heartCloce{
NSLog(@"socket心跳断开");
[self.beforTimer invalidate];
self.beforTimer = nil;
[self.heartTimer invalidate];
self.heartTimer = nil;
if (_delegate && [_delegate respondsToSelector:@selector(getConnect:)]) {
[_delegate getConnect:@"连接中..."];
}
//断开连接
[self disMissNeedConnect];
}
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag{
}
// 收到消息
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
//将得到的数据进行处理
[self hangleBufferData:data];
[self.clientSocket readDataWithTimeout:-1 tag:0];
}
- (void)hangleBufferData:(NSData *)newData{
[self.bufferData appendData:newData];//合包
//取前面四个字节的长度
int i = 1;
[_bufferData getBytes:&i length:4];
if (i == ([[_bufferData copy] length] - 4)) {//刚好
//处理接受的数据
[self checkIsHeart:i];
//将_bufferData初始化
_bufferData = [NSMutableData data];
}else if(i < [_bufferData length] - 4){//需要拆包
//处理接受的数据
[self checkIsHeart:i];
//将剩余的数据继续进行判断
NSData *buffer = [_bufferData subdataWithRange:NSMakeRange(i + 4, _bufferData.length- i - 4)];
_bufferData = [NSMutableData data];
[_bufferData appendData:buffer];
//继续进行判断处理
[self hangleBufferData:nil];
}
}
//处理接受的数据
- (void)checkIsHeart:(int)userDataLenght{
//截取有用的数据
NSData *contentData = [_bufferData subdataWithRange:NSMakeRange(4, userDataLenght)];
//对有用的数据进行接收(有些数据进行了一些加密措施)
NSString *strData = [NSString stringChangInfo:contentData];
//查看是否是心跳包(看个人的数据如何解析的)
NSArray * staArr = [strData componentsSeparatedByString:@"|"];
if ([[staArr firstObject] isEqualToString:@"C_Heart"]) {
NSLog(@"接受心跳包");
[self.heartTimer invalidate];
self.heartTimer = nil;
}
//将得到的数据传递出去
if (_delegate && [_delegate respondsToSelector:@selector(getBackData:)]) {
[_delegate getBackData:strData];
}
}
@end