UDP Client Example in Swift 3.1
UDP stands for User Datagram Protocol. It is one of many protocols in electronic communications for send information. It is ideal for small amounts of information and/or situations where relieability is not a priority, and simplicity. For more information use Wikipedia to get started.
Most simple examples for writing programs using UDP online are in C. But, I wanted to do my example in Swift 3+. Xcode has the ability to translate C-code and Objective-C code into Swift if the appropriate header files are added to a project via an Objective-C Bridging Header. So, adding the needed C-header files to my bridging header, I set out to write a UDP Client class. This was done quick and dirty, and there may be improvements needed, but I got it to work for now and I am pretty stoked! My example project is based on the C example found here.
UDPClient.swift:
import Foundation
/**
The enumeration defines errors that are throw UDPClient methods
````
noSocket Error thrown if a socket file descriptor could not be created
bindSocket Error thrown if the bind process fails for the socket file descriptor
badAddress Error thrown if the address string given is not proper
alreadyInProgress Error thrown if beginOperation() is called on a UPDClient object who's receive/send process currently running
setupForNonBlocking Error thrown if the init method fails to set the socket for non-blocking operation
threadLock Error thrown if the init method fails to obtain a valid thread lock
- parameter localizedDescription: Explanation of the current error
*/
enum UDPClientError: Int, LocalizedError {
case noSocket = -999
case bindSocket
case badAddress
case alreadyInProgress
case setupForNonBlocking
case threadLock
var localizedDescription: String {
switch self {
case .alreadyInProgress:
return "operation in progress"
case .badAddress:
return "Address string given is not valid"
case .bindSocket:
return "Could not bind socket"
case .noSocket:
return "Could not obtain socket"
case .setupForNonBlocking:
return "Could not setup socket for non-blocking operation"
case .threadLock:
return "Could not obtain thread lock"
}
}
}
/**
This class defines objects that can be created to send and receive UDP messages
*/
class UDPClient {
// MARK: - properties
// socket file descriptor
private var mySocket: Int32 = 0
// socket address for the client
private var myAddress = sockaddr_in()
// socket address for the target client
private var otherAddress = sockaddr_in()
// Holds messages received and read by the Rx/Tx thread method
private var receiveQueue = [String]()
// Holds messages to be transmitted in the Rx/Tx thread method
private var sendQueue = [String]()
// flag controls the execute loop with the Rx/Tx thread method
private var okToRun = false
// thread mutex resource
private var threadLock = pthread_mutex_t()
// limit for sent and received message
private let BufferLimit = 1024
// name for this client
let name: String
// MARK: - initialization and de-initialization
/**
Designated initializater
- parameters:
- name: Client's name
- port: The client's port
- otherIPAddress: The IPv4 address of the recipient's machine
- otherPort: The port the recipient will be listening for the client's message
- throws:A UDPClientError on failure
*/
init(name: String, port: UInt16, otherIPAddress: String, otherPort: UInt16) throws {
// assign
self.name = name
// get a socket file descriptor
mySocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
// no socket
if mySocket == -1 {
throw UDPClientError.noSocket
}
// set the socket as non-blocking, else throw error
if fcntl(mySocket, F_SETFL, O_NONBLOCK) == -1 {
throw UDPClientError.setupForNonBlocking
}
// configure the client's socket address
myAddress.sin_family = sa_family_t(AF_INET)
myAddress.sin_port = in_port_t(port)
myAddress.sin_addr.s_addr = in_addr_t(INADDR_ANY)
// bind the socket to the socket address
let retCode = withUnsafeMutablePointer(to: &myAddress) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
bind(mySocket, UnsafeMutablePointer<sockaddr>($0), socklen_t(MemoryLayout<sockaddr_in>.size))
}
}
// throw error if the socket could not be bound to the socket address
if retCode == -1 {
throw UDPClientError.bindSocket
}
// configure recipient's address
otherAddress.sin_family = sa_family_t(AF_INET)
otherAddress.sin_port = in_port_t(otherPort)
// need a buffer
var buffer: [Int8] = Array(otherIPAddress.utf8CString)
// copy address, throw error on failure
if inet_aton(&buffer, &otherAddress.sin_addr) == 0 {
throw UDPClientError.badAddress
}
// obtain mutex and throw error if we fail to obtain one
if pthread_mutex_init(&threadLock, nil) != 0 {
throw UDPClientError.threadLock
}
}
/**
Necessary cleanup performed in this method
*/
deinit {
pthread_mutex_unlock(&threadLock)
pthread_mutex_destroy(&threadLock)
close(mySocket)
}
// MARK: - methods
/**
This method kicks off the receive and transmit thread.
- throws: A UDPClientError if the method is called and the receive/transmit operation is already in progress
*/
func beginOperation() throws {
// Rx/Tx thread process already running
if okToRun {
throw UDPClientError.alreadyInProgress
}
// set the flag, detach and start Rx/Tx thread
okToRun = true
_ = Thread.detachNewThreadSelector(#selector(process), toTarget: self, with: nil)
}
/**
This method stops the receive and transmit operation for the client
*/
func endOperation() {
okToRun = false
}
/**
The method adds the desired message to the send queue
- parameter message: Desired message to be transmitted
*/
func send(message: String) {
pthread_mutex_lock(&threadLock)
sendQueue.append(message)
pthread_mutex_unlock(&threadLock)
}
/**
This method retrieves one message from the receive queue. Message are retreived in a first in, first out manner.
- returns: The message at the front of the receive queue, or nil if the receive queue is empty
*/
func getMessage() -> String? {
var message: String?
pthread_mutex_lock(&threadLock)
// do we have any messages?
if receiveQueue.isEmpty == false {
message = receiveQueue.remove(at: 0)
}
pthread_mutex_unlock(&threadLock)
return message
}
// MARK: - private thread process
/**
The method is run in a separate dedicated thread. This method contains a loop that handles both the receive
and transmit of a message for the client
*/
@objc private func process() {
// buffers for Rx and Tx
var sendBuffer = [UInt8](repeating: 0, count: BufferLimit)
var receiveBuffer = [UInt8](repeating: 0, count: BufferLimit)
// struct size, needed for revcfrom() and sendto() functions
var slen = socklen_t(MemoryLayout<sockaddr_in>.size)
print("Process running for " + name)
// holds the number of bytes read and received
var bytesRead = 0
var bytesSent = 0
/* MAIN PROCESS LOOP */
while okToRun {
// are there any messages to send?
if sendQueue.isEmpty == false {
// get a single message, from the front
pthread_mutex_lock(&threadLock)
var str = sendQueue.remove(at: 0)
pthread_mutex_unlock(&threadLock)
// message cannot be longer than buffer limit
if str.characters.count > BufferLimit {
let index = str.index(str.startIndex, offsetBy: BufferLimit)
str = str.substring(to: index)
sendBuffer.replaceSubrange(0 ..< BufferLimit, with: str.utf8)
}
else {
sendBuffer.replaceSubrange(0 ..< str.characters.count, with: str.utf8)
}
// send message to recipient
bytesSent = withUnsafeMutablePointer(to: &otherAddress) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
sendto(mySocket, sendBuffer, str.characters.count, 0, $0, slen)
}
}
// successfully sent message
if bytesSent != -1 {
print("\(name): bytes sent = \(bytesSent)")
}
// reset buffer
sendBuffer = [UInt8](repeating: 0, count: BufferLimit)
}
// read message, can be no longer than buffer limit
bytesRead = withUnsafeMutablePointer(to: &otherAddress) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
recvfrom(mySocket, &receiveBuffer, BufferLimit, 0, $0, &slen)
}
}
// successfully read message
if bytesRead != -1 {
print("\(name): bytes read = \(bytesRead)")
// add received message to queue
pthread_mutex_lock(&threadLock)
receiveQueue.append(String(bytes: receiveBuffer, encoding: .utf8)!)
pthread_mutex_unlock(&threadLock)
slen = socklen_t(MemoryLayout<sockaddr_in>.size)
// reset buffer
receiveBuffer = [UInt8](repeating: 0, count: BufferLimit)
}
// reset byte counts
bytesRead = 0
bytesSent = 0
} // end processing loop
} // end process
}
UDPClient-Bridging-Header.h:
#ifndef UDP_Bridging_Header_h
#define UDP_Bridging_Header_h
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <pthread.h>
#endif /* UDP_Bridging_Header_h */
Main.swift for testing out our class:
import Foundation
var client_1: UDPClient?
var client_2: UDPClient?
// attempt to create clients, catch errors
do {
try client_1 = UDPClient(name: "Client A", port: 9990, otherIPAddress: "192.112.171.32", otherPort: 9992)
}
catch {
print(error.localizedDescription)
}
do {
try client_2 = UDPClient(name: "Client B", port: 9992, otherIPAddress: "192.112.171.32", otherPort: 9990)
}
catch {
print(error.localizedDescription)
}
// if both clients created successfully
if client_1 != nil && client_2 != nil {
client_1!.send(message: "Finally got this thing to work")
client_1!.send(message: "Feels good don't it man!")
client_1!.send(message: "...and one more for good measure.")
// attempt to begin Rx/Tx operation, catch error
do {
try client_1!.beginOperation()
}
catch {
print(error.localizedDescription)
}
do {
try client_2!.beginOperation()
}
catch {
print(error.localizedDescription)
}
// hold up...
Thread.sleep(forTimeInterval: 2.0)
// print first message
if let msg = client_2!.getMessage() {
print("got something: " + msg)
}
// hold up again...
Thread.sleep(forTimeInterval: 2.0)
// print another message...
if let msg = client_2!.getMessage() {
print("got something else: " + msg)
}
// hold up just once more...
Thread.sleep(forTimeInterval: 2.0)
// last message
if let msg = client_2!.getMessage() {
print("got something last message: " + msg)
}
}
Running main.swift on my machine I get the following in the console view:

This project was done using a late 2016 MacBook Pro and Xcode 8.3.2 (8E2002).