Skip to content

Instantly share code, notes, and snippets.

@op183
Last active July 2, 2022 03:48
Show Gist options
  • Save op183/047500a2d982fd8840be8bc00abb9450 to your computer and use it in GitHub Desktop.
Save op183/047500a2d982fd8840be8bc00abb9450 to your computer and use it in GitHub Desktop.
Echo UDP Server written in pure Swift
import Darwin
import Dispatch
class EchoUDPServer {
// syncQueue help us print and read/write safely from our internal storage
// while running, the main queue is blocking with readLine()
private let syncQueue = DispatchQueue(label: "syncQueue")
// to be able maintain curren status of all DispatchSources
// we store all the information here
var serverSources:[Int32:DispatchSourceRead] = [:]
deinit {
// first stop the server !!
stop()
print("Echo UDP Server deinit")
}
func start() {
// create buffer for temporary c strings
var temp = [CChar](repeating: 0, count: 255)
// host name
gethostname(&temp, temp.count)
// create addrinfo based on hints
// if host name is nil or "" we can connect on localhost
// if host name is specified ( like "computer.domain" ... "My-MacBook.local" )
// than localhost is not aviable.
// if port is 0, bind will assign some free port for us
var port: UInt16 = 0
let hosts = ["localhost", String(cString: temp)]
var hints = addrinfo()
hints.ai_flags = 0
hints.ai_family = PF_UNSPEC
hints.ai_socktype = SOCK_DGRAM
hints.ai_protocol = IPPROTO_UDP
for host in hosts {
print("\t\(host)")
print()
// retrieve the info
// getaddrinfo will allocate the memory, we are responsible to free it!
var info: UnsafeMutablePointer<addrinfo>?
defer {
if info != nil
{
freeaddrinfo(info)
}
}
let status: Int32 = getaddrinfo(host, String(port), nil, &info)
guard status == 0 else {
print(errno, String(cString: gai_strerror(errno)))
return
}
var p = info
var serverSocket: Int32 = 0
var i = 0
var ipFamily = ""
// for each address avaiable
while p != nil {
i += 1
// use local copy of info
var _info = p!.pointee
p = _info.ai_next
// (1) create server socket
serverSocket = socket(_info.ai_family, _info.ai_socktype, _info.ai_protocol)
if serverSocket < 0 {
continue
}
// set port is tricky, because we need to remap ai_addr differently
// for inet and for inet6 family
switch _info.ai_family {
case PF_INET:
_info.ai_addr.withMemoryRebound(to: sockaddr_in.self, capacity: 1, { p in
p.pointee.sin_port = port.bigEndian
})
case PF_INET6:
_info.ai_addr.withMemoryRebound(to: sockaddr_in6.self, capacity: 1, { p in
p.pointee.sin6_port = port.bigEndian
})
default:
continue
}
// (2) bind
//
// associates a socket with a socket address structure, i.e. a specified local port number and IP address
// if port is set to 0, bind will set first free port for us and update
if bind(serverSocket, _info.ai_addr, _info.ai_addrlen) < 0 {
close(serverSocket)
continue
}
// (3) we need to know an actual address and port number after bind
if getsockname(serverSocket, _info.ai_addr, &_info.ai_addrlen) < 0 {
close(serverSocket)
continue
}
// (4) retrieve the address and port from updated _info
switch _info.ai_family {
case PF_INET:
_info.ai_addr.withMemoryRebound(to: sockaddr_in.self, capacity: 1, { p in
inet_ntop(AF_INET, &p.pointee.sin_addr, &temp, socklen_t(temp.count))
ipFamily = "IPv4"
port = p.pointee.sin_port.bigEndian
})
case PF_INET6:
_info.ai_addr.withMemoryRebound(to: sockaddr_in6.self, capacity: 1, { p in
inet_ntop(AF_INET6, &p.pointee.sin6_addr, &temp, socklen_t(temp.count))
ipFamily = "IPv6"
port = p.pointee.sin6_port.bigEndian
})
default:
break
}
// !!!!! refuze all listening sockets !!!
if listen(serverSocket, 5) < 0 {} else {
close(serverSocket)
continue
}
print("\tsocket \(serverSocket)\t\(ipFamily)\t\(String(cString: temp))/\(port)")
// (6) enable receiving data
// by installing event handler for a socket
let serverSource = DispatchSource.makeReadSource(fileDescriptor: serverSocket)
serverSource.setEventHandler {
var info = sockaddr_storage()
var len = socklen_t(MemoryLayout<sockaddr_storage>.size)
let s = Int32(serverSource.handle)
var buffer = [UInt8](repeating:0, count: 1024)
withUnsafeMutablePointer(to: &info, { (pinfo) -> () in
let paddr = UnsafeMutableRawPointer(pinfo).assumingMemoryBound(to: sockaddr.self)
let received = recvfrom(s, &buffer, buffer.count, 0, paddr, &len)
if received < 0 {
return
}
var totalSended = 0
repeat {
let sended = sendto(s, &buffer + totalSended, received - totalSended, 0, paddr, len)
if sended < 0 {
return
}
totalSended += sended
} while totalSended < received
})
}
serverSources[serverSocket] = serverSource
serverSource.resume()
}
print()
}
}
func stop() {
for (socket, source) in serverSources {
source.cancel()
close(socket)
print(socket,"\tclosed")
}
serverSources.removeAll()
}
}
print()
print("Hello, World! Echo UDP Server is listenig ...")
print("Connect any number of clients /nc -u host port, .../ to any of:")
print()
var echoServis: EchoUDPServer?
echoServis = EchoUDPServer()
echoServis?.start()
print("Press CTRL+D to exit")
print()
defer {
echoServis = nil
print("By by, World!")
}
while let input = readLine(){}
@op183
Copy link
Author

op183 commented May 16, 2017

echoudp

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment