What's the recommended way to write a TCP client application?

I need to write a client for a custom, propriety protocol over TCP/IP. I'd prefer it to be usable on macOS, iOS, and Linux. What's the recommended approach to doing that?

I have an existing set of Objective-C code for doing this, and there I just use raw BSD sockets. They're straightforward, and as this doesn't need to be a high performance server, I'm perfectly happy blocking: I just stick my code in an NSOperation, run it on a serial NSOperationQueue, and use callbacks to deliver results. Using BSD sockets in Swift feels like I'm fighting the language though: lots of dropping down into UnsafePointer territory.

I've taken a look at both the Network framework introduced in Mojave and Swift-NIO, but in both cases I'm really not sure how I'm meant to structure a client. I need to do a lot of back and forth: send a command to a thing, read the response back, send the next command, read a response, etc. Funneling everything through a single channelRead handler (in Swift-NIO's case) feels, again, like I'm doing the wrong thing.

Does anyone know of an example for a similar sort of back and forth communications for Swift-NIO? Or am I looking in the wrong direction?

Further update: I found technical note 3151 - Choosing the right networking API, and while it's helpful, the general recommendation for cross-platform code there seems to be "use Swift-NIO". Digging around in the Swift-NIO category I found the Sent TCP command and await response thread, which is right up my alley, but doesn't seem to have a great answer either. Digging further on that though.

Hi there,

I'm the OP of the thread you mentioned.

It's been a very long time since I last looked at my code, and I'm sure the API has changed significantly since then, but the thing that helped me most was looking at the Netty documentation for Java. Swift-NIO appears to have rather good documentation now, so I'd take a read through that first.

Again it's quite old, and things have perhaps since changed, but there's a simple example of how the Swift-NIO client and handler classes work together here. This default implementation lands you with the exact problem you mentioned, but if you declare the channel, handler, event loop etc. outside of the TCP client class, you can handle changes occurring in the handler class, such as disconnecting or receiving a message, in other methods in the client class. I'm not sure if it's the "right" way of going about it, but it was good enough to get it working smoothly for the application I was building.

While this probably isn't quite the answer you were looking for, I hope it's enough to get you going.

1 Like

You might consider BlueSocket developed by IBM and running on macOS, iOS and Linux.

I added support for Windows to this library and available under the name GreenSocket.

BlueSocket here (macOS, iOS, Linux):

GreenSocket here (macOS, iOS, Linux and Windows):

Hope this can help you.

1 Like

Looking around a bit more, I think the JsonRpc example in swift-nio-examples is pretty close to what I need. Thanks everyone!

You might consider BlueSocket developed by IBM and running on macOS,
iOS and Linux.

The problem with most cross-platform networking libraries, and this applies to BlueSocket AFAIK, is that they use BSD Sockets exclusively, which is seriously suboptimal for Apple platforms. TN3151 covers this in some detail.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

1 Like

It's true Quinn but, unfortunately Swift to date does not provide an optimal TPC and UDP networking solution on all platforms. If we want to use the same code base on iOS, macOS, Linux and Windows we are forced to use WinSock on Windows and BSD socket on other OS. Swift-NIO is excellent, used by Vapor, but not available on Windows and Swift-NIO uses BSD socket on Linux.

This brings me to the next question:

Is the Swift team (Apple and non-Apple) planning to create a single optimal networking library that can be used on iOS, macOS, Linux and Windows?

Apple employees are generally prohibited from talking about any forward planning, so I think it's very unlikely you'll get a response - either negative or positive - to this question as written.

I will rephrase my question.

Considering that Networking including TCP and UDP programming is a fundamental need on all platforms, is the Swift team considering the design of a single framework usable on iOS, macOS, Linux and Windows? In the statement, is there a development timetable?

Thanks

I have a handful of macOS + Linux tools and libraries that use TCP and UDP. I had good experience with GitHub - BiAtoms/Socket.swift: A POSIX socket wrapper written in swift. (my fork with a bunch of additions is GitHub - mickeyl/Socket.swift: A POSIX socket wrapper written in swift.)

IMPORTANT My role at Apple is to support what’s currently shipping, not to plan what might ship in the future. Keep that in mind as you read the following (and, honestly, everything I write in public).

Considering that Networking including TCP and UDP programming is a
fundamental need on all platforms, is the Swift team … ?

If you want to talk about The Future™, I recommend that you engage with Swift Evolution. A good place to start is with the Community Structure section on Community Overview. If you look at the workgroups listed there, only one, the Swift on Server Workgroup, is likely to wade into the issue of networking. If I were in your shoes, I’d start there and then decide on a path forward based on the response that you get.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

1 Like

I wonder how complicated would it be to create a critical / minimal usable subset of Network framework API (on top of sockets) for those other mentioned platforms where it's not available.

Well, it’s certainly possible to implement the Network framework API on top of BSD Sockets because that’s one of the modes the current implementation supports. I’m not familiar enough with the implementation to comment as to how hard it would be )-:

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

On what platform/s is this mode chosen?

On what platform/s is this mode chosen?

It’s complicated, and I don’t have a full answer, but:

  • For iOS and its child platform, user-space networking has been the default since Network framework was introduced in the 2018 releases (iOS 12 and friends).

  • For macOS, user-space networking became the default in macOS 12.

  • I suspect, but I haven’t confirmed, that specific setups cause Network framework on macOS to revert to using the BSD Sockets implementation.

On macOS you can tell which implementation a connection is using with command-line tools:

  • If it’s BSD Sockets, lsof will show a socket with the connection’s details.

  • If it’s Network framework, there’ll be no socket but there will be an entry in skywalkctl. See the skywalkctl man page for details.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

4 Likes

Since you want to run your TCP client cross platform you really need a library that supports all the platforms. As others have pointed out, you can use raw sockets and they are a thing on Darwin based platforms but I would recommend you to take a look at SwiftNIO. NIO sets out to be a non-blocking IO library and it abstracts away the underlying connection mechanisms for you. NIO itself ships with a module that works on top of sockets called NIOPosix and you can find an example how a TCP echo client is implemented here.

Additionally, as @eskimo pointed out the preferred networking stack on Darwin platforms is Network.framework. NIO also has a compatibility module that adds support for Network.framework based connections which is called swift-nio-transport-services.

Depending on where you run you might conditionally choose to bootstrap your client with either NIOPosix or NIOTransportServices.

3 Likes

That's the bit that's interesting. Without having the sources it's quite hard to figure out those specific conditions when the socket based implementation is chosen.