Skip to content

Commit

Permalink
ping work pool
Browse files Browse the repository at this point in the history
  • Loading branch information
yanue committed Sep 17, 2023
1 parent a2c6919 commit 2f818e5
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 107 deletions.
34 changes: 18 additions & 16 deletions V2rayU/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,22 +42,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}
}

AppCenter.start(withAppSecret: "d52dd1a1-7a3a-4143-b159-a30434f87713", services:[
Analytics.self,
Crashes.self
])

// auto check updates
if UserDefaults.getBool(forKey: .autoCheckVersion) {
menuController.checkV2rayUVersion()
// check version
V2rayUpdater.checkForUpdatesInBackground()
}

_ = GeneratePACFile(rewrite: true)
// start http server for pac
V2rayLaunch.startHttpServer()

// wake and sleep
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(onSleepNote(note:)), name: NSWorkspace.willSleepNotification, object: nil)
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(onWakeNote(note:)), name: NSWorkspace.didWakeNotification, object: nil)
Expand All @@ -78,6 +62,23 @@ class AppDelegate: NSObject, NSApplicationDelegate {

// Register global hotkey
ShortcutsController.bindShortcuts()

// appcenter init
AppCenter.start(withAppSecret: "d52dd1a1-7a3a-4143-b159-a30434f87713", services:[
Analytics.self,
Crashes.self
])

// auto check updates
if UserDefaults.getBool(forKey: .autoCheckVersion) {
menuController.checkV2rayUVersion()
// check version
V2rayUpdater.checkForUpdatesInBackground()
}

_ = GeneratePACFile(rewrite: true)
// start http server for pac
V2rayLaunch.startHttpServer()
}

func checkDefault() {
Expand All @@ -94,6 +95,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
if UserDefaults.get(forKey: .runMode) == nil {
UserDefaults.set(forKey: .runMode, value: RunMode.pac.rawValue)
}
V2rayServer.loadConfig()
if V2rayServer.count() == 0 {
// add default
V2rayServer.add(remark: "default", json: "", isValid: false)
Expand Down
8 changes: 6 additions & 2 deletions V2rayU/MainMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -312,8 +312,10 @@ class MenuController: NSObject, NSMenuDelegate {
// set URLSessionDataDelegate
let metric = PingMetrics()
metric.ping = ping
let config = getProxyUrlSessionConfigure()
config.timeoutIntervalForRequest = 2
// url request
let session = URLSession(configuration: getProxyUrlSessionConfigure(), delegate: metric, delegateQueue: nil)
let session = URLSession(configuration: config, delegate: metric, delegateQueue: nil)
let url = URL(string: "http://www.google.com/generate_204")!
let task = session.dataTask(with: URLRequest(url: url)){(data: Data?, response: URLResponse?, error: Error?) in
NSLog("ping current end")
Expand Down Expand Up @@ -431,7 +433,9 @@ class MenuController: NSObject, NSMenuDelegate {

func showServers() {
// reomve old items
serverItems.submenu?.removeAllItems()
if serverItems.submenu != nil {
serverItems.submenu?.removeAllItems()
}
let curSer = UserDefaults.get(forKey: .v2rayCurrentServerName)
// reload servers
V2rayServer.loadConfig()
Expand Down
187 changes: 105 additions & 82 deletions V2rayU/Ping.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ var fastV2rayName = ""
var fastV2raySpeed = 5
var ping = PingSpeed()
let second: Double = 1000000

let pingURL = URL(string: "http://www.google.com/generate_204")!

class PingSpeed: NSObject {
var pingServers: [V2rayItem] = []
var serverLen: Int = 0
var unpingServers: Dictionary = [String: Bool]()

let lock = NSLock()
let semaphore = DispatchSemaphore(value: 10) // work pool

func pingAll() {
NSLog("ping start")
Expand All @@ -28,7 +30,7 @@ class PingSpeed: NSObject {
return
}
fastV2rayName = ""
pingServers = []
unpingServers = [String: Bool]()
let itemList = V2rayServer.list()
if itemList.count == 0 {
return
Expand All @@ -51,69 +53,77 @@ class PingSpeed: NSObject {
menuController.statusMenu.item(withTag: 1)?.title = pingTip
// in ping
inPing = true
let pingQueue = DispatchQueue.init(label: "pingQueue") // 串行队列
// use thread
let thread = Thread {
// ping
self.serverLen = itemList.count

for item in itemList {
pingQueue.async {
// run ping by async queue
self.pingEachServer(item: item)
}
let pingQueue = DispatchQueue(label: "pingQueue", attributes: .concurrent) // 串行队列
for item in itemList {
unpingServers[item.name] = true
pingQueue.async {
self.semaphore.wait()
// run ping by async queue
self.pingEachServer(item: item)
}
// refresh menu
self.refreshStatusMenu()
NSLog("ping done")
}
thread.name = "pingThread"
thread.threadPriority = 1 /// 优先级
thread.start()
}

func pingEachServer(item: V2rayItem) {
NSLog("ping \(item.name) - \(item.remark)")

if !item.isValid {
pingServers.append(item)
return
}
// ping
doPing(item: item)
// print("finished",message)
pingServers.append(item)
// refresh menu
refreshStatusMenu()
PingServer(item: item).doPing()
}

func refreshStatusMenu() {
if pingServers.count == serverLen {
func refreshStatusMenu(item: V2rayItem) {
lock.lock()
defer { lock.unlock() }

semaphore.signal()

unpingServers.removeValue(forKey: item.name)

if unpingServers.count == 0 {
inPing = false
menuController.statusMenu.item(withTag: 1)?.title = "Ping Speed..."
menuController.showServers()
// reload config
if menuController.configWindow != nil {
// fix: must be used from main thread only
usleep(useconds_t(1 * second))
do {
DispatchQueue.main.async {
menuController.configWindow.serversTableView.reloadData()
menuController.statusMenu.item(withTag: 1)?.title = "Ping Speed..."
menuController.showServers()
// reload config
if menuController.configWindow != nil {
// fix: must be used from main thread only
menuController.configWindow.serversTableView.reloadData()
}
}
}
}
}
}

class PingServer: NSObject, URLSessionDataDelegate {
var item: V2rayItem

var bindPort: UInt16 = 0
var jsonFile: String = ""
var process: Process = Process()

init(item: V2rayItem) {
self.item = item
super.init() // can actually be omitted in this example because will happen automatically.
}

func doPing(item: V2rayItem) {
let randomInt = Int.random(in: 9000...36500)
let (_, bindPort) = getUsablePort(port: uint16(randomInt))
func doPing() {
if !item.isValid {
// refresh servers
ping.refreshStatusMenu(item: item)
return
}
let (_, _bindPort) = getUsablePort(port: uint16(Int.random(in: 9000 ... 36500)))

NSLog("doPing: \(item.name)-\(item.remark) - \(bindPort)")
let jsonFile = AppHomePath + "/.config_ping.\(item.name).json"
NSLog("doPing: \(item.name)-\(item.remark) - \(_bindPort)")
bindPort = _bindPort
jsonFile = AppHomePath + "/.config_ping.\(item.name).json"

// create v2ray config file
createV2rayJsonFileForPing(item: item, bindPort: bindPort, jsonFile: jsonFile)
createV2rayJsonFileForPing()

// Create a Process instance with async launch
let process = Process()
// can't use `/bin/bash -c cmd...` otherwize v2ray process will become a ghost process
process.launchPath = v2rayCoreFile
process.arguments = ["-config", jsonFile]
Expand All @@ -126,48 +136,18 @@ class PingSpeed: NSObject {
}
// async launch and can't waitUntilExit
process.launch()

// sleep for wait v2ray process instanse
usleep(useconds_t(1 * second))

// async process
// set URLSessionDataDelegate
let metric = PingMetrics()
metric.ping = item

// url request
let session = URLSession(configuration: getProxyUrlSessionConfigure(httpProxyPort: bindPort), delegate: metric, delegateQueue: nil)
let url = URL(string: "http://www.google.com/generate_204")!
let task = session.dataTask(with: URLRequest(url: url)){(data: Data?, response: URLResponse?, error: Error?) in
self.pingEnd(process: process, jsonFile: jsonFile, bindPort: bindPort)
}
let session = URLSession(configuration: getProxyUrlSessionConfigure(httpProxyPort: bindPort), delegate: self, delegateQueue: nil)
let task = session.dataTask(with: URLRequest(url: pingURL))
task.resume()
}

func pingEnd(process: Process, jsonFile:String, bindPort: UInt16) {
// exit process
if process.isRunning {
// terminate v2ray process
process.terminate()
process.waitUntilExit()
}

// close port
closePort(port: bindPort)

// delete config
do {
let jsonFilePath = URL(fileURLWithPath: jsonFile)
try? FileManager.default.removeItem(at: jsonFilePath)
}

// refresh server
self.refreshStatusMenu()
}

func createV2rayJsonFileForPing(item: V2rayItem, bindPort: UInt16, jsonFile: String) {
func createV2rayJsonFileForPing() {
var jsonText = item.json
NSLog("bindPort: \(item.name)-\(item.remark) - \(bindPort)")
// parse old
let vCfg = V2rayConfig()
vCfg.enableSocks = false // just can use one tcp port
Expand All @@ -181,18 +161,61 @@ class PingSpeed: NSObject {

do {
let jsonFilePath = URL(fileURLWithPath: jsonFile)

// delete before config
if FileManager.default.fileExists(atPath: jsonFile) {
try? FileManager.default.removeItem(at: jsonFilePath)
}

// write
try jsonText.write(to: jsonFilePath, atomically: true, encoding: String.Encoding.utf8)
} catch let error {
// failed to write file – bad permissions, bad filename, missing permissions, or more likely it can't be converted to the encoding
NSLog("save json file fail: \(error)")
}
}

// MARK: - URLSessionDataDelegate

func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {
item.speed = "-1ms"
if let transactionMetrics = metrics.transactionMetrics.first {
let fetchStartDate = transactionMetrics.fetchStartDate
let responseEndDate = transactionMetrics.responseEndDate
// check
if responseEndDate != nil && fetchStartDate != nil {
let requestDuration = responseEndDate!.timeIntervalSince(fetchStartDate!)
let pingTs = Int(requestDuration * 100)
print("PingResult: fetchStartDate=\(fetchStartDate!),responseEndDate=\(responseEndDate!),requestDuration=\(requestDuration),pingTs=\(pingTs)")
// update ping speed
item.speed = String(format: "%d", pingTs) + "ms"
}
}
// save
item.store()
pingEnd()
}

func pingEnd() {
NSLog("ping end: \(item.remark) - \(item.speed)")

// refresh servers
ping.refreshStatusMenu(item: item)

// exit process
if process.isRunning {
// terminate v2ray process
process.terminate()
process.waitUntilExit()
}

// close port
closePort(port: bindPort)

// delete config
do {
let jsonFilePath = URL(fileURLWithPath: jsonFile)
try? FileManager.default.removeItem(at: jsonFilePath)
}
}
}

class PingMetrics: NSObject, URLSessionDataDelegate {
Expand Down
6 changes: 5 additions & 1 deletion V2rayU/Util.swift
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,8 @@ func getProxyUrlSessionConfigure() -> URLSessionConfiguration {
kCFNetworkProxiesHTTPSPort as AnyHashable: proxyPort,
]
}
configuration.requestCachePolicy = .reloadIgnoringLocalCacheData
configuration.urlCache = nil
configuration.timeoutIntervalForRequest = 30 // Set your desired timeout interval in seconds

return configuration
Expand All @@ -510,7 +512,9 @@ func getProxyUrlSessionConfigure(httpProxyPort: uint16) -> URLSessionConfigurati
kCFNetworkProxiesHTTPSProxy as AnyHashable: proxyHost,
kCFNetworkProxiesHTTPSPort as AnyHashable: proxyPort,
]
configuration.timeoutIntervalForRequest = 30 // Set your desired timeout interval in seconds
configuration.requestCachePolicy = .reloadIgnoringLocalCacheData
configuration.urlCache = nil
configuration.timeoutIntervalForRequest = 2 // Set your desired timeout interval in seconds
return configuration
}

Expand Down
2 changes: 2 additions & 0 deletions V2rayU/V2rayLaunch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ class V2rayLaunch: NSObject {
v2rayProcess = Process()
v2rayProcess.launchPath = v2rayCoreFile
v2rayProcess.arguments = ["-config", JsonConfigFilePath]
v2rayProcess.standardError = nil
v2rayProcess.standardOutput = nil
v2rayProcess.terminationHandler = { process in
if process.terminationStatus != EXIT_SUCCESS {
NSLog("process is not kill \(process.description) - \(process.processIdentifier) - \(process.terminationStatus)")
Expand Down
Loading

0 comments on commit 2f818e5

Please sign in to comment.