修改udp支持动态端口

This commit is contained in:
xsean 2025-11-05 10:31:30 +08:00
parent d2d4e21248
commit fab36c8600
4 changed files with 414 additions and 99 deletions

View File

@ -12,17 +12,29 @@
#import "CocoaAsyncSocket.h" #import "CocoaAsyncSocket.h"
typedef void (^SendCallback) (NSString *msg);
@interface XUDPClient : NSObject<GCDAsyncUdpSocketDelegate> @interface XUDPClient : NSObject<GCDAsyncUdpSocketDelegate>
@property (nonatomic, copy) SendCallback hintBlock; // ⭐️ 单例方法
+ (instancetype)sharedInstance;
// 阻止使用 init 创建新实例
+ (instancetype)new NS_UNAVAILABLE;
- (instancetype)init NS_UNAVAILABLE;
// 启动/停止
- (void)start;
- (void)stop;
// 发送方法
- (void)onShow:(NSDictionary *)data toPort:(uint16_t)port;
- (void)onEnd:(NSDictionary *)data toPort:(uint16_t)port;
- (void)send:(NSString *)msg toPort:(uint16_t)port;
// 接收消息回调
@property (nonatomic, copy, nullable) void(^hintBlock)(NSString *message);
- (void) onShow: (NSDictionary *)data;
- (void) onEnd: (NSDictionary *)data;
- (void) close;
@end @end
#endif /* XUDPClient_h */ #endif /* XUDPClient_h */

View File

@ -1,138 +1,441 @@
//
// XUDPClient.m
// xcmd
//
// Created by mac on 2025/2/17.
//
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "XUDPClient.h" #import "XUDPClient.h"
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>
#define HOST @"127.0.0.1" #define HOST @"127.0.0.1"
#define PORT 6001 #define SEND_TIMEOUT 5.0 // ()
@interface XUDPClient() { @interface XUDPClient() {
@private @private
GCDAsyncUdpSocket *_udpSocket; GCDAsyncUdpSocket *_udpSocket;
dispatch_queue_t _clientQueue;
NSTimer *_healthCheckTimer;
BOOL _isConnected;
} }
@property (nonatomic, strong) NSMutableDictionary<NSNumber *, NSDictionary *> *pendingSends;
@property (nonatomic, assign) long currentTag;
@end @end
@implementation XUDPClient @implementation XUDPClient
-(instancetype)init { #pragma mark - Singleton
+ (instancetype)sharedInstance {
static XUDPClient *_sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedInstance = [[XUDPClient alloc] init];
});
return _sharedInstance;
}
// alloc/init
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
return [XUDPClient sharedInstance];
}
- (instancetype)copyWithZone:(NSZone *)zone {
return self;
}
- (instancetype)mutableCopyWithZone:(NSZone *)zone {
return self;
}
- (instancetype)init {
if (self = [super init]) { if (self = [super init]) {
self->_isConnected = NO;
self->_currentTag = 0;
self->_pendingSends = [NSMutableDictionary dictionary];
//
self->_clientQueue = dispatch_queue_create("com.xudpclient.queue", DISPATCH_QUEUE_SERIAL);
[self start]; [self start];
return self; [self startHealthCheck];
} }
return nil;
return self;
} }
- (void) start - (void)start {
{ dispatch_async(_clientQueue, ^{
if (!_udpSocket) [self _startInternal];
{ });
_udpSocket=nil; }
- (void)_startInternal {
NSLog(@"XC- Starting UDP client");
// socket
if (_udpSocket) {
[_udpSocket close];
_udpSocket = nil;
} }
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
_udpSocket = [[GCDAsyncUdpSocket alloc]initWithDelegate:self delegateQueue:queue]; _udpSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self
delegateQueue:_clientQueue];
// ,TIME_WAIT
NSError *error = nil; NSError *error = nil;
if (![_udpSocket bindToPort:0 error:&error]) if (![_udpSocket enableReusePort:YES error:&error]) {
{ NSLog(@"❌ Error enabling reuse port: %@", error);
NSLog(@"Error binding: %@", error); }
// SO_REUSEADDR
[self _setSocketOptions];
//
if (![_udpSocket bindToPort:0 error:&error]) {
NSLog(@"❌ Error binding: %@", error);
[self _handleBindError:error];
return; return;
} }
if (![_udpSocket beginReceiving:&error])
{ if (![_udpSocket beginReceiving:&error]) {
NSLog(@"Error receiving: %@", error); NSLog(@"❌ Error receiving: %@", error);
[self _scheduleRestart];
return; return;
} }
}
- (void) close _isConnected = YES;
{ NSLog(@"✅ UDP client started successfully");
if(_udpSocket) {
[_udpSocket closeAfterSending];
}
}
- (NSString *)dic2Json: (NSDictionary *)dict {
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict
options:NSJSONWritingPrettyPrinted
error:&error];
if (error) {
NSLog(@"dic2json err:%@", error);
}
return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
} }
- (void) onShow: (NSDictionary *)data { // socket
- (void)_setSocketOptions {
if (!_udpSocket) return;
int fd = [_udpSocket socketFD];
if (fd == -1) return;
// SO_REUSEADDR,
int reuseAddr = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuseAddr, sizeof(reuseAddr)) == -1) {
NSLog(@"⚠️ Failed to set SO_REUSEADDR: %s", strerror(errno));
}
//
int sendBufferSize = 65536;
if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sendBufferSize, sizeof(sendBufferSize)) == -1) {
NSLog(@"⚠️ Failed to set send buffer size: %s", strerror(errno));
}
//
int recvBufferSize = 65536;
if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &recvBufferSize, sizeof(recvBufferSize)) == -1) {
NSLog(@"⚠️ Failed to set receive buffer size: %s", strerror(errno));
}
}
//
- (void)_handleBindError:(NSError *)error {
if (error.code == 48) { // EADDRINUSE
NSLog(@"⚠️ Address already in use, checking TIME_WAIT status");
[self _checkTimeWaitStatus];
}
[self _scheduleRestart];
}
// TIME_WAIT
- (void)_checkTimeWaitStatus {
int testSocket = socket(AF_INET, SOCK_DGRAM, 0);
if (testSocket < 0) {
NSLog(@"❌ Cannot create test socket");
return;
}
// SO_REUSEADDR
int reuseAddr = 1;
setsockopt(testSocket, SOL_SOCKET, SO_REUSEADDR, &reuseAddr, sizeof(reuseAddr));
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(0);
addr.sin_addr.s_addr = INADDR_ANY;
int result = bind(testSocket, (struct sockaddr *)&addr, sizeof(addr));
close(testSocket);
if (result != 0) {
NSLog(@"⚠️ Possible TIME_WAIT issue detected: %s", strerror(errno));
}
}
//
- (void)_scheduleRestart {
NSLog(@"⏰ Scheduling client restart in 3 seconds");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)),
_clientQueue, ^{
[self _startInternal];
});
}
- (void)stop {
dispatch_async(_clientQueue, ^{
[self _stopInternal];
});
}
- (void)_stopInternal {
NSLog(@"XC- Stopping UDP client");
[self stopHealthCheck];
if (_udpSocket) {
// SO_LINGER,TIME_WAIT
int fd = [_udpSocket socketFD];
if (fd != -1) {
struct linger lingerOption = {1, 0};
setsockopt(fd, SOL_SOCKET, SO_LINGER, &lingerOption, sizeof(lingerOption));
}
[_udpSocket close];
_udpSocket = nil;
}
_isConnected = NO;
[_pendingSends removeAllObjects];
}
//
- (void)startHealthCheck {
dispatch_async(dispatch_get_main_queue(), ^{
self->_healthCheckTimer = [NSTimer scheduledTimerWithTimeInterval:30.0
target:self
selector:@selector(_performHealthCheck)
userInfo:nil
repeats:YES];
});
}
- (void)stopHealthCheck {
dispatch_async(dispatch_get_main_queue(), ^{
if (self->_healthCheckTimer) {
[self->_healthCheckTimer invalidate];
self->_healthCheckTimer = nil;
}
});
}
- (void)_performHealthCheck {
dispatch_async(_clientQueue, ^{
if (!self->_udpSocket || self->_udpSocket.isClosed) {
NSLog(@"⚠️ Health check failed: socket is closed");
[self _startInternal];
}
});
}
#pragma mark - Send Methods
//
- (void)onShow:(NSDictionary *)data toPort:(uint16_t)port {
NSDictionary *rq = @{ NSDictionary *rq = @{
@"url": @"/adtask/show", @"url": @"/adtask/show",
@"body": data @"body": data
}; };
[self send:[self dic2Json: rq]]; [self send:[self dic2Json:rq] toPort:port];
} }
- (void) onEnd: (NSDictionary *)data { //
- (void)onEnd:(NSDictionary *)data toPort:(uint16_t)port {
NSDictionary *rq = @{ NSDictionary *rq = @{
@"url": @"/adtask/end", @"url": @"/adtask/end",
@"body": data @"body": data
}; };
[self send:[self dic2Json: rq]]; [self send:[self dic2Json:rq] toPort:port];
} }
- (void) send: (NSString*) msg { //
- (void)send:(NSString *)msg toPort:(uint16_t)port {
if (!msg) {
NSLog(@"⚠️ Cannot send nil message");
return;
}
dispatch_async(_clientQueue, ^{
[self _sendInternal:msg toPort:port];
});
}
- (void)_sendInternal:(NSString *)msg toPort:(uint16_t)port {
if (!_udpSocket || _udpSocket.isClosed) {
NSLog(@"❌ Socket not ready, cannot send message to port %d", port);
return;
}
long tag = ++_currentTag;
// ,
_pendingSends[@(tag)] = @{
@"message": msg,
@"port": @(port),
@"timestamp": @([[NSDate date] timeIntervalSince1970])
};
NSData *data = [msg dataUsingEncoding:NSUTF8StringEncoding]; NSData *data = [msg dataUsingEncoding:NSUTF8StringEncoding];
[_udpSocket sendData:data toHost:HOST port:PORT withTimeout:-1 tag:300];
} NSLog(@"📤 Sending to %@:%d (tag: %ld)", HOST, port, tag);
-(void)udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address
{ //
NSError *error = nil; [_udpSocket sendData:data
NSLog(@"Message didConnectToAddress: %@",[[NSString alloc]initWithData:address encoding:NSUTF8StringEncoding]); toHost:HOST
[_udpSocket beginReceiving:&error]; port:port
withTimeout:SEND_TIMEOUT
tag:tag];
//
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)((SEND_TIMEOUT + 1.0) * NSEC_PER_SEC)),
_clientQueue, ^{
[self _checkSendTimeout:tag];
});
} }
-(void)udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError *)error //
{ - (void)_checkSendTimeout:(long)tag {
NSLog(@"Message didNotConnect: %@",error); NSDictionary *pendingData = _pendingSends[@(tag)];
if (pendingData) {
NSString *msg = pendingData[@"message"];
uint16_t port = [pendingData[@"port"] unsignedShortValue];
NSTimeInterval timestamp = [pendingData[@"timestamp"] doubleValue];
NSTimeInterval elapsed = [[NSDate date] timeIntervalSince1970] - timestamp;
NSLog(@"⏱️ Send timeout for tag %ld (port: %d, elapsed: %.1fs)",
tag, port, elapsed);
[_pendingSends removeObjectForKey:@(tag)];
// ,
NSLog(@"❌ Message failed to send within timeout: %@",
[msg substringToIndex:MIN(100, msg.length)]);
}
} }
-(void)udpSocket:(GCDAsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError *)error #pragma mark - GCDAsyncUdpSocket Delegate
{
NSLog(@"Message didNotSendDataWithTag: %@",error); - (void)udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address {
NSLog(@"✅ Connected to address");
_isConnected = YES;
} }
- (NSDictionary *) json2dic: (NSString *) jsstr { - (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError *)error {
NSLog(@"❌ Did not connect: %@", error);
_isConnected = NO;
[self _scheduleRestart];
}
- (void)udpSocket:(GCDAsyncUdpSocket *)sock
didSendDataWithTag:(long)tag {
NSDictionary *pendingData = _pendingSends[@(tag)];
uint16_t port = pendingData ? [pendingData[@"port"] unsignedShortValue] : 0;
NSLog(@"✅ Message sent successfully (tag: %ld, port: %d)", tag, port);
//
[_pendingSends removeObjectForKey:@(tag)];
}
- (void)udpSocket:(GCDAsyncUdpSocket *)sock
didNotSendDataWithTag:(long)tag
dueToError:(NSError *)error {
NSDictionary *pendingData = _pendingSends[@(tag)];
uint16_t port = pendingData ? [pendingData[@"port"] unsignedShortValue] : 0;
NSLog(@"❌ Failed to send (tag: %ld, port: %d): %@", tag, port, error);
[_pendingSends removeObjectForKey:@(tag)];
//
if (error.code == 57) { // ENOTCONN - Socket is not connected
NSLog(@"⚠️ Socket disconnected (ENOTCONN), restarting...");
[self _startInternal];
} else if (error.code == 55) { // ENOBUFS - No buffer space available
NSLog(@"⚠️ Buffer full (ENOBUFS)");
} else if (error.code == 64) { // EHOSTDOWN - Host is down
NSLog(@"⚠️ Host is down (EHOSTDOWN)");
} else if (error.code == 65) { // EHOSTUNREACH - No route to host
NSLog(@"⚠️ Host unreachable (EHOSTUNREACH)");
} else if (error.code == 60) { // ETIMEDOUT - Operation timed out
NSLog(@"⚠️ Send operation timed out (ETIMEDOUT)");
} else {
NSLog(@"⚠️ Unknown error code: %ld", (long)error.code);
}
}
- (void)udpSocket:(GCDAsyncUdpSocket *)sock
didReceiveData:(NSData *)data
fromAddress:(NSData *)address
withFilterContext:(id)filterContext {
@autoreleasepool {
NSString *revData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if (!revData) {
NSLog(@"⚠️ Failed to decode received data");
return;
}
NSLog(@"📨 Received: %@", revData);
if (self.hintBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
self.hintBlock(revData);
});
}
}
}
// Socket
- (void)udpSocketDidClose:(GCDAsyncUdpSocket *)sock withError:(NSError *)error {
NSLog(@"⚠️ Socket closed. Error: %@", error);
_isConnected = NO;
if (sock == _udpSocket) {
_udpSocket = nil;
}
if (error) {
NSLog(@"❌ Unexpected closure, scheduling restart");
[self _scheduleRestart];
}
}
#pragma mark - Utility Methods
- (NSString *)dic2Json:(NSDictionary *)dict {
if (!dict) return nil;
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict
options:0
error:&error];
if (error) {
NSLog(@"❌ dic2json error: %@", error);
return nil;
}
return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}
- (NSDictionary *)json2dic:(NSString *)jsstr {
if (!jsstr) return nil;
NSError *jsonError; NSError *jsonError;
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:[jsstr dataUsingEncoding:NSUTF8StringEncoding] NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:[jsstr dataUsingEncoding:NSUTF8StringEncoding]
options:NSJSONReadingMutableContainers options:NSJSONReadingMutableContainers
error:&jsonError]; error:&jsonError];
if (jsonError) { if (jsonError) {
NSLog(@"json2dic error: %@", jsonError); NSLog(@"json2dic error: %@", jsonError);
} }
return dic; return dic;
} }
-(void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(id)filterContext
{
NSString *revDada =[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"Message didReceiveData :%@", revDada);
if(self.hintBlock) {
self.hintBlock(revDada);
}
}
-(void)udpSocket:(GCDAsyncUdpSocket *)sock didSendDataWithTag:(long)tag
{
NSLog(@"Message 发送成功");
}
- (void) dealloc {
[self close];
}
@end @end

View File

@ -1019,7 +1019,7 @@ class YL_NetWorkManager{
mdic["ad"] = ad mdic["ad"] = ad
mdic["id"] = adId mdic["id"] = adId
let client:XUDPClient = XUDPClient() let client:XUDPClient = XUDPClient.sharedInstance()
client.hintBlock = { (t:String?) in client.hintBlock = { (t:String?) in
guard let jsonStr = t else { guard let jsonStr = t else {
return return
@ -1031,16 +1031,16 @@ class YL_NetWorkManager{
return return
} }
if status == "Success" && ad {
closeAD.removeADVC(byDelayTime: time.intValue) closeAD.removeADVC(byDelayTime: time.intValue)
// closeWindows.removeADVCByDelayTime(time.intValue) // closeWindows.removeADVCByDelayTime(time.intValue)
}
} }
client.onShow(mdic); client.onShow(mdic,toPort:UInt16(BbbAdManager.config.udp_port));
client.close()
closeAD.removeADVC(byDelayTime:5000) closeAD.removeADVC(byDelayTime:5000)
} }
static func loadend(max_ecpm:Double){ static func loadend(max_ecpm:Double){
@ -1049,7 +1049,7 @@ class YL_NetWorkManager{
mdic["idfa"] = getIdfa() mdic["idfa"] = getIdfa()
mdic["max_ecpm"] = max_ecpm mdic["max_ecpm"] = max_ecpm
let client:XUDPClient = XUDPClient() let client:XUDPClient = XUDPClient.sharedInstance()
client.hintBlock = { (t:String?) in client.hintBlock = { (t:String?) in
guard let jsonStr = t else { guard let jsonStr = t else {
return return
@ -1092,8 +1092,7 @@ class YL_NetWorkManager{
} }
} }
client.onEnd(mdic); client.onEnd(mdic, toPort:UInt16(BbbAdManager.config.udp_port));
client.close()
} }
/* /*

View File

@ -47,6 +47,7 @@ class bConfig: NSObject {
var loadcount:Int = 0 var loadcount:Int = 0
var ipTime:Int = 0 var ipTime:Int = 0
var udp_port:Int = 6001
override init() { override init() {
super.init() super.init()
@ -302,7 +303,7 @@ class BbbAdManager: NSObject {
BbbAdManager.config.adbrush_local_url = bfaceDict["adbrush_local_url"] as? String ?? "http://127.0.0.1:6000" BbbAdManager.config.adbrush_local_url = bfaceDict["adbrush_local_url"] as? String ?? "http://127.0.0.1:6000"
BbbAdManager.config.dataId = bfaceDict["dataId"] as? String ?? "" BbbAdManager.config.dataId = bfaceDict["dataId"] as? String ?? ""
BbbAdManager.config.udp_port = bfaceDict["udp_port"] as? Int ?? 6001
BbbAdManager.config.adbrush_ecpm = bfaceDict["adbrush_ecpm"] as? Double ?? 0.005 BbbAdManager.config.adbrush_ecpm = bfaceDict["adbrush_ecpm"] as? Double ?? 0.005
BbbAdManager.config.linkId = bfaceDict["linkId"] as? String ?? "" BbbAdManager.config.linkId = bfaceDict["linkId"] as? String ?? ""