美文网首页
iOS之P2P视频语音通话(-) 打洞

iOS之P2P视频语音通话(-) 打洞

作者: ningning_ | 来源:发表于2017-03-29 16:26 被阅读676次

    背景介绍

    由于近期需要实现视频通话功能。非第三方,需自己实现。直接上打洞代码吧。

    #import <Foundation/Foundation.h>
    
    @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 ()<GCDAsyncUdpSocketDelegate>
    @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](https://img.haomeiwen.com/i1281559/6efb7ff47468b107.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    此处只有打洞的过程 其余省略。
    一般项目很少涉及到,如果有需要有问题的盆友可以留言。

    相关文章

      网友评论

          本文标题:iOS之P2P视频语音通话(-) 打洞

          本文链接:https://www.haomeiwen.com/subject/rmheottx.html