大家好! 我已经尝试了很长一段时间使用UDP和GCDAsyncUDPSocket库发送数据包p2p。所以我发现一个开源库连接到STUN服务器并保持外部端口打开。它工作得很好,我已经成功地在本地网络中发送数据。 (在自己的路由器出现问题后,拒绝通过本地网络中的udp连接p2p - Android创建的网络仍然没有问题)
当我尝试使用UDP打孔发送数据时出现问题。
STUNClient: https://github.com/soulfly/STUN-iOS
我修改了UDPServer的代码:
ViewController.h:
#import <UIKit/UIKit.h>
#import "STUNClient.h"
@interface ViewController : UIViewController <STUNClientDelegate>
{
IBOutlet UITextField *portField;
IBOutlet UIButton *startStopButton;
IBOutlet UIWebView *webView;
}
- (IBAction)startStop:(id)sender;
- (IBAction)startStun:(id)sender;
@end
ViewController.m
#import "ViewController.h"
#import "GCDAsyncUdpSocket.h"
#import "DDLog.h"
// Log levels: off, error, warn, info, verbose
static const int ddLogLevel = LOG_LEVEL_VERBOSE;
#define FORMAT(format, ...) [NSString stringWithFormat:(format), ##__VA_ARGS__]
@interface ViewController ()
{
BOOL isRunning;
GCDAsyncUdpSocket *udpSocket;
NSMutableString *log;
STUNClient* stunClient;
}
@end
@implementation ViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]))
{
log = [[NSMutableString alloc] init];
// Setup our socket.
// The socket will invoke our delegate methods using the usual delegate paradigm.
// However, it will invoke the delegate methods on a specified GCD delegate dispatch queue.
//
// Now we can configure the delegate dispatch queues however we want.
// We could simply use the main dispatch queue, so the delegate methods are invoked on the main thread.
// Or we could use a dedicated dispatch queue, which could be helpful if we were doing a lot of processing.
//
// The best approach for your application will depend upon convenience, requirements and performance.
//
// For this simple example, we're just going to use the main thread.
stunClient= [[STUNClient alloc] init];
udpSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
webView.dataDetectorTypes = UIDataDetectorTypeNone;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
}
- (void)viewDidUnload
{
[super viewDidUnload];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)getKeyboardHeight:(float *)keyboardHeightPtr
animationDuration:(double *)animationDurationPtr
from:(NSNotification *)notification
{
float keyboardHeight;
double animationDuration;
// UIKeyboardCenterBeginUserInfoKey:
// The key for an NSValue object containing a CGRect
// that identifies the start frame of the keyboard in screen coordinates.
CGRect beginRect = [[[notification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue];
CGRect endRect = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation))
{
keyboardHeight = ABS(beginRect.origin.x - endRect.origin.x);
}
else
{
keyboardHeight = ABS(beginRect.origin.y - endRect.origin.y);
}
// UIKeyboardAnimationDurationUserInfoKey
// The key for an NSValue object containing a double that identifies the duration of the animation in seconds.
animationDuration = [[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
if (keyboardHeightPtr) *keyboardHeightPtr = keyboardHeight;
if (animationDurationPtr) *animationDurationPtr = animationDuration;
}
- (void)keyboardWillShow:(NSNotification *)notification
{
float keyboardHeight = 0.0F;
double animationDuration = 0.0;
[self getKeyboardHeight:&keyboardHeight animationDuration:&animationDuration from:notification];
CGRect webViewFrame = webView.frame;
webViewFrame.size.height -= keyboardHeight;
void (^animationBlock)(void) = ^{
webView.frame = webViewFrame;
};
UIViewAnimationOptions options = 0;
[UIView animateWithDuration:animationDuration
delay:0.0
options:options
animations:animationBlock
completion:NULL];
}
- (void)keyboardWillHide:(NSNotification *)notification
{
float keyboardHeight = 0.0F;
double animationDuration = 0.0;
[self getKeyboardHeight:&keyboardHeight animationDuration:&animationDuration from:notification];
CGRect webViewFrame = webView.frame;
webViewFrame.size.height += keyboardHeight;
void (^animationBlock)(void) = ^{
webView.frame = webViewFrame;
};
UIViewAnimationOptions options = 0;
[UIView animateWithDuration:animationDuration
delay:0.0
options:options
animations:animationBlock
completion:NULL];
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
DDLogError(@"webView:didFailLoadWithError: %@", error);
}
- (void)webViewDidFinishLoad:(UIWebView *)sender
{
NSString *scrollToBottom = @"window.scrollTo(document.body.scrollWidth, document.body.scrollHeight);";
[sender stringByEvaluatingJavaScriptFromString:scrollToBottom];
}
- (void)logError:(NSString *)msg
{
NSString *prefix = @"<font color=\"#B40404\">";
NSString *suffix = @"</font><br/>";
[log appendFormat:@"%@%@%@\n", prefix, msg, suffix];
NSString *html = [NSString stringWithFormat:@"<html><body>\n%@\n</body></html>", log];
[webView loadHTMLString:html baseURL:nil];
}
- (void)logInfo:(NSString *)msg
{
NSString *prefix = @"<font color=\"#6A0888\">";
NSString *suffix = @"</font><br/>";
[log appendFormat:@"%@%@%@\n", prefix, msg, suffix];
NSString *html = [NSString stringWithFormat:@"<html><body>\n%@\n</body></html>", log];
[webView loadHTMLString:html baseURL:nil];
}
- (void)logMessage:(NSString *)msg
{
NSString *prefix = @"<font color=\"#000000\">";
NSString *suffix = @"</font><br/>";
[log appendFormat:@"%@%@%@\n", prefix, msg, suffix];
NSString *html = [NSString stringWithFormat:@"<html><body>%@</body></html>", log];
[webView loadHTMLString:html baseURL:nil];
}
- (IBAction)startStop:(id)sender
{
if (isRunning)
{
// STOP udp echo server
[udpSocket close];
[self logInfo:@"Stopped Udp Echo server"];
isRunning = false;
[portField setEnabled:YES];
[startStopButton setTitle:@"Start" forState:UIControlStateNormal];
}
else
{
//*START udp echo server
int port = [portField.text intValue];
if (port < 0 || port > 65535)
{
portField.text = @"";
port = 0;
}
NSError *error = nil;
if (![udpSocket bindToPort:port error:&error])
{
[self logError:FORMAT(@"Error starting server (bind): %@", error)];
return;
}
if (![udpSocket beginReceiving:&error])
{
[udpSocket close];
[self logError:FORMAT(@"Error starting server (recv): %@", error)];
return;
}
[self logInfo:FORMAT(@"Udp Echo server started on port %hu", [udpSocket localPort])];
isRunning = YES;
[portField setEnabled:NO];
[startStopButton setTitle:@"Stop" forState:UIControlStateNormal];
}
}
-(void)didReceivePublicIPandPort:(NSDictionary *) data{
NSLog(@"Public IP=%@, public Port=%@, NAT is Symmetric: %@", [data objectForKey:publicIPKey],
[data objectForKey:publicPortKey], [data objectForKey:isPortRandomization]);
int portT= [((NSNumber*)[data objectForKey:publicPortKey]) intValue];
NSString* ipT=[data objectForKey:publicIPKey];
[self logInfo:FORMAT(@"STUN says external IP: %@ PORT %d", ipT,portT)];
[self logInfo:FORMAT(@"LOCAL IP: %@ LOCAL PORT %d", [udpSocket localHost],[udpSocket localPort])];
isRunning =YES;
[stunClient startSendIndicationMessage];
}
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data
fromAddress:(NSData *)address
withFilterContext:(id)filterContext
{
if (!isRunning) return;
NSString *msg = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if (msg)
{
/* If you want to get a display friendly version of the IPv4 or IPv6 address, you could do this:
NSString *host = nil;
uint16_t port = 0;
[GCDAsyncUdpSocket getHost:&host port:&port fromAddress:address];
*/
[self logMessage:msg];
}
else
{
[self logError:@"Error converting received data into UTF-8 String"];
}
[udpSocket sendData:data toAddress:address withTimeout:-1 tag:0];
}
- (IBAction)startStun:(id)sender
{
[stunClient requestPublicIPandPortWithUDPSocket:udpSocket delegate:self];
}
@end
我的所作所为:
启用客户端和服务器应用。
按下STUN按钮 - 哪个 启动` - (IBAction)startStun:(id)sender;
也许是路由器问题?或者我不完全理解打孔方法?