2017-08-06 163 views
3

我試圖創建一個iOS客戶端,通過設備的蜂窩通信將數據發送到UDP套接字上的服務器。將UDP套接字綁定到蜂窩IP

Does IOS support simultaneous wifi and 3g/4g connections? 連結iOS Multipath BSD Sockets Test,我試圖實現在夫特3中的溶液,即枚舉網絡接口設備中,識別蜂窩接口(如在Swift - Get device's IP Address建議的),創建UDP套接字並將其綁定到sockaddr從界面中檢索。

在Swift中實現套接字編程是通過以下示例從Socket Programming in Swift: Part 1 - getaddrinfo和以下帖子完成的。

不幸的是,我收到操作試圖發送套接字上的數據時,不允許,所以不是我試圖創建套接字,並從綁定到數據的getaddrinfo稱爲指定端口(5555)上。

這也沒有辦法。 有趣的是,在試圖瞭解什麼是錯誤的時候,我爲這兩種方法創建了一個測試應用程序,並且當連續1000次創建 - >綁定 - >發送 - >關閉測試時,大約有3-5次嘗試實際上是確實是在任一方法上發送沒有錯誤的數據。

不用說這是在實際的iPhone上測試的。

相當不知所措,我很感激任何關於此的建議。在靜態 「SocketManager」 類實現

碼(編輯:固定套接字地址分配大小)

// Return IP address String, port String & sockaddr of WWAN interface (pdp_ip0), or `nil` 
public static func getInterface() -> (String?, String?, UnsafeMutablePointer<sockaddr>?) { 
    var host : String? 
    var service : String? 

    // Get list of all interfaces on the local machine: 
    var ifaddr : UnsafeMutablePointer<ifaddrs>? 
    var clt : UnsafeMutablePointer<sockaddr>? 

    guard getifaddrs(&ifaddr) == 0 else { 
     return (nil, nil, clt) 
    } 
    guard let firstAddr = ifaddr else { 
     return (nil, nil, clt) 
    } 

    // For each interface ... 
    for ifptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) { 
     let interface = ifptr.pointee 
     let flags = Int32(ifptr.pointee.ifa_flags) 

     /// Check for running IPv4 interfaces. Skip the loopback interface. 
     if (flags & (IFF_UP|IFF_RUNNING|IFF_LOOPBACK)) == (IFF_UP|IFF_RUNNING) { 
      let addrFamily = interface.ifa_addr.pointee.sa_family 
      if addrFamily == UInt8(AF_INET) { //Interested in IPv4 for in particular case 

       // Check interface name: 
       let name = String(cString: interface.ifa_name) 
       print("interface name: \(name)") 
       if name.hasPrefix("pdp_ip") { //cellular interface 

        // Convert interface address to a human readable string: 
        let ifa_addr_Value = interface.ifa_addr.pointee 
        clt = UnsafeMutablePointer<sockaddr>.allocate(capacity: 1) 
        clt?.initialize(to: ifa_addr_Value, count: 1) 

        var hostnameBuffer = [CChar](repeating: 0, count: Int(NI_MAXHOST)) 
        var serviceBuffer = [CChar](repeating: 0, count: Int(NI_MAXSERV)) 
        getnameinfo(interface.ifa_addr, socklen_t(ifa_addr_Value.sa_len), 
          &hostnameBuffer, socklen_t(hostnameBuffer.count), 
          &serviceBuffer, 
          socklen_t(serviceBuffer.count), 
          NI_NUMERICHOST | NI_NUMERICSERV) 
        host = String(cString: hostnameBuffer) 
        if let host = host { 
         print("found host \(String(describing: host))") 
        } 

        service = String(cString: serviceBuffer) 
        if let service = service { 
         print("found service \(String(describing: service))") 
        } 
        break; 
       } 
      } 
     } 
    } 
    freeifaddrs(ifaddr) 

    return (host, service, clt) 
} 

public static func bindSocket(ip: String, port : String, clt : UnsafeMutablePointer<sockaddr>, useCltAddr : Bool = false) -> Int32 { 

    print("binding socket for IP: \(ip):\(port) withCltAddr=\(useCltAddr)") 
    var hints = addrinfo(ai_flags: 0, 
          ai_family: AF_INET, 
          ai_socktype: SOCK_DGRAM, 
          ai_protocol: IPPROTO_UDP, 
          ai_addrlen: 0, 
          ai_canonname: nil, 
          ai_addr: nil, 
          ai_next: nil) 

    var connectionInfo : UnsafeMutablePointer<addrinfo>? = nil 

    let status = getaddrinfo(
      ip, 
      port, 
      &hints, 
      &connectionInfo) 
    if status != 0 { 
     var strError: String 
     if status == EAI_SYSTEM { 
      strError = String(validatingUTF8: strerror(errno)) ?? "Unknown error code" 
     } else { 
      strError = String(validatingUTF8: gai_strerror(status)) ?? "Unknown error code" 
     } 
     print(strError) 
     return -1 
    } 

    let socketDescriptor = useCltAddr ? socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) : socket(connectionInfo!.pointee.ai_family, connectionInfo!.pointee.ai_socktype, connectionInfo!.pointee.ai_protocol) 

    if socketDescriptor == -1 { 
     let strError = String(utf8String: strerror(errno)) ?? "Unknown error code" 
     let message = "Socket creation error \(errno) (\(strError))" 
     freeaddrinfo(connectionInfo) 
     print(message) 
     return -1 
    } 
    let res = useCltAddr ? bind(socketDescriptor, clt, socklen_t(clt.pointee.sa_len)) : bind(socketDescriptor, connectionInfo?.pointee.ai_addr, socklen_t((connectionInfo?.pointee.ai_addrlen)!)) 

    if res != 0 { 
     let strError = String(utf8String: strerror(errno)) ?? "Unknown error code" 
     let message = "Socket bind error \(errno) (\(strError))" 
     freeaddrinfo(connectionInfo) 
     close(socketDescriptor) 
     print(message) 
     return -1 
    } 
    freeaddrinfo(connectionInfo) 
    print("returned socket descriptor \(socketDescriptor)") 
    return socketDescriptor 
} 

//returns 0 for failure, 1 for success 
public static func sendData(toIP: String, onPort : String, withSocketDescriptor : Int32, data : Data) -> Int{ 

    print("sendData called for targetIP: \(toIP):\(onPort) with socket descriptor: \(withSocketDescriptor)") 
    var target = UnsafeMutablePointer<sockaddr_in>.allocate(capacity: MemoryLayout<sockaddr_in>.size) 

    target.pointee.sin_family = sa_family_t(AF_INET) 
    target.pointee.sin_addr.s_addr = inet_addr(toIP) 
    target.pointee.sin_port = in_port_t(onPort)! 

    var res = 0 
    data.withUnsafeBytes { (u8Ptr: UnsafePointer<UInt8>) in 
     let rawPtr = UnsafeRawPointer(u8Ptr) 
     withUnsafeMutablePointer(to: &target) { 
      $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { 
       let bytesSent = sendto(withSocketDescriptor, rawPtr, data.count, 0, $0, socklen_t(MemoryLayout.size(ofValue: target))) 
       if bytesSent > 0 { 
        print(" Sent \(bytesSent) bytes ") 
        res = 1 
       } 
       if bytesSent == -1 { 
        let strError = String(utf8String: strerror(errno)) ?? "Unknown error code" 
        let message = "Socket sendto error \(errno) (\(strError))" 
        print(message) 
       } 
      } 
     } 
    } 
    return res 
} 

public static func closeSocket(socketDescriptor : Int32, clt : UnsafeMutablePointer<sockaddr>) { 
    print("closing socket descriptor \(socketDescriptor)") 
    close(socketDescriptor) 
    clt.deinitialize() 
    clt.deallocate(capacity: 1) 
} 

在視圖控制器:

override func viewDidLoad() { 
    super.viewDidLoad() 
    var i = 0 
    for _ in 0..<1000 { 
     i += connectSendClose(withDescriptor: false) // change withDescriptor to switch socket create/bind method 
    } 
    print("Sent \(i) packets") 
} 

private func connectSendClose(withDescriptor : Bool) -> Int { 
    let interface = SocketManager.getInterface() 
    guard let ip = interface.0 else { 
     print("no relevant interface") 
     return 0 
    } 
    guard let clt = interface.2 else { 
     print("no addr") 
     return 0 
    } 
    let socketDescriptor = SocketManager.bindSocket(ip: ip, port: "5555", clt: clt, useCltAddr: withDescriptor) 
    if socketDescriptor == -1 { 
     print("faild to configure socket") 
     return 0 
    } 
    let serverIP = "59.122.442.9" //dummy IP, test was preformed on actual server 
    let serverPort = "10025" //dummy port, test was preformed on actual server 
    let input = 42.13 
    var value = input 
    let data = withUnsafePointer(to: &value) { 
     Data(bytes: UnsafePointer($0), count: MemoryLayout.size(ofValue: input)) 
    } 

    let res = SocketManager.sendData(toIP: serverIP, onPort: serverPort, withSocketDescriptor: socketDescriptor, data: data) 

    SocketManager.closeSocket(socketDescriptor: socketDescriptor, clt: clt) 
    return res 

} 
+0

這@brianhaak傢伙有幾個帖子聲稱可以連接到移動數據,即使無線網絡連接上。但我無法通過任何文檔來證實這一點。這個想法本身就是違背直覺的。如果我是一個iPhone用戶,並且我有wifi,我希望每個應用程序都使用WiFi連接。我會認爲應用程序偷偷使用移動數據是一個安全問題。 – Sal

+0

@保爾這個特定的應用程序將不會偷偷地使用移動數據 - 用戶將獲得有關數據使用情況的信息。 至於brianhaak的帖子,他指的是使用本機C代碼。 雖然這應該工作(iOS是一個類似Unix的操作系統),但我現在最感興趣的是Swift實現: 據我所知,這些特定的Swift網絡函數是本地代碼的包裝,因此我對於我收到的錯誤感到困惑,並且好奇地想知道我的結果是否存在執行錯誤,或者是否有其他工作出現。 –

+0

我沒有安裝程序來測試您的代碼,但請注意,在您的代碼中將套接字地址「複製」到分配的內存中,「capacity/count」參數表示*項的數量,*不是字節數。 –

回答

1

編輯:固定網絡字節順序錯誤創建目標sockadd_in

好的,發現問題: 首先,正如Martin所說,我錯過了使用UnsafeMutablePointer的配置,因爲我把capacity/count的參數作爲字節。

這也做,當我在sendData功能(var target = UnsafeMutablePointer<sockaddr_in>.allocate(capacity: MemoryLayout<sockaddr_in>.size而不是var target = UnsafeMutablePointer<sockaddr_in>.allocate(capacity: 1)用於服務器的詳細信息分配sockaddr_in

解決這個問題後,我開始獲得更好的結果(1000次發送中有16次發送通過),但顯然這還不夠。 我找到了Send a message using UDP in Swift 3,並決定改用sockaddr_in改爲var target = sockaddr_in(sin_len: __uint8_t(MemoryLayout<sockaddr_in>.size), sin_family: sa_family_t(AF_INET), sin_port: in_port_t(onPort)!, sin_addr: in_addr(s_addr: inet_addr(toIP)), sin_zero: (0,0,0,0, 0,0,0,0)),一切正常。

我仍然困惑,爲什麼使用不安全的內存與此結構不起作用。

另一件事:我搬到這個代碼回到我的實際應用程序,試圖將套接字綁定到我自己的addrinfo通過getaddrinfo不斷失敗,無法分配請求地址,使用一個我從列舉的接口工作搞定,但我收到很多沒有可用的緩衝空間錯誤(另一個研究的東西:)。

在測試代碼中,兩種綁定方法(枚舉爲& getaddrinfo)都正常工作。

固定sendData功能:

public static func sendData(toIP: String, onPort : String, withSocketDescriptor : Int32, data : Data) -> Int{ 

    print("sendData called for targetIP: \(toIP):\(onPort) with socket descriptor: \(withSocketDescriptor)")   
    var target = sockaddr_in(sin_len: __uint8_t(MemoryLayout<sockaddr_in>.size), sin_family: sa_family_t(AF_INET), sin_port: in_port_t(bigEndian: onPort)!, sin_addr: in_addr(s_addr: inet_addr(toIP)), sin_zero: (0,0,0,0, 0,0,0,0)) 

    var res = 0 
    data.withUnsafeBytes { (u8Ptr: UnsafePointer<UInt8>) in 
     let rawPtr = UnsafeRawPointer(u8Ptr) 
     withUnsafeMutablePointer(to: &target) { 
      $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { 
       let bytesSent = sendto(withSocketDescriptor, rawPtr, data.count, 0, $0, socklen_t(MemoryLayout.size(ofValue: target))) 
       if bytesSent > 0 { 
        print(" Sent \(bytesSent) bytes ") 
        res = 1 
       } 
       if bytesSent == -1 { 
        let strError = String(utf8String: strerror(errno)) ?? "Unknown error code" 
        let message = "Socket sendto error \(errno) (\(strError))" 
        print(message) 
       } 
      } 
     } 
    } 
    return res   
} 
+0

嘿@ Cha.OS你有沒有想過如何解決「無法分配請求的地址」問題?每次嘗試向服務器發送消息時,我都會看到這一點。 – Eric

+0

嗨@Eric,遺憾的是不是,因爲我選擇的解決方案是基於將套接字綁定到未發生此錯誤的枚舉接口。 我建議檢查所有參數是否正確發送網絡字節順序,我有幾個地方沒有強制執行此操作,導致我的一些網絡錯誤(請參閱我的答案中的最新編輯)。 –

+0

嘿@ Cha.OS,謝謝你的迴應。枚舉接口是什麼意思?我沒有在你的原始文章中看到提及,所以想知道你是如何與從'getifaddrs'獲得的不同界面綁定的? – Eric