mirror of
https://github.com/bolucat/Archive.git
synced 2025-12-24 13:28:37 +08:00
333 lines
10 KiB
Swift
333 lines
10 KiB
Swift
//
|
|
// ping.swift
|
|
// V2rayU
|
|
//
|
|
// Created by Erick on 2019/10/30.
|
|
// Copyright © 2019 yanue. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
// ping and choose fastest v2ray
|
|
var inPing = false
|
|
var inPingCurrent = false
|
|
|
|
var ping = PingSpeed()
|
|
let second: Double = 1000000
|
|
let pingURL = URL(string: "http://www.gstatic.com/generate_204")!
|
|
|
|
func getRandomPort() -> UInt16 {
|
|
return UInt16.random(in: 49152...65535)
|
|
}
|
|
|
|
class PingSpeed: NSObject {
|
|
let maxConcurrentTasks = 30
|
|
|
|
func pingAll() {
|
|
NSLog("ping start")
|
|
guard !inPing else {
|
|
NSLog("ping inPing")
|
|
return
|
|
}
|
|
|
|
V2rayLaunch.checkV2rayCore()
|
|
|
|
inPing = true
|
|
killAllPing()
|
|
|
|
let itemList = V2rayServer.all()
|
|
guard !itemList.isEmpty else {
|
|
NSLog("no items")
|
|
inPing = false
|
|
return
|
|
}
|
|
|
|
let pingTip = isMainland ? "Ping Speed - In Testing" : "Ping Speed - 测试中"
|
|
menuController.setStatusMenuTip(pingTip: pingTip)
|
|
|
|
Task {
|
|
do {
|
|
try await pingTaskGroup(items: itemList)
|
|
} catch {
|
|
NSLog("pingTaskGroup error: \(error)")
|
|
}
|
|
}
|
|
NSLog("pingAll")
|
|
}
|
|
|
|
func pingTaskGroup(items: [V2rayItem]) async throws {
|
|
let taskChunks = stride(from: 0, to: items.count, by: maxConcurrentTasks).map {
|
|
Array(items[$0..<min($0 + maxConcurrentTasks, items.count)])
|
|
}
|
|
NSLog("pingTaskGroup-start: taskChunks=\(taskChunks.count)")
|
|
|
|
for (i, chunk) in taskChunks.enumerated() {
|
|
NSLog("pingTaskGroup-start-\(i): count=\(chunk.count)")
|
|
try await withThrowingTaskGroup(of: Void.self) { group in
|
|
for item in chunk {
|
|
group.addTask {
|
|
do {
|
|
try await self.pingEachServer(item: item)
|
|
} catch {
|
|
NSLog("pingEachServer error: \(error)")
|
|
}
|
|
}
|
|
}
|
|
try await group.waitForAll()
|
|
}
|
|
NSLog("pingTaskGroup-end-\(i)")
|
|
}
|
|
NSLog("pingTaskGroup-end")
|
|
pingEnd()
|
|
}
|
|
|
|
func pingEachServer(item: V2rayItem) async throws {
|
|
NSLog("pingEachServer: \(item.name) - \(item.remark)")
|
|
guard item.isValid else { return }
|
|
|
|
let t = PingServer(item: item)
|
|
try await t.doPing()
|
|
}
|
|
|
|
func pingEnd() {
|
|
inPing = false
|
|
let pingTip = "Ping"
|
|
NSLog("pingEnd: \(pingTip)")
|
|
menuController.setStatusMenuTip(pingTip: pingTip)
|
|
menuController.showServers()
|
|
killAllPing()
|
|
}
|
|
}
|
|
|
|
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()
|
|
}
|
|
|
|
func doPing() async throws {
|
|
bindPort = getRandomPort()
|
|
let _json_file = ".\(item.name).json"
|
|
jsonFile = AppHomePath + "/" + _json_file
|
|
|
|
createV2rayJsonFileForPing()
|
|
|
|
let pingCmd = "cd \(AppHomePath) && ./v2ray-core/v2ray run -config \(_json_file)"
|
|
NSLog("pingCmd: \(pingCmd)")
|
|
|
|
process.launchPath = "/bin/bash"
|
|
process.arguments = ["-c", pingCmd]
|
|
process.terminationHandler = { _process in
|
|
if _process.terminationStatus != EXIT_SUCCESS {
|
|
NSLog("process is not kill \(self.bindPort) - \(_process.description) - \(_process.processIdentifier) - \(_process.terminationStatus)")
|
|
_process.terminate()
|
|
_process.waitUntilExit()
|
|
}
|
|
}
|
|
|
|
process.launch()
|
|
usleep(useconds_t(2 * second))
|
|
|
|
let session = URLSession(configuration: getProxyUrlSessionConfigure(httpProxyPort: bindPort), delegate: self, delegateQueue: nil)
|
|
do {
|
|
_ = try await session.data(for: URLRequest(url: pingURL))
|
|
} catch {
|
|
NSLog("session request fail: \(error)")
|
|
}
|
|
}
|
|
|
|
func createV2rayJsonFileForPing() {
|
|
var jsonText = item.json
|
|
let vCfg = V2rayConfig()
|
|
vCfg.enableSocks = false
|
|
vCfg.parseJson(jsonText: item.json)
|
|
vCfg.httpHost = "127.0.0.1"
|
|
vCfg.socksHost = "127.0.0.1"
|
|
vCfg.httpPort = String(bindPort)
|
|
vCfg.socksPort = String(bindPort - 1)
|
|
jsonText = vCfg.combineManual()
|
|
|
|
do {
|
|
let jsonFilePath = URL(fileURLWithPath: jsonFile)
|
|
if FileManager.default.fileExists(atPath: jsonFile) {
|
|
try FileManager.default.removeItem(at: jsonFilePath)
|
|
}
|
|
try jsonText.write(to: jsonFilePath, atomically: true, encoding: .utf8)
|
|
} catch {
|
|
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
|
|
if let fetchStartDate = fetchStartDate, let responseEndDate = responseEndDate {
|
|
let requestDuration = responseEndDate.timeIntervalSince(fetchStartDate)
|
|
let pingTs = Int(requestDuration * 100)
|
|
NSLog("PingResult: fetchStartDate=\(fetchStartDate), responseEndDate=\(responseEndDate), requestDuration=\(requestDuration), pingTs=\(pingTs)")
|
|
item.speed = String(format: "%dms", pingTs)
|
|
}
|
|
}
|
|
item.store()
|
|
pingEnd()
|
|
}
|
|
|
|
func pingEnd() {
|
|
NSLog("ping end: \(item.remark) - \(item.speed)")
|
|
do {
|
|
if process.isRunning {
|
|
process.interrupt()
|
|
process.terminate()
|
|
process.waitUntilExit()
|
|
}
|
|
try FileManager.default.removeItem(at: URL(fileURLWithPath: jsonFile))
|
|
} catch {
|
|
NSLog("remove ping config error: \(error)")
|
|
}
|
|
}
|
|
}
|
|
|
|
class PingCurrent: NSObject, URLSessionDataDelegate {
|
|
static let shared = PingCurrent()
|
|
|
|
var item: V2rayItem?
|
|
var tryPing = 0
|
|
var inPingProcess = false
|
|
|
|
private override init() {
|
|
super.init()
|
|
}
|
|
|
|
func startPing(with item: V2rayItem) {
|
|
guard !inPingProcess else {
|
|
return
|
|
}
|
|
self.item = item
|
|
tryPing = 0
|
|
doPing()
|
|
}
|
|
|
|
private func doPing() {
|
|
guard let item = item else { return }
|
|
|
|
inPingProcess = true
|
|
Task {
|
|
do {
|
|
try await _doPing()
|
|
pingCurrentEnd()
|
|
} catch {
|
|
NSLog("doPing error: \(error)")
|
|
inPingProcess = false
|
|
}
|
|
}
|
|
}
|
|
|
|
private func _doPing() async throws {
|
|
usleep(useconds_t(1 * second)) // 1 second
|
|
guard let item = item else { return }
|
|
NSLog("PingCurrent start: try=\(tryPing), item=\(item.remark)")
|
|
|
|
let config = getProxyUrlSessionConfigure()
|
|
config.timeoutIntervalForRequest = 3
|
|
|
|
let session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
|
|
tryPing += 1
|
|
|
|
do {
|
|
let (_, _) = try await session.data(for: URLRequest(url: pingURL))
|
|
} catch {
|
|
NSLog("save json file fail: \(error)")
|
|
}
|
|
}
|
|
|
|
func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) {
|
|
guard let item = item else { return }
|
|
|
|
item.speed = "-1ms"
|
|
if let transactionMetrics = metrics.transactionMetrics.first,
|
|
let fetchStartDate = transactionMetrics.fetchStartDate,
|
|
let responseEndDate = transactionMetrics.responseEndDate {
|
|
let requestDuration = responseEndDate.timeIntervalSince(fetchStartDate)
|
|
let pingTs = Int(requestDuration * 100)
|
|
print("PingCurrent: fetchStartDate=\(fetchStartDate), responseEndDate=\(responseEndDate), requestDuration=\(requestDuration), pingTs=\(pingTs)")
|
|
item.speed = "\(pingTs)ms"
|
|
}
|
|
item.store()
|
|
}
|
|
|
|
private func pingCurrentEnd() {
|
|
guard let item = item else { return }
|
|
|
|
NSLog("PingCurrent end: try=\(tryPing), item=\(item.remark)")
|
|
if item.speed == "-1ms" {
|
|
if tryPing < 3 {
|
|
inPingProcess = false
|
|
DispatchQueue.global().asyncAfter(deadline: .now() + 3) {
|
|
self.doPing()
|
|
}
|
|
} else {
|
|
chooseNewServer()
|
|
}
|
|
} else {
|
|
inPingProcess = false
|
|
menuController.showServers()
|
|
}
|
|
}
|
|
|
|
private func chooseNewServer() {
|
|
guard let item = item else {
|
|
inPingProcess = false
|
|
return
|
|
}
|
|
|
|
guard UserDefaults.getBool(forKey: .autoSelectFastestServer) else {
|
|
NSLog(" - choose new server: disabled")
|
|
inPingProcess = false
|
|
return
|
|
}
|
|
|
|
let serverList = V2rayServer.all()
|
|
guard serverList.count > 1 else {
|
|
inPingProcess = false
|
|
return
|
|
}
|
|
|
|
var pingedSvrs = [String: Int]()
|
|
var allSvrs = [String]()
|
|
|
|
for svr in serverList where svr.name != item.name {
|
|
allSvrs.append(svr.name)
|
|
if svr.isValid && svr.speed != "-1ms" {
|
|
let speed = svr.speed.replacingOccurrences(of: "ms", with: "")
|
|
if let speedInt = Int(speed) {
|
|
pingedSvrs[svr.name] = speedInt
|
|
}
|
|
}
|
|
}
|
|
|
|
let newSvrName: String
|
|
if let fastestSvr = pingedSvrs.sorted(by: { $0.value < $1.value }).first {
|
|
newSvrName = fastestSvr.key
|
|
} else if let randomSvr = allSvrs.randomElement() {
|
|
newSvrName = randomSvr
|
|
} else {
|
|
inPingProcess = false
|
|
return
|
|
}
|
|
|
|
NSLog(" - choose new server: \(newSvrName)")
|
|
UserDefaults.set(forKey: .v2rayCurrentServerName, value: newSvrName)
|
|
V2rayLaunch.restartV2ray()
|
|
inPingProcess = false
|
|
}
|
|
}
|