1.集成第三方开源库 CocoaAsyncSocket
2.对第三库进行二次封装
RZSocketTools.h
//
// RZSocketTools.h
// UDPTest
//
// Created by on 17/3/8.
// Copyright © 2017年 刘. All rights reserved.
//
#import <Foundation/Foundation.h>
#define AUTO_RECONNECT_NAME @"AUTO_RECONNECT_NAME"
typedef void(^ConnectStateBlock)(BOOL connectState);
typedef void(^RBServersBlock)(NSMutableArray *servers);
typedef void(^RBServerMsgBlock)(NSDictionary * msgDic);
typedef void(^RBGetEMIdMsgBlock)(NSDictionary *msgDic);
@interface RZSocketTools : NSObject
@property (nonatomic,copy) ConnectStateBlock connectStateBlock; //链接状态更新
@property (nonatomic,copy) RBServersBlock serverBlock; //获取搜索到的设备
@property (nonatomic,copy) RBGetEMIdMsgBlock getIdBlock; //获取到环信ID block
@property (nonatomic,copy) NSMutableArray *serverArrs; //UDP监听到的服务器数组
@property (nonatomic,assign) BOOL connectAuto; //是否自动重连
@property (nonatomic,assign) BOOL connectState; //机器人链接状态
@property (nonatomic,assign) BOOL udpReciveState; //判断是否有接受到UDP,没有接受到 弹出AirKiss 网络链接框
+ (instancetype)shareSocketTool;
- (void)scanNearServers; //搜索周围机器人
- (void)close;
- (void)connectRobot:(NSString*)robotName; //链接机器人
- (BOOL)sendMsgToRobot:(NSDictionary*)msgDic CallBack:(RBServerMsgBlock)msgBlock; //发送信息给机器人
- (void)disConnectRobot; //断开机器人链接
@end
RZSocketTools.m
//
// RZSocketTools.m
// UDPTest
//
// Created by on 17/3/8.
// Copyright © 2017年 . All rights reserved.
//
#import "RZSocketTools.h"
#import "GCDAsyncUdpSocket.h"
#import "GCDAsyncSocket.h"
#import "common.h"
#import "Socket_Defines.h"
#define UDP_PORT 30695
#define TCP_PORT 30690
#define IOS_CLIENT @"X-ZERO-IOS"
#define SERVER_NAME
#define TCP_FIRST_TAG @"0"
#define AUTO_CONNECT_MAX 6 //自动重连数据
#define AUTO_TIME_SPACE 5 //自动重连时间间隔
#define SEND_HEART_TIME_SPACE 13 //发送心跳包数据时间间隔 13
#define ACCEPT_HEART_TIME_SPACE 40 //未收到心跳包数据时间间隔 40
@interface RZSocketTools()<GCDAsyncUdpSocketDelegate,GCDAsyncSocketDelegate>
{
GCDAsyncUdpSocket *asyncUdpSocket; //UDPSocket
GCDAsyncSocket *asyncSocket; //TCPSocket
RBServerMsgBlock msgReveiceBlock; //消息回调
NSTimer *sendTimer; //定时想服务器发送消息,保持服务器socket不断
NSString *connectName; //连接的设备名称
NSDictionary *currentPacketHead;
// BOOL sendHeartFlag; //发送心跳包数据标志量 SEND_HEART_TIME_SPACE后不发送
NSTimer *noSendTimer; //断开连接的轮训 60s未收到自动断开
BOOL startAutoConnect; //开机自动重连
}
@end
static RZSocketTools *socketTool = nil;
@implementation RZSocketTools
+ (instancetype)shareSocketTool
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
socketTool = [[super allocWithZone:NULL] init];
[socketTool initSocket];
});
//lxaddlx
if (!socketTool) {
socketTool = [[super allocWithZone:NULL] init];
[socketTool initSocket];
}
return socketTool;
}
- (void)close{
socketTool = nil;
}
+ (id)allocWithZone:(struct _NSZone *)zone
{
return [RZSocketTools shareSocketTool];
}
- (id)copyWithZone:(struct _NSZone *)zone
{
return [RZSocketTools shareSocketTool];
}
#pragma mark - InitSocket
- (void)initSocket
{
_serverArrs = [NSMutableArray arrayWithCapacity:15];
asyncUdpSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
NSError *err = nil;
[asyncUdpSocket enableBroadcast:YES error:&err];
[asyncUdpSocket bindToPort:UDP_PORT error:&err];
asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)];
_connectState = NO;
//是否收到UDP
_udpReciveState = NO;
//自动连接
//这句是控制启动APP后首次扫描时,自动连接最后一次连接的设备的
startAutoConnect = YES;
sendTimer = [NSTimer timerWithTimeInterval:SEND_HEART_TIME_SPACE target:self selector:@selector(sendHeartData) userInfo:nil repeats:YES]; //定义心跳轮询
connectName = [NSString string];
//lx开心跳包,自动重连存到沙盒的机器人
[socketTool autoConnectTcp:[UserDataHelper getUserData:AUTO_RECONNECT_NAME]]; //设置自动重连
}
#pragma mark - ConnectTcp
//扫描周围的设备
- (void)scanNearServers
{
[_serverArrs removeAllObjects];
[asyncUdpSocket receiveOnce:nil];
}
//根据机器名字来链接TCP
- (void)connectRobot:(NSString*)robotName
{
NSMutableArray *arr = _serverArrs;
for (NSDictionary *dic in _serverArrs) {
if ([dic[@"name"] isEqualToString:robotName]){
NSError *err;
[asyncSocket disconnect];
//收到消息后反馈给robot
NSData *clientData = [IOS_CLIENT dataUsingEncoding:NSUTF8StringEncoding];
[asyncUdpSocket sendData:clientData toHost:dic[@"ip"] port:UDP_PORT withTimeout:2 tag:1];
[NSThread sleepForTimeInterval:0.8];
BOOL flag = [asyncSocket connectToHost:dic[@"ip"] onPort:TCP_PORT error:&err];
connectName = robotName;
NSLog(@"链接TCP%d,%@",flag,err);
break;
}
}
}
//给rb发送指令并且回调
- (BOOL)sendMsgToRobot:(NSDictionary*)msgDic CallBack:(RBServerMsgBlock)msgBlock
{
// NSLog(@"给机器人发消息(未连接)%@",msgDic);
if (!_connectState) {
return NO;
}
NSLog(@"lxadd给机器人发的信息%@",msgDic);
msgReveiceBlock = msgBlock;
[socketTool sendMsgToRobot:msgDic];
return YES;
}
//断开TCP直接的链接
- (void)disConnectRobot
{
NSLog(@"发断开连接");
[asyncSocket disconnect];
socketTool.connectAuto = NO;
}
//自动重连
- (void)autoConnectTcp:(NSString*)robotName
{
if (_connectAuto && ([robotName isEqualToString:[UserDataHelper getUserData:AUTO_RECONNECT_NAME]])) {//存到沙盒的机器人名字
for (NSInteger i = 0; i < AUTO_CONNECT_MAX; i++) {
NSTimeInterval autoTime = i * AUTO_TIME_SPACE + i*i; //重连4次
NSTimer *autoTimer = [NSTimer timerWithTimeInterval:autoTime target:self selector:@selector(sendAutoConnect:) userInfo:nil repeats:NO]; //定义心跳重连
[[NSRunLoop mainRunLoop] addTimer:autoTimer forMode:NSRunLoopCommonModes];
}
}
}
- (void)sendAutoConnect:(NSTimer*)timer
{
// NSLog(@"自动重连的心跳包");
if (_connectState) {
[timer invalidate];
return;
}
[socketTool connectRobot:connectName];
}
#pragma mark - UDPDelegate
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(nullable id)filterContext
{
// NSLog(@"lxaddl--udpDidReceiveData");
//这里需要把robot-name,IP地址 记录下来;
NSString *robotName = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
[asyncUdpSocket receiveOnce:nil];
if ([robotName hasPrefix:SERVER_NAME] && [GCDAsyncUdpSocket isIPv4Address:address]) {
_udpReciveState = YES;
if (![_serverArrs containsObject:@{@"name":robotName,@"ip":[GCDAsyncUdpSocket hostFromAddress:address]}]) {
NSMutableArray *testArr = _serverArrs;
NSDictionary *dict = @{@"name":robotName,@"ip":[GCDAsyncUdpSocket hostFromAddress:address]};
[_serverArrs addObject:@{@"name":robotName,@"ip":[GCDAsyncUdpSocket hostFromAddress:address]}];
// _serverArrs = [_serverArrs mutableCopy];
if (socketTool.serverBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
socketTool.serverBlock(_serverArrs);
});
}
if ([robotName isEqualToString:[UserDataHelper getUserData:AUTO_RECONNECT_NAME]] && startAutoConnect) { //开机自动连接
// NSLog(@"开机自动连接");
// NSString *str = [UserDataHelper getUserData:AUTO_RECONNECT_NAME];
//
// [self connectRobot:[UserDataHelper getUserData:AUTO_RECONNECT_NAME]];
}
}
}
}
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didSendDataWithTag:(long)tag
{
NSLog(@"消息已经发送出去 ,%ld",tag);
}
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError * _Nullable)error
{
NSLog(@"消息并没有发送出去,请重试 ,%ld,err:%@",tag,error);
}
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError *)error
{
assert(@"UDP 无法连接");
}
- (void)udpSocketDidClose:(GCDAsyncUdpSocket *)sock withError:(NSError * _Nullable)error
{
assert(@"UDP Socket 关闭");
}
#pragma mark - TCPDelegate
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
{
NSLog(@"didAcceptNewSocket");
}
//机器人连接成功
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
{
if (asyncSocket == nil) {
asyncSocket = sock;
}
_connectAuto = NO;
_connectState = YES;
startAutoConnect = NO;
if (socketTool.connectStateBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
socketTool.connectStateBlock(_connectState);
});
}
//把连到的机器人名字存进沙盒
[UserDataHelper setUserData:connectName forKey:AUTO_RECONNECT_NAME];
if (sendTimer.isValid) {
[sendTimer setFireDate:[NSDate distantPast]];
}
//连接后开始发心跳包
[[NSRunLoop mainRunLoop] addTimer:sendTimer forMode:NSRunLoopCommonModes]; //加入到RunLoop中
[asyncSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:1];
}
//lxadd读到机器人发来的数据
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
NSDictionary *dict = [NSJSONSerialization
JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:nil];
// NSLog(@"读取到的数据包信息--==%@",dict);
// NSLog(@"currentPacktHead==%@",currentPacketHead);
// NSLog(@"当前线程=--%@",[NSThread currentThread]);
//先读取到当前数据包头部信息
if (!currentPacketHead) {//当第一次来的是心跳包的时候会出问题
currentPacketHead = [NSJSONSerialization
JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:nil];
//NSLog(@"currentPacketHead---%@",currentPacketHead);
NSUInteger packetLength = [currentPacketHead[@"size"] integerValue];
if (packetLength == 0) {
currentPacketHead = nil;
return;
}
//读到数据包的大小
// NSLog(@"packetLength:---%ld",packetLength);
[sock readDataToLength:packetLength withTimeout:-1 tag:1];
return;
}
if (!currentPacketHead) {
NSLog(@"error:当前数据包的头为空");
//断开连接
return;
}
//正式的包处理
NSUInteger packetLength = [currentPacketHead[@"size"] integerValue];
// NSLog(@"packetLength--%zd",packetLength);
//NSLog(@"data.length--%zd",data.length);
// NSLog(@"正式包数据--==%@",dict);
//说明数据有问题 看正式包的长度是否与包头告诉你的长度一致
if (packetLength <= 0 || data.length != packetLength) {
NSLog(@"error:当前数据包数据大小不正确");
currentPacketHead = nil;
return;
}
NSString *aString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
id jsonMap = [JsonHelper fromJsonString:aString];
if (([jsonMap[@"commondType"] integerValue] != 20)) {//表示回的不是心跳包,是有效数据
dispatch_async(dispatch_get_main_queue(), ^{
[UserDataHelper setUserData:jsonMap[@"robot_version"] forKey:ROBOT_VERSION];
if (msgReveiceBlock) {
NSLog(@"机器人传回的jsonMap==%@",jsonMap);
msgReveiceBlock(jsonMap);
// if ([jsonMap[@"commondType"] integerValue] == [PLAY_RESOURCE_DATA integerValue]){
// NSNotification *notification =[NSNotification notificationWithName:@"fromRobot" object:nil userInfo:jsonMap];
// [[NSNotificationCenter defaultCenter] postNotification:notification];
// }
}
});
}else{//表示回的是心跳包
[UserDataHelper setUserData:jsonMap[@"hyID"] forKey:Robot_EM_ID];
[noSendTimer invalidate];
noSendTimer = nil;
noSendTimer = [NSTimer scheduledTimerWithTimeInterval:ACCEPT_HEART_TIME_SPACE target:self selector:@selector(robotConnectTimeOut) userInfo:nil repeats:NO];
static dispatch_once_t onceToken_id;
dispatch_once(&onceToken_id, ^{
if (_getIdBlock) {
_getIdBlock(jsonMap);
}
});
}
currentPacketHead = nil;
// NSLog(@"fromRobot msg,currentPacketHead,%@",jsonMap);
[asyncSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:1];
}
- (void)robotConnectTimeOut
{
// NSLog(@"检测是否收到心跳包的定定时器,40秒没收到说明断开了");
_connectState = NO;
if (socketTool.connectStateBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
socketTool.connectStateBlock(_connectState);
});
}
[noSendTimer invalidate];
noSendTimer = nil;
}
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(nullable NSError *)err
{//lxaddlx 机器人连接失败的超时时长是oc的socket框架定的
NSLog(@"lxaddlx已经断开连接了 TCP%@",err);
_connectState = NO;
if (socketTool.connectStateBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
socketTool.connectStateBlock(_connectState);
});
}
[sendTimer setFireDate:[NSDate distantFuture]];
[socketTool autoConnectTcp:connectName];
}
- (void)socketDidCloseReadStream:(GCDAsyncSocket *)sock
{
NSLog(@"%@",sock);
}
#pragma mark - Actions
- (void)sendHeartData
{
//NSLog(@"来到发心跳包的定时器");
if (!_connectState) {
//关掉定时器
[sendTimer invalidate];
}
NSDictionary *writeDic = @{@"commondType":@"19"};
// NSLog(@"给机器人发心跳包%@",writeDic);
NSMutableData *writeData = [NSMutableData dataWithData:[JsonHelper toJsonData:writeDic]];
[writeData appendData:[GCDAsyncSocket CRLFData]];
NSUInteger size = writeData.length;
NSMutableDictionary *headDic = [NSMutableDictionary dictionary];
[headDic setObject:[NSString stringWithFormat:@"%ld",(unsigned long)size] forKey:@"size"];
NSData *jsonData = [JsonHelper toJsonData:headDic];
// NSLog(@"写入Socket的心跳包%@",headDic);
NSMutableData *mData = [NSMutableData dataWithData:jsonData];
//分界
[mData appendData:[GCDAsyncSocket CRLFData]];
[mData appendData:writeData];
// NSLog(@"sendHeartData:%@",mData);
[asyncSocket writeData:mData withTimeout:-1 tag:1];
}
//给RB发送指令
- (void)sendMsgToRobot:(NSDictionary*)msgDic{
NSMutableData *writeData = [NSMutableData dataWithData: [JsonHelper toJsonData:msgDic]];
[writeData appendData:[GCDAsyncSocket CRLFData]];
NSUInteger size = writeData.length;
NSMutableDictionary *headDic = [NSMutableDictionary dictionary];
[headDic setObject:[NSString stringWithFormat:@"%ld",size] forKey:@"size"];
//NSLog(@"给机器人发的数据%@",headDic);
NSData *jsonData = [JsonHelper toJsonData:headDic];
NSMutableData *mData = [NSMutableData dataWithData:jsonData];
//分界
[mData appendData:[GCDAsyncSocket CRLFData]];
[mData appendData:writeData];
//NSLog(@"%@",writeData);
[asyncSocket writeData:mData withTimeout:-1 tag:1];
}
@end