2017-08-06 53 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