背景介绍
由于近期需要实现视频通话功能。非第三方,需自己实现。直接上打洞代码吧。
#import
@interface YGUDPNatManager : NSObject
@property (nonatomic, assign) BOOL isUdpNatTranting;
+ (instancetype)sharedManager;
- (NSUInteger)sendDataReturnBlock:(RequestCompletion)block;
- (BOOL)sendDataWithTargetIP:(NSString *)targetIP targetPort:(NSString *)targetPort content:(NSString *)content;
//test
- (void)sendTestData:(NSData *)data withIP:(NSString *)ip withPort:(uint16_t)port;
@end
#import "YGUDPNatManager.h"
#import "GCDAsyncUdpSocket.h"
#import "YGSocketInputSteam.h"
#import "YGSocketOutputSteam.h"
#import "YGTCPUserManager.h"
#import "YGMRLProxy.h"
@interface YGUDPNatManager ()
@property (nonatomic, strong) GCDAsyncUdpSocket *udpSocket;
@property (nonatomic, strong) dispatch_queue_t socketDelegateQueue;
@property (nonatomic, strong) dispatch_queue_t socketSendQueue;
@property (nonatomic, assign) long tag;
@property (nonatomic, copy) RequestCompletion block;
@property (nonatomic, copy) NSDictionary *recvData;
@property (nonatomic,copy) NSString* targetIP;
@end
@implementation YGUDPNatManager {
NSUInteger localport;
}
+ (instancetype)sharedManager{
static YGUDPNatManager* socketManager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
socketManager = [[YGUDPNatManager alloc] init];
});
return socketManager;
}
- (instancetype)init{
self = [super init];
if (self) {
_socketDelegateQueue = dispatch_queue_create("UDPNatQueue", DISPATCH_QUEUE_SERIAL);
_socketSendQueue = dispatch_queue_create("UDPNatSendQueue", DISPATCH_QUEUE_SERIAL);
[self setupSocket];
_tag = 0;
}
return self;
}
//test
- (void)sendTestData:(NSData *)data withIP:(NSString *)ip withPort:(uint16_t)port {
[self.udpSocket sendData:data toHost:ip port:port withTimeout:-1 tag:self.tag];
}
- (void)setupSocket {
self.udpSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:self.socketDelegateQueue];
NSError *error = nil;
if (![UserDefaultsObj integerForKey:@"LocalPortStr"]) {
localport = (arc4random() % 20000) + 12345;
[UserDefaultsObj setInteger:localport forKey:@"LocalPortStr"];
}
YGNLog(@"222=========%ld",(long)[UserDefaultsObj integerForKey:@"LocalPortStr"]);
if (![self.udpSocket bindToPort:[UserDefaultsObj integerForKey:@"LocalPortStr"] error:&error]) {
YGNLog(@"Error binding: %@", error);
return;
}
if (![self.udpSocket beginReceiving:&error]) {
YGNLog(@"Error receiving: %@", error);
return;
}
}
/*
发送数据至 udp服务器--->获取UDP 服务器返回的IP+PORT
*/
- (NSUInteger)sendDataReturnBlock:(RequestCompletion)block {
NSDictionary *dict = @{
@"message":@{
@"type": @"chat",
@"id": [YGTimeUtils messageID],
@"subtype":@"request",
}
};
NSData *requestData = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:nil];
NSMutableData *sendData = [[NSMutableData alloc] initWithData:[self getContent]];
[sendData appendData:requestData];
self.block = block;
[self timeoutSendData:sendData withTime:1];
return localport;
}
- (void)sendData:(NSData*)data {
uint16_t port = IM_UDP_PORT;
NSString *IMhost = IM_OUTSIDENET_IP;
dispatch_async(self.socketSendQueue, ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.udpSocket sendData:data toHost:IMhost port:port withTimeout:-1 tag:self.tag];
});
});
self.tag++;
}
- (NSData *)getContent {
NSData *returnData = [[NSData alloc] init];
//时间戳
NSString *timeString = [YGTimeUtils messageIDForSecond];
//签名
NSString *keyString = nil;
if ([YGUtilities jugdeCurrentEvironment]==1) {
keyString = IM_OutsideNetTestToken;
}else if ([YGUtilities jugdeCurrentEvironment]==2) {
keyString = IM_OfficialToken;
}else {
return returnData;
}
//版本号
NSString *versionStr = [[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"] stringByReplacingOccurrencesOfString:@"." withString:@""];
while (versionStr.length < 4) {
versionStr = [NSString stringWithFormat:@"0%@",versionStr];
}
//合并
NSString *MD5Str = [NSString stringWithFormat:@"%@%@",timeString,keyString];
//服务器标识
NSString *server = @"";
if ([server length] != 8) {
server = @"000.0000";
}
MD5Str = [NSString stringWithFormat:@"%@%@",MD5Str,server];
NSString *requestString = [NSString stringWithFormat:@"%@,%@,%@",timeString,versionStr,[MD5Str md5String]];
returnData = [requestString dataUsingEncoding:NSUTF8StringEncoding];
return returnData;
}
/*
发送 至 指定地址端口的 数据(用于UDP 打洞)
*/
- (BOOL)sendDataWithTargetIP:(NSString *)targetIP targetPort:(NSString *)targetPort content:(NSString *)content {
_isUdpNatTranting = NO;
__block int count = 0;
//开始打洞--->开启线程 不断发送消息
while (!_isUdpNatTranting) {
//发送数据包
self.targetIP = targetIP;
[self sendDataWith:targetIP targetPort:targetPort content:content callBack:^(NSDictionary *response, NSError *error) {
//接收数据包
if (error) {
YGNLog(@"获取数据失败!");
}
YGNLog(@"获取的数据response===============%@",response);
if (response != nil && [response[@"address"] isEqualToString:targetIP]) {
if (count >= 5) {
_isUdpNatTranting = YES;
YGMRLProxy *proxy = [[YGMRLProxy sharedManager]initWithMRL:@"rtp://1.1.1.180:23850" updSocket:self.udpSocket];
}
count++;
}
}];
}
return _isUdpNatTranting;
}
//打洞数据发送
- (void)sendDataWith:(NSString *)targetIP targetPort:(NSString *)targetPort content:(NSString *)content callBack:(RequestCompletion)callBack {
dispatch_async(self.socketSendQueue, ^{
NSData *contentData = [content dataUsingEncoding:NSUTF8StringEncoding];
YGNLog(@"%@发送消息 111111: ip===%@ port===%@ content===%@",[NSThread currentThread],targetIP,targetPort,content);
//发送数据包
[self.udpSocket sendData:contentData toHost:targetIP port:targetPort.integerValue withTimeout:2000 tag:self.tag];
YGNLog(@"%@发送消息 222222: ip===%@ port===%@ content===%@",[NSThread currentThread],targetIP,targetPort,content);
});
self.block = callBack;
self.tag++;
}
- (void)timeoutSendData:(NSData *)data withTime:(NSInteger)time {
[self sendData:data];
//超时
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (self.block) {
if (time >= 3) {
self.block(nil, [NSError errorWithDomain:@"失败" code:101 userInfo:nil]);
self.block = nil;
} else {
[self timeoutSendData:data withTime:time+1];
}
}
});
}
#pragma mark - GCDAsyncUdpSocketDelegate
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address {
YGNLog(@"\n\n didConnectToAddress \n\n");
}
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError *)error {
YGNLog(@"\n\n didNotConnect \n\n");
if (self.block) {
self.block(nil, error);
self.block = nil;
}
}
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didSendDataWithTag:(long)tag {
YGNLog(@"\n\n didSendDataWithTag \n\n");
[sock beginReceiving:nil];
}
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError *)error {
YGNLog(@"\n\n didNotSendDataWithTag \n\n");
if (self.block) {
self.block(nil, error);
self.block = nil;
}
}
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(id)filterContext {
if (_isUdpNatTranting) {
NSLog(@"=9898989898====%@====",data);
return;
}
YGNLog(@"\n\n didReceiveData \n\n");
NSLog(@"fromAddress==%@",[GCDAsyncUdpSocket hostFromAddress:address]);
NSString *result =[[ NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if ([result isEqualToString:@"000"]) {
NSString *strAddress = [GCDAsyncUdpSocket hostFromAddress:address];
NSDictionary *dict = @{@"content":result,@"address":strAddress};
if (self.block) {
self.block(dict, nil);
self.block = nil;
}
} else {
NSError *err;
NSDictionary *jsonDic = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:&err];
if(err) {
NSLog(@"json解析失败:%@",err);
return;
}
if (jsonDic && [jsonDic isKindOfClass:[NSDictionary class]]) {
YGNLog(@"socket收到数据:\n%@",result);
if (self.block) {
self.block(jsonDic[@"message"], nil);
self.block = nil;
}
} else {
if (self.block) {
self.block(nil, [NSError errorWithDomain:@"失败" code:101 userInfo:nil]);
self.block = nil;
}
}
}
}
- (void)udpSocketDidClose:(GCDAsyncUdpSocket *)sock withError:(NSError *)error {
YGNLog(@"\n\n udpSocketDidClose \n\n");
if (self.block) {
self.block(nil, error);
self.block = nil;
}
}
@end
调用:
//接收方打洞 用dstport,和 dstip开始打洞======== 网关 getawayip
YGMessageChatNatType netType = [self judgeTheNat];
ipTestString = _address;
if (netType == YGMessageChatNatType_Intranet) {//非内网
NSString *dsport = [NSString stringWithFormat:@"%ld",_dstPort];
[YGUtilities showTextHUD:@"非内网开始打洞" andView:self.view maintainTime:HUD_DURATION_TIME];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
BOOL flag = [[YGUDPNatManager sharedManager] sendDataWithTargetIP:_dstIp targetPort:dsport content:@"000"];
if (flag) {
dispatch_async(dispatch_get_main_queue(), ^{
[YGUtilities showTextHUD:@"打洞成功!" andView:weakSelf.view maintainTime:5.0];
});
}
});
} else if(netType == YGMessageChatNatType_NotSameNat) {//不同Nat 模拟打洞用_dstLocalPort address
NSString *dsport = [NSString stringWithFormat:@"%ld",_dstLocalPort];//对方的端口
//NSString *localPort = [NSString stringWithFormat:@"%ld",_srcPort];// 本地的端口
[YGUtilities showTextHUD:@"不同NAT开始打洞" andView:self.view maintainTime:HUD_DURATION_TIME];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//
BOOL flag = [[YGUDPNatManager sharedManager] sendDataWithTargetIP:_address targetPort:dsport content:@"000"];
if (flag) {
dispatch_async(dispatch_get_main_queue(), ^{
[YGUtilities showTextHUD:@"打洞成功!" andView:weakSelf.view maintainTime:5.0];
});
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//
BOOL flag = [[YGUDPNatManager sharedManager] sendDataWithTargetIP:_address targetPort:dsport content:@"000"];
if (flag) {
dispatch_async(dispatch_get_main_queue(), ^{
[YGUtilities showTextHUD:@"打洞成功!" andView:weakSelf.view maintainTime:5.0];
});
}
});
} else {//内网
NSString *dsport = [NSString stringWithFormat:@"%ld",_dstPort];
[YGUtilities showTextHUD:@"非内网开始打洞" andView:self.view maintainTime:HUD_DURATION_TIME];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
BOOL flag = [[YGUDPNatManager sharedManager] sendDataWithTargetIP:_dstIp targetPort:dsport content:@"000"];
if (flag) {
dispatch_async(dispatch_get_main_queue(), ^{
[YGUtilities showTextHUD:@"打洞成功!" andView:weakSelf.view maintainTime:5.0];
});
}
});
}
```
图解
![8313FF8CCEAD2BFF6FE677838234F900.jpg](http://upload-images.jianshu.io/upload_images/1281559-6efb7ff47468b107.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
此处只有打洞的过程 其余省略。
一般项目很少涉及到,如果有需要有问题的盆友可以留言。