将 UDP 套接字绑定到蜂窝 IP

Binding UDP Socket to Cellular IP

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

正在关注IOS是否支持同步 wifi 和 3g/4g 连接? link 到 iOS Multipath BSD Sockets Test, I've tried implementing the solution in Swift 3, that is enumerate network interfaces in the device, identifying the Cellular interface (as suggested in ),创建一个 UDP 套接字并将其绑定到从接口检索到的 sockaddr

Swift 中套接字编程的实现是通过遵循 Socket Programming in Swift: Part 1 - getaddrinfo 和后续帖子中的示例完成的。

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

那也没有成功。 有趣的是,在试图理解问题所在的同时,我为这两种方法创建了一个测试应用程序,当测试 1000 次连续的创建->绑定->发送->关闭时,实际上大约有 3-5 次尝试 did 两种方法都没有错误地发送数据。

不用说这是在实际 iPhone.

上测试的

很茫然,如果有任何相关建议,我将不胜感激。

静态实现的代码 "SocketManager" class(编辑: 固定 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: { [=10=].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) {
            [=10=].withMemoryRebound(to: sockaddr.self, capacity: 1) {
                let bytesSent = sendto(withSocketDescriptor, rawPtr, data.count, 0, [=10=], 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) 
}

在 ViewController:

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([=11=]), 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

}

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

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

当我在 sendData 函数中为服务器详细信息分配 sockaddr_in 时,也是这样做的(var target = UnsafeMutablePointer<sockaddr_in>.allocate(capacity: MemoryLayout<sockaddr_in>.size 而不是 var target = UnsafeMutablePointer<sockaddr_in>.allocate(capacity: 1)。

修复此问题后,我开始获得更好的结果(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)),一切正常。

我仍然不明白为什么对这个结构使用不安全内存不起作用。

另一件事:我将此代码移回我的实际应用程序,试图通过 getaddrinfo 将套接字绑定到我自己的 addrinfo 不断失败,并显示 无法分配请求address,使用我从枚举接口中获得的那个可以工作,但我收到很多 No buffer space available 错误(另一项研究的东西 :).

在测试代码中,两种绑定方法(枚举和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) {
            [=10=].withMemoryRebound(to: sockaddr.self, capacity: 1) {
                let bytesSent = sendto(withSocketDescriptor, rawPtr, data.count, 0, [=10=], 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        
}