4 Replies
      Latest reply on Jan 15, 2019 9:45 PM by jon_tt
      neolu Level 1 Level 1 (0 points)

        hi all

         

        How to send only one UDP packet with swift playgrounds?

         

        Thanks,

        • Re: How to send only one UDP packet with swift playgrounds?
          eskimo Apple Staff Apple Staff (12,025 points)

          The challenge here is not the Swift playground but sending UDP in general.  Apple platforms do not have a nice, high-level API for UDP and thus you have to use BSD Sockets.  Using BSD Sockets from Swift is quite challenging.  You can find some hints on that topic in this thread.

          On the UDP side of things, a good place to start is the UDPEcho sample code.  It is in Objective-C, but if you have any questions about how to convert something to Swift I’d be happy to answer them.

          Even with all of this info, it’s still a challenge assembling things into a coherent whole.  Pasted in below is an example I just created out of the stuff I discussed above.

          Share and Enjoy

          Quinn “The Eskimo!”
          Apple Developer Relations, Developer Technical Support, Core OS/Hardware
          let myEmail = "eskimo" + "1" + "@apple.com"


          import Foundation
          
          extension sockaddr_storage {
          
              // not actually in Socket API Helper, but an 'obvious' extension
          
              init(sa: UnsafeMutablePointer<sockaddr>, saLen: socklen_t) {
                  var ss = sockaddr_storage()
                  withUnsafeMutableBytes(of: &ss) { ssPtr -> Void in
                      let addrBuf = UnsafeRawBufferPointer(start: sa, count: Int(saLen))
                      assert(addrBuf.count <= MemoryLayout<sockaddr_storage>.size)
                      ssPtr.copyMemory(from: addrBuf)
                  }
                  self = ss
              }
          
              // from Socket API Helper
          
              static func fromSockAddr<ReturnType>(_ body: (_ sa: UnsafeMutablePointer<sockaddr>, _ saLen: inout socklen_t) throws -> ReturnType) rethrows -> (ReturnType, sockaddr_storage) {
                  // We need a mutable `sockaddr_storage` so that we can pass it to `withUnsafePointer(to:_:)`.
                  var ss = sockaddr_storage()
                  // Similarly, we need a mutable copy of our length for the benefit of `saLen`.
                  var saLen = socklen_t(MemoryLayout<sockaddr_storage>.size)
                  let result = try withUnsafeMutablePointer(to: &ss) {
                      try $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
                          try body($0, &saLen)
                      }
                  }
                  return (result, ss)
              }
          
              // from Socket API Helper
          
              func withSockAddr<ReturnType>(_ body: (_ sa: UnsafePointer<sockaddr>, _ saLen: socklen_t) throws -> ReturnType) rethrows -> ReturnType {
                  // We need to create a mutable copy of `self` so that we can pass it to `withUnsafePointer(to:_:)`.
                  var ss = self
                  return try withUnsafePointer(to: &ss) {
                      try $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
                          try body($0, socklen_t(self.ss_len))
                      }
                  }
              }
          }
          
          func addressesFor(host: String, port: Int) throws -> [sockaddr_storage] {
              var hints = addrinfo()
              hints.ai_socktype = SOCK_DGRAM
              var addrList: UnsafeMutablePointer<addrinfo>? = nil
              let err = getaddrinfo(host, "\(port)", &hints, &addrList)
              guard err == 0, let start = addrList else {
                  throw NSError(domain: NSURLErrorDomain, code: NSURLErrorCannotFindHost, userInfo: nil)
              }
              defer { free(addrList) }
              return sequence(first: start, next: { $0.pointee.ai_next} ).map { (addr) -> sockaddr_storage in
                  sockaddr_storage(sa: addr.pointee.ai_addr, saLen: addr.pointee.ai_addrlen)
              }
          }
          
          func sendExample() {
              guard let addresses = try? addressesFor(host: "fluffy.local.", port: 12345) else {
                  print("host not found")
                  return
              }
              if addresses.count != 1 {
                  print("host ambiguous; using the first one")
              }
              let address = addresses[0]
              let fd = socket(Int32(address.ss_family), SOCK_DGRAM, 0)
              guard fd >= 0 else {
                  print("`socket` failed`")
                  return
              }
              defer {
                  let junk = close(fd)
                  assert(junk == 0)
              }
              let message = "Hello Cruel World!\r\n\(Date())\r\n".data(using: .utf8)!
              let messageCount = message.count
              let sendResult = message.withUnsafeBytes { (messagePtr: UnsafePointer<UInt8>) -> Int in
                  return address.withSockAddr { (sa, saLen) -> Int in
                      return sendto(fd, messagePtr, messageCount, 0, sa, saLen)
                  }
              }
              guard sendResult >= 0 else {
                  print("send failed")
                  return
              }
              print("success")
          }
          
          sendExample()

            • Re: How to send only one UDP packet with swift playgrounds?
              jon_tt Level 1 Level 1 (0 points)

              I’ve found this code really useful for an IoT device I’m building, but being new to swift I couldn’t figure out how to create a ReadExample function. I tried modifying your SendExample but using “recvfrom“ but just kept getting compiler errors I couldn’t solve so I was wondering if you could possibly post an addition to this code showing a ReadExample function as well. It would be great help, thanks.

                • Re: How to send only one UDP packet with swift playgrounds?
                  eskimo Apple Staff Apple Staff (12,025 points)

                  These days I would use Network framework rather than BSD Sockets.  It makes this sort of thing much easier.  Pasted in below is an example playground that shows this is action.  Use nc -u -l 12345 for the server side.

                  Share and Enjoy

                  Quinn “The Eskimo!”
                  Apple Developer Relations, Developer Technical Support, Core OS/Hardware
                  let myEmail = "eskimo" + "1" + "@apple.com"


                  import Network
                  import Foundation
                  import PlaygroundSupport
                  
                  class Main {
                  
                      init() {
                          let connection = NWConnection(host: "sully.local.", port: 12345, using: .udp)
                          self.connection = connection
                      }
                  
                      let connection: NWConnection
                  
                      func start() {
                          // Start the connection.
                          connection.stateUpdateHandler = self.stateDidChange(to:)
                          connection.start(queue: .main)
                          self.setupReceive(connection)
                          // Start the send timer.
                          let sendTimer = DispatchSource.makeTimerSource(queue: .main)
                          sendTimer.setEventHandler(handler: self.send)
                          sendTimer.schedule(deadline: .now(), repeating: 1.0)
                          sendTimer.resume()
                          self.sendTimer = sendTimer
                      }
                  
                      var sendTimer: DispatchSourceTimer?
                  
                      func stateDidChange(to state: NWConnection.State) {
                          switch state {
                          case .setup:
                              break
                          case .waiting(let error):
                              self.connectionDidFail(error: error)
                          case .preparing:
                              print("Preparing")
                          case .ready:
                              print("Connected")
                          case .failed(let error):
                              self.connectionDidFail(error: error)
                          case .cancelled:
                              break
                          }
                      }
                  
                      func send() {
                          let messageID = UUID()
                          self.connection.send(content: "\(messageID)\r\n".data(using: .utf8)!, completion: .contentProcessed({ sendError in
                              if let error = sendError {
                                  self.connectionDidFail(error: error)
                              } else {
                                  print("Did send, messageID: \(messageID)")
                              }
                          }))
                      }
                  
                      func setupReceive(_ connection: NWConnection) {
                          connection.receiveMessage { (data, _, isComplete, error) in
                              if let data = data, !data.isEmpty {
                                  print("Did receive, size: \(data.count)")
                              }
                              if let error = error {
                                  self.connectionDidFail(error: error)
                                  return
                              }
                              self.setupReceive(connection)
                          }
                      }
                  
                      func connectionDidFail(error: Error) {
                          print("Failed, error: \(error)")
                          if connection.stateUpdateHandler != nil {
                              self.connection.stateUpdateHandler = nil
                              connection.cancel()
                          }
                          exit(0)
                      }
                  }
                  
                  PlaygroundPage.current.needsIndefiniteExecution = true
                  let m = Main()
                  m.start()

                    • Re: How to send only one UDP packet with swift playgrounds?
                      jon_tt Level 1 Level 1 (0 points)

                      Thanks for this code. Unfortunately, I’m having problems with it. Firstly I had to add some if #available(iOSApplicationExtension 12.0, *) lines (but maybe that’s normal).

                       

                      The main issue I’m having is with receiving still. If I use the nc -u -l on a Mac the playground code works fine. But sending and receiving to and from my IoT device and code causes issues.

                       

                      I can send data to the IoT device and it receives fine, but if I send data from the device to the playground app nothing is received. Now, I know you’ll say it’s my code on the IoT, but if I run an echo server running in playground the IoT sends and receives fine.

                       

                      Any idea what could be causing this inconsistency? Is there any kind of special header that your code is expecting? The receive message code isn’t even triggering and then failing. It never seems to trigger at all. Is there anything in the new framework that would error check the message I’m sending and screen it before your code even gets it? I’ve tried sending different types of messages from the IoT, and even just echoed back what your code is sending out, but nothing is ever received. Whereas the Echo UDP playground code echoes back fine.