feat: sync httpdns sdk/platform updates without large binaries
This commit is contained in:
291
HttpDNSSDK/sdk/ios/NewHttpDNS/Utils/HttpdnsIPQualityDetector.m
Normal file
291
HttpDNSSDK/sdk/ios/NewHttpDNS/Utils/HttpdnsIPQualityDetector.m
Normal file
@@ -0,0 +1,291 @@
|
||||
//
|
||||
// HttpdnsIPQualityDetector.m
|
||||
// NewHttpDNS
|
||||
//
|
||||
// Created by xuyecan on 2025/3/13.
|
||||
// Copyright © 2025 new-inc.com. All rights reserved.
|
||||
//
|
||||
|
||||
#import "HttpdnsIPQualityDetector.h"
|
||||
#import <sys/socket.h>
|
||||
#import <netinet/in.h>
|
||||
#import <arpa/inet.h>
|
||||
#import <netdb.h>
|
||||
#import <unistd.h>
|
||||
#import <sys/time.h>
|
||||
#import <fcntl.h>
|
||||
#import <errno.h>
|
||||
#import "HttpdnsLog_Internal.h"
|
||||
#import "HttpdnsUtil.h"
|
||||
|
||||
// 定义任务类,替代之前的结构体,确保正确的内存管理
|
||||
@interface HttpdnsDetectionTask : NSObject
|
||||
@property (nonatomic, copy) NSString *cacheKey;
|
||||
@property (nonatomic, copy) NSString *ip;
|
||||
@property (nonatomic, strong) NSNumber *port;
|
||||
@property (nonatomic, copy) HttpdnsIPQualityCallback callback;
|
||||
@end
|
||||
|
||||
@implementation HttpdnsDetectionTask
|
||||
@end
|
||||
|
||||
@interface HttpdnsIPQualityDetector ()
|
||||
|
||||
@property (nonatomic, strong) dispatch_queue_t detectQueue;
|
||||
@property (nonatomic, strong) dispatch_semaphore_t concurrencySemaphore;
|
||||
@property (nonatomic, strong) NSMutableArray<HttpdnsDetectionTask *> *pendingTasks;
|
||||
@property (nonatomic, strong) NSLock *pendingTasksLock;
|
||||
@property (nonatomic, assign) BOOL isProcessingPendingTasks;
|
||||
|
||||
/**
|
||||
* 最大并发检测数量,默认为10
|
||||
*/
|
||||
@property (nonatomic, assign) NSUInteger maxConcurrentDetections;
|
||||
|
||||
@end
|
||||
|
||||
@implementation HttpdnsIPQualityDetector
|
||||
|
||||
+ (instancetype)sharedInstance {
|
||||
static HttpdnsIPQualityDetector *instance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
instance = [[HttpdnsIPQualityDetector alloc] init];
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_detectQueue = dispatch_queue_create("com.new.httpdns.ipqualitydetector", DISPATCH_QUEUE_CONCURRENT);
|
||||
_maxConcurrentDetections = 10;
|
||||
_concurrencySemaphore = dispatch_semaphore_create(_maxConcurrentDetections);
|
||||
_pendingTasks = [NSMutableArray array];
|
||||
_pendingTasksLock = [[NSLock alloc] init];
|
||||
_isProcessingPendingTasks = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)scheduleIPQualityDetection:(NSString *)cacheKey
|
||||
ip:(NSString *)ip
|
||||
port:(NSNumber *)port
|
||||
callback:(HttpdnsIPQualityCallback)callback {
|
||||
if (!cacheKey || !ip || !callback) {
|
||||
HttpdnsLogDebug("IPQualityDetector invalid parameters for detection: cacheKey=%@, ip=%@", cacheKey, ip);
|
||||
return;
|
||||
}
|
||||
|
||||
// 尝试获取信号量,如果获取不到,说明已达到最大并发数
|
||||
if (dispatch_semaphore_wait(_concurrencySemaphore, DISPATCH_TIME_NOW) != 0) {
|
||||
// 将任务加入等待队列
|
||||
[self addPendingTask:cacheKey ip:ip port:port callback:callback];
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取到信号量,可以执行检测
|
||||
[self executeDetection:cacheKey ip:ip port:port callback:callback];
|
||||
}
|
||||
|
||||
- (void)addPendingTask:(NSString *)cacheKey ip:(NSString *)ip port:(NSNumber *)port callback:(HttpdnsIPQualityCallback)callback {
|
||||
// 创建任务对象,ARC会自动管理内存
|
||||
HttpdnsDetectionTask *task = [[HttpdnsDetectionTask alloc] init];
|
||||
task.cacheKey = cacheKey;
|
||||
task.ip = ip;
|
||||
task.port = port;
|
||||
task.callback = callback;
|
||||
|
||||
// 加锁添加任务
|
||||
[_pendingTasksLock lock];
|
||||
[_pendingTasks addObject:task];
|
||||
[_pendingTasksLock unlock];
|
||||
|
||||
// 如果没有正在处理等待队列,则开始处理
|
||||
[self processPendingTasksIfNeeded];
|
||||
}
|
||||
|
||||
- (NSUInteger)pendingTasksCount {
|
||||
[_pendingTasksLock lock];
|
||||
NSUInteger count = _pendingTasks.count;
|
||||
[_pendingTasksLock unlock];
|
||||
return count;
|
||||
}
|
||||
|
||||
- (void)processPendingTasksIfNeeded {
|
||||
[_pendingTasksLock lock];
|
||||
BOOL shouldProcess = !_isProcessingPendingTasks && _pendingTasks.count > 0;
|
||||
if (shouldProcess) {
|
||||
_isProcessingPendingTasks = YES;
|
||||
}
|
||||
[_pendingTasksLock unlock];
|
||||
|
||||
if (shouldProcess) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
[self processPendingTasks];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)processPendingTasks {
|
||||
while (1) {
|
||||
// 尝试获取信号量
|
||||
if (dispatch_semaphore_wait(_concurrencySemaphore, DISPATCH_TIME_NOW) != 0) {
|
||||
// 无法获取信号量,等待一段时间后重试
|
||||
[NSThread sleepForTimeInterval:0.1];
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取到信号量,取出一个等待任务
|
||||
HttpdnsDetectionTask *task = nil;
|
||||
|
||||
[_pendingTasksLock lock];
|
||||
if (_pendingTasks.count > 0) {
|
||||
task = _pendingTasks.firstObject;
|
||||
[_pendingTasks removeObjectAtIndex:0];
|
||||
} else {
|
||||
// 没有等待任务了,结束处理
|
||||
_isProcessingPendingTasks = NO;
|
||||
[_pendingTasksLock unlock];
|
||||
// 释放多余的信号量
|
||||
dispatch_semaphore_signal(_concurrencySemaphore);
|
||||
break;
|
||||
}
|
||||
[_pendingTasksLock unlock];
|
||||
|
||||
// 执行任务
|
||||
[self executeDetection:task.cacheKey ip:task.ip port:task.port callback:task.callback];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)executeDetection:(NSString *)cacheKey ip:(NSString *)ip port:(NSNumber *)port callback:(HttpdnsIPQualityCallback)callback {
|
||||
// 创建强引用以确保在异步操作期间对象不会被释放
|
||||
HttpdnsIPQualityCallback strongCallback = [callback copy];
|
||||
|
||||
// 使用后台队列进行检测,避免阻塞主线程
|
||||
dispatch_async(self.detectQueue, ^{
|
||||
NSInteger costTime = [self tcpConnectToIP:ip port:port ? [port intValue] : 80];
|
||||
|
||||
// 在后台线程回调结果
|
||||
dispatch_async(dispatch_get_global_queue(0, 0), ^{
|
||||
strongCallback(cacheKey, ip, costTime);
|
||||
|
||||
// 释放信号量,允许执行下一个任务
|
||||
dispatch_semaphore_signal(self->_concurrencySemaphore);
|
||||
|
||||
// 检查是否有等待的任务需要处理
|
||||
[self processPendingTasksIfNeeded];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
- (NSInteger)tcpConnectToIP:(NSString *)ip port:(int)port {
|
||||
if (!ip || port <= 0) {
|
||||
return -1;
|
||||
}
|
||||
int socketFd;
|
||||
struct sockaddr_in serverAddr;
|
||||
struct sockaddr_in6 serverAddr6;
|
||||
void *serverAddrPtr;
|
||||
socklen_t serverAddrLen;
|
||||
BOOL isIPv6 = [HttpdnsUtil isIPv6Address:ip];
|
||||
BOOL isIpv4 = [HttpdnsUtil isIPv4Address:ip];
|
||||
|
||||
// 创建socket
|
||||
if (isIPv6) {
|
||||
socketFd = socket(AF_INET6, SOCK_STREAM, 0);
|
||||
if (socketFd < 0) {
|
||||
HttpdnsLogDebug("IPQualityDetector failed to create IPv6 socket: %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(&serverAddr6, 0, sizeof(serverAddr6));
|
||||
serverAddr6.sin6_family = AF_INET6;
|
||||
serverAddr6.sin6_port = htons(port);
|
||||
inet_pton(AF_INET6, [ip UTF8String], &serverAddr6.sin6_addr);
|
||||
|
||||
serverAddrPtr = &serverAddr6;
|
||||
serverAddrLen = sizeof(serverAddr6);
|
||||
} else if (isIpv4) {
|
||||
socketFd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (socketFd < 0) {
|
||||
HttpdnsLogDebug("IPQualityDetector failed to create IPv4 socket: %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(&serverAddr, 0, sizeof(serverAddr));
|
||||
serverAddr.sin_family = AF_INET;
|
||||
serverAddr.sin_port = htons(port);
|
||||
inet_pton(AF_INET, [ip UTF8String], &serverAddr.sin_addr);
|
||||
|
||||
serverAddrPtr = &serverAddr;
|
||||
serverAddrLen = sizeof(serverAddr);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 设置非阻塞模式
|
||||
int flags = fcntl(socketFd, F_GETFL, 0);
|
||||
fcntl(socketFd, F_SETFL, flags | O_NONBLOCK);
|
||||
|
||||
// 开始计时
|
||||
struct timeval startTime, endTime;
|
||||
gettimeofday(&startTime, NULL);
|
||||
|
||||
// 尝试连接
|
||||
int connectResult = connect(socketFd, serverAddrPtr, serverAddrLen);
|
||||
|
||||
if (connectResult < 0) {
|
||||
if (errno == EINPROGRESS) {
|
||||
// 连接正在进行中,使用select等待
|
||||
fd_set fdSet;
|
||||
struct timeval timeout;
|
||||
|
||||
FD_ZERO(&fdSet);
|
||||
FD_SET(socketFd, &fdSet);
|
||||
|
||||
// 设置超时时间为2秒
|
||||
// 更长的超时时间不是很有必要,因为建连超过2秒的IP,已经没有优选必要了
|
||||
timeout.tv_sec = 2;
|
||||
timeout.tv_usec = 0;
|
||||
|
||||
int selectResult = select(socketFd + 1, NULL, &fdSet, NULL, &timeout);
|
||||
|
||||
if (selectResult <= 0) {
|
||||
// 超时或错误
|
||||
HttpdnsLogDebug("IPQualityDetector connection to %@ timed out or error: %s", ip, strerror(errno));
|
||||
close(socketFd);
|
||||
return -1;
|
||||
} else {
|
||||
// 检查连接是否成功
|
||||
int error;
|
||||
socklen_t errorLen = sizeof(error);
|
||||
if (getsockopt(socketFd, SOL_SOCKET, SO_ERROR, &error, &errorLen) < 0 || error != 0) {
|
||||
HttpdnsLogDebug("IPQualityDetector connection to %@ failed after select: %s", ip, strerror(error));
|
||||
close(socketFd);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 其他错误
|
||||
HttpdnsLogDebug("IPQualityDetector connection to %@ failed: %s", ip, strerror(errno));
|
||||
close(socketFd);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// 结束计时
|
||||
gettimeofday(&endTime, NULL);
|
||||
|
||||
// 关闭socket
|
||||
close(socketFd);
|
||||
|
||||
// 计算耗时(毫秒)
|
||||
long seconds = endTime.tv_sec - startTime.tv_sec;
|
||||
long microseconds = endTime.tv_usec - startTime.tv_usec;
|
||||
NSInteger costTime = (seconds * 1000) + (microseconds / 1000);
|
||||
|
||||
return costTime;
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user