If you're looking to write for Apple platforms only, you should take a look at Network.framework. If you're looking to write for Linux or cross platform, then you could use SwiftNIO.
A pretty much equivalent (but improved in many ways) program can be written easily in SwiftNIO.
Client
import NIO
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
let client = try ClientBootstrap(group: group)
.connect(host: "localhost", port: 8089)
.wait() // This makes it "blocking", ie. synchronous.
try client.writeAndFlush(ByteBuffer(string: "hello")).flatMap {
client.close()
}.wait()
Server
final class PrintEverythingReceivedHandler: ChannelInboundHandler {
typealias InboundIn = ByteBuffer
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
let buffer = self.unwrapInboundIn(data)
print(String(buffer: buffer))
}
}
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
let server = try ServerBootstrap(group: group)
// Sets the listen backlog to 5. Contrary to your comment this doesn't set a
// maximum of 5 connections. I'd recommend to delete this line but I left it
// because you have it
.serverChannelOption(ChannelOptions.backlog, value: 5)
// You don't have this in your example but I'd recommend it
.serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
// You only read a maximum of 64 bytes at time. I wouldn't recommend it,
// I'd just delete this line. But again, you've got it :)
.childChannelOption(ChannelOptions.recvAllocator, value: FixedSizeRecvByteBufferAllocator(capacity: 64))
.childChannelInitializer { channel in
channel.pipeline.addHandler(PrintEverythingReceivedHandler())
}
.bind(host: "localhost", port: 8089)
.wait()
try server.closeFuture.wait()
Comments
Yes, the SwiftNIO program has more lines. And yes, SwiftNIO is overkill (and too much ceremony) for tiny network programs that just send the string "hello"
over the network. SwiftNIO is built as an abstraction that lets you develop & compose real network programs. Also it's asynchronous (because it is supposed to scale to thousands of connections) which -- at the moment -- makes things more complicated.
Once async/await lands we (or anyone really) will be able to offer asynchronous and yet super easy APIs for toy programs like the one you suggest.
What I mean with network protocol composability is that SwiftNIO makes it easy to add other protocols in the pipeline such as TLS, HTTP1/2, WebSocket, SSH, ... . For example you really should use TLS these days and that can be done using only very slightly modified code (see below).
Also, I should note that on Apple platforms, you should not be using the BSD Socket API anymore. So your Python program (and the NIO program I posted above) would -- on Apple platforms -- use the deprecated APIs. With SwiftNIO however it's very easy to make SwiftNIO use Network.framework instead of the Sockets API by using SwiftNIOTransportServices. Code using "plain" SwiftNIO which runs on BSD Sockets or SwiftNIOTransportServices (which runs on Network.framework) is pretty much the same, you just need to swap out the ClientBootstrap
for a NIOTSConnectionBootstrap
and the ServerBootstrap
for a NIOTSListenerBootstrap
. Packages like GRPC Swift, AsyncHTTPClient, or the NIOSMTP example automatically pick the best backend, they will choose NIOTransportServices (Network.framework) for modern Apple OSes and BSD Sockets on Linux (and old Apple OSes).
Client
let tlsConfiguration = TLSConfiguration.forClient(/* optional config if you don't have real certs */)
let sslContext = try! NIOSSLContext(configuration: tlsConfiguration)
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
let client = try ClientBootstrap(group: group)
.channelInitializer { channel in
// add TLS
channel.pipeline.addHandler(try! NIOSSLClientHandler(context: sslContext, serverHostname: "localhost"))
}
.connect(host: "localhost", port: 8089)
.wait()
try client.writeAndFlush(ByteBuffer(string: "hello")).flatMap {
client.close()
}.wait()
Server
final class PrintEverythingReceivedHandler: ChannelInboundHandler {
typealias InboundIn = ByteBuffer
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
let buffer = self.unwrapInboundIn(data)
print(String(buffer: buffer))
}
}
let certificateChain = try NIOSSLCertificate.fromPEMFile("/path/to/your/cert")
let sslContext = try! NIOSSLContext(configuration: TLSConfiguration.forServer(certificateChain: certificateChain.map { .certificate($0) },
privateKey: .privateKey(.init(file: "/path/to/your/private/key",
format: .pem))))
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
let server = try ServerBootstrap(group: group)
.serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
.childChannelInitializer { channel in
channel.pipeline.addHandlers(
// add TLS
NIOSSLServerHandler(context: sslContext),
PrintEverythingReceivedHandler())
}
.bind(host: "localhost", port: 8089)
.wait()
try server.closeFuture.wait()