ios - Enlace UDP Socket a IP celular
swift sockets (1)
Intento crear un cliente iOS que envíe datos a un servidor en un socket UDP a través de la comunicación celular del dispositivo.
Siguiente ¿IOS admite conexiones wifi simultáneas y 3g / 4g? enlace a iOS Multipath BSD Sockets Test , intenté implementar la solución en Swift 3, enumerar las interfaces de red en el dispositivo, identificar la interfaz celular (como se sugiere en Swift - Obtener la dirección IP del dispositivo ), crear un socket UDP y vincular al sockaddr recuperado de la interfaz.
La implementación de la programación de socket en Swift se realizó siguiendo ejemplos de la Programación de Socket en Swift: Parte 1 - getaddrinfo y siguientes publicaciones.
Desafortunadamente recibí Operación no permitida cuando trato de enviar datos en el socket, así que en lugar de eso he intentado crear el socket y vincularlo a los datos de getaddrinfo llamados en un puerto designado (5555).
Eso tampoco funcionó. Lo interesante es que, al tratar de entender qué es lo que está mal, creé una aplicación de prueba para ambos métodos, y cuando se probaron 1000 creaciones consecutivas-> bind-> send-> close, aproximadamente 3-5 de los intentos enviaron los datos sin el error en ninguno de los métodos.
Huelga decir que esto fue probado en un iPhone real.
Absolutamente perdido, agradecería cualquier consejo con respecto a esto.
Código implementado en una clase "SocketManager" estática ( edit: tamaño de asignación sockaddr fijo)
// 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)
}
En 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($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
}
Editar: Se corrigió el error de orden de bytes de red en la creación del destino sockadd_in
.
Bien, encontré el problema: primero, como notó Martin, echo de UnsafeMutablePointer
asignación UnsafeMutablePointer
utilizada ya que tomé los parámetros de capacity/count
como bytes.
Esto también se hizo cuando sendData
para los detalles del servidor en la función sendData
( var target = UnsafeMutablePointer<sockaddr_in>.allocate(capacity: MemoryLayout<sockaddr_in>.size
en oposición a var target = UnsafeMutablePointer<sockaddr_in>.allocate(capacity: 1
) .
Después de arreglar esto, comencé a obtener mejores resultados (alrededor de 16 de cada 1000 envíos pasaron), pero obviamente no fue suficiente. Encontré Envíe un mensaje usando UDP en Swift 3 , y decidí cambiar el uso de sockaddr_in
a 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))
, todo funciona.
Todavía estoy desconcertado sobre por qué usar memoria insegura con esta estructura no funcionó.
Otra cosa: volví a mover este código a mi aplicación real, tratando de enlazar el socket a mi propio addrinfo
través de getaddrinfo
falla constantemente con No se puede asignar la dirección solicitada , usando el que obtengo de las interfaces enumeradas funciona, pero recibo un montón de No espacio de búfer disponible errores (algo para otra investigación :).
En el código de prueba, ambos métodos de enlace (enumerados & getaddrinfo
) funcionan bien.
Se sendData
función 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
}