【问题标题】:Binding UDP Socket to Cellular IP将 UDP 套接字绑定到蜂窝 IP
【发布时间】:2017-10-22 15:54:14
【问题描述】:

我正在尝试创建一个 iOS 客户端,通过设备的蜂窝通信将数据发送到 UDP 套接字上的服务器。

Does IOS support simultaneous wifi and 3g/4g connections? 链接到iOS Multipath BSD Sockets Test之后,我尝试在Swift 3中实现解决方案,即枚举设备中的网络接口,识别蜂窝接口(如Swift - Get device's IP Address中所建议的那样),创建一个UDP套接字并将其绑定到从接口检索到的 sockaddr

按照Socket Programming in Swift: Part 1 - getaddrinfo 中的示例和以下帖子,在 Swift 中实现套接字编程。

不幸的是,我在尝试在套接字上发送数据时收到 Operation not allowed,因此我尝试创建套接字并将其绑定到来自 getaddrinfo 的数据调用在指定端口 (5555) 上。

那也没有成功。 有趣的是,在试图了解问题所在的同时,我为这两种方法创建了一个测试应用程序,当测试 1000 次连续的 create->bind->send->close 时,实际上大约有 3-5 次尝试确实做到了 在任一方法上发送数据均不会出错。

不用说这是在实际 iPhone 上测试的。

很茫然,我很感激有关此的任何建议。

在静态“SocketManager”类中实现的代码(编辑:固定 sockaddr 分配大小)

// 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

}

【问题讨论】:

  • 那个@brianhaak 有几篇帖子声称即使打开了 wifi 也可以连接到移动数据。但我无法通过任何文件自己确认这一点。这个想法本身是反直觉的。如果我是 iphone 用户,并且我打开了 wifi,我希望每个应用程序都使用 wifi 连接。我会认为应用偷偷使用移动数据是一个安全问题。
  • @Paul 这个特定的应用程序不会偷偷使用移动数据 - 用户将提前了解数据使用情况。至于 brianhaak 的帖子,他指的是使用原生 C 代码。虽然这应该可行(iOS 是一个类 Unix 操作系统),但我此时最感兴趣的是 Swift 实现:据我了解,这些特定的 Swift 网络功能是本机代码的包装器,因此我是对我收到的错误感到困惑,并且很想知道我是否存在实施错误,或者其他原因。
  • 我目前没有设置来测试您的代码,但请注意,在您的代码中将套接字地址“复制”到分配的内存中,capacity/count 参数表示 项目的数量, 不是字节数。
  • @MartinR 我知道我的代码目前不支持 IPv6,这是由设计完成的,因为我的测试设备目前仅输出 IPv4 的接口(IPv6 支持将在实际添加后添加问题将得到解决)。至于capacity/count,我假设您指的是initialize?谢谢,我会解决的
  • clt = UnsafeMutablePointer&lt;sockaddr&gt;.allocate(capacity: Int(ifa_addr_Value.sa_len))ifa_addr_Value.sa_len 类型为sockaddr 的项目分配内存,即ifa_addr_Value.sa_len * sizeof(sockaddr) 字节。

标签: ios swift sockets udp getaddrinfo


【解决方案1】:

编辑:修复了创建目标sockadd_in时的网络字节顺序错误。

好的,找到问题了: 首先,正如 Martin 所说,我错过了使用 UnsafeMutablePointer 分配,因为我将 capacity/count 参数作为字节。

当我在sendData 函数(var target = UnsafeMutablePointer&lt;sockaddr_in&gt;.allocate(capacity: MemoryLayout&lt;sockaddr_in&gt;.size 而不是var target = UnsafeMutablePointer&lt;sockaddr_in&gt;.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&lt;sockaddr_in&gt;.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)),一切正常。

我仍然对为什么在这个结构中使用 Unsafe Memory 不起作用感到困惑。

另一件事:我将此代码移回我的实际应用程序,尝试通过getaddrinfo 将套接字绑定到我自己的addrinfo 经常失败,并使用 无法分配请求的地址,使用我从枚举接口得到的一个工作正常,但我收到很多 没有可用的缓冲区空间 错误(另一项研究:)。

在测试代码中,两种绑定方法(枚举和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        
}

【讨论】:

  • 嘿@Cha.OS 你有没有想过如何解决“无法分配请求的地址”问题?我每次尝试向服务器发送消息时都会看到这种情况。
  • 嗨@Eric,很遗憾不是,因为我选择的解决方案是基于将套接字绑定到枚举接口,而这个错误没有发生。我建议检查所有参数是否正确使用网络字节顺序发送,我有几个地方没有强制执行这导致我遇到的一些网络错误(例如,请参阅我的答案中的最新编辑)。
  • 嘿@Cha.OS,感谢您的回复。枚举接口是什么意思?我在您的原始帖子中没有看到提到这一点,所以想知道您如何绑定与您从 getifaddrs 获得的界面不同的界面?
  • @Eric,我想澄清一下:我将我的套接字绑定到使用getifaddrs 检索到的同一接口,有两种不同的方式,或者直接使用sockaddr(称为clt ) 或通过getaddrinfo 为IP 和端口手动创建sockaddr 结构(请参阅我的原始帖子中的bindSocket 并根据useCltAddr 的值遵循不同的流程)。您询问的特定错误仅在我尝试将套接字绑定到手动创建的addrinfo 时发生,但是当我将套接字绑定到使用getifaddrs 检索的sockaddr 结构时不会发生。
猜你喜欢
  • 2023-02-12
  • 1970-01-01
  • 2012-05-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多