TCP Sockets with Swift on Linux

| | 4 Comments| 11:54 AM
Categories:

A long time ago in a galaxy far, far away, software developers wrote client-server applications with TCP/IP sockets. That was before the dark times, before HTTP.

I am of course, joking. HTTP can be leveraged to provide a wide variety of client-server applications and is of course at the base of REST applications. What HTTP brings to the table though is not the plumbing to get packets on the wire, but an agreed upon protocol structure (and to some degree a standard as to what port is being used) for those packets. Action verbs such as GET, POST, PUT, etc. and the HTTP header itself are what makes HTTP ideal for developing client-server applications.

In the end though, at the bottom of the stack, bits and bytes are marshaled through your operating system’s socket interface. The API for interacting with network sockets is quite rich and many a tutorial and books have been written on the topic. IP networking routines in C can be considerably verbose, and were one of the first “real-world” APIs encapsulated in object-oriented routines with C++. That tradition continued with Foundation’s CFStream class and now our Swift swiftysockets API.

Swiftychat

To illustrate how to use TCP/IP network sockets with Swift, we’ll be developing Swiftychat, a basic “chat system” application. It’s quite a naive application, limited in functionality, and would stand no chance in being used in the real world, but, even still, it’s a working example of how to send and receive strings on TCP/IP sockets in Swift.

swiftysockets

Swiftychat will make use of swiftysockets, a Swift Package Manager-ready TCP/IP socket implementation that was originally developed by the Zewo team. Unfortunately due to packaging constraints we have to do a little bit of a dance first to get an underlying C library, Tide, installed on our system. So let’s do that now.

$ git clone https://github.com/iachievedit/Tide
Cloning into 'Tide'...
...
$ cd Tide
$ sudo make install
clang -c Tide/tcp.c Tide/ip.c Tide/utils.c
ar -rcs libtide.a *.o
rm *.o
mkdir -p tide/usr/local/lib
mkdir -p tide/usr/local/include/tide
cp Tide/tcp.h Tide/ip.h Tide/utils.h Tide/tide_swift.h tide/usr/local/include/tide
# copy .a
cp libtide.a tide/usr/local/lib/
mkdir -p /usr/local
cp -r tide/usr/local/* /usr/local/

At some point we believe the Swift Package Manager will be able to compile C libraries that can be linked against in the rest of your package build. Until then, this is the best we can do.

Once Tide is installed we can leverage swiftysockets in our Swiftychat apps.

Start Coding!

Our main.swift file is as simple as it gets. Create a ChatterServer and start it.

main.swift

Of course, a brief main.swift can only mean one thing. Invasion. Oh wait, I’m done with the Star Wars references.

A brief main.swift means our implementation is tucked away in the ChatterServer class, which looks like this:

ChatterServer.swift:

Breaking down our server we have:

1. Initialization

We make use of the init? initializer to signal that nil is a possible return value since both the IP and TCPServerSocket classes (from swiftysockets) can throw an error. IP encapsulates our IP address and port information nicely and we provide an instance of it to the TCPServerSocket initializer. If init succeeds we now have TCP socket on the given port ready to accept incoming connections.

2. The Main Loop

Name the function startListening, start, main, we don’t care. It is the main event loop that accepts new client connections (server!.accept()) and adds them to the list of connected clients. server!.accept() is a blocking function that hangs out and waits for new connections. Pretty standard stuff.

3. Client Management

The rest of the ChatterServer contains all of the “client management” functions. There are a few variables and three routines that manage clients.

Our variables are straightforward:

  • an array of connected clients ([TCPClientSocket])
  • a connection counter that is used to hand out a “client identifier”

The routines are straightforward as well:

  • addClient takes a TCPClientSocket, increments our connection count, and then sets up an NSThread whose sole purpose is “manage” that given client’s connection. As additional connections come in new NSThreads are created for them as well. We’ll talk about the NSThread routine itself in a moment. Once the thread is started addClient will then append the TCPClientSocket onto the end of the array of connected clients.
  • removeClient removes a client from the connected clients list using the filter function to “filter out” the given client. Note here we use the !== identity operator.
  • broadcastMessage is what makes the ChatterServer a chat server. It uses the where keyword to create a filtered array that broadcasts a message created by a client to all of the other connected clients. Again, we use the !== operator.

Recall that a thread is a separate execution path that runs inside the main process. Our server creates a separate thread for each client that is connected. Now, you can argue whether or not that is a good idea, and if we’re designing a server that will eventually handle tens of thousands of clients, I’d argue that it isn’t. For our purposes though we’ll be fine.

Looking once more at our thread:

Our client handling thread also sits in a loop waiting for input through the receiveString method of the TCPClientSocket class. When a string is received the server logs it to the console and then broadcasts a response. If the try results in an error (a disconnect) the server removes the client.

Putting it All Together

Our goal is to, as much as possible, use the Swift Package Manager for building our applications. For an introduction to swiftpm check out our tutorial.

Add the following to Package.swift:

and in a directory named Sources add your main.swift and ChatterServer.swift code.

Running swift build should download and build our two dependencies (Tide and swiftysockets) and compile our application code. If all goes well you’ll have a binary named chatterserver in a directory named .build/debug/.

Testing it Out

Our next tutorial will be writing up a nifty little chat client, but for now we can test our server with the nc (netcat) command. Start your server and then in another terminal window type nc localhost 5555. You should see in your server’s window Client 1 connected. If you hit CTRL-C in your netcat “client” window the server will print a disconnect message along with the reason (like, Connection reset by peer).

For the real test we’ll start up the server and three connected clients.

Chatville
Chatville

In the left-hand terminal our chat server is running. On the right we have 3 clients, each started by typing the command nc localhost 5555. As each client connects the server prints out a connection message.

Recall that our broadcastMessage function excludes the originator of the message from the broadcast. This prevents a client from receiving his own message back (take out the where clause and you will see what we mean).

What’s Next

Using nc as our client is a bit boring. We can’t set a nickname, there’s no “structure” to our messages, no timestamps, etc. In the above example someone receiving a message has no idea who was writing! swiftysockets already has a TCPClientSocket class, why not create a more robust chat client?

Getting the Code

Of course we’ve put together the code for our little chat server here on Github. It also contains a chatterclient project which at the moment is not implemented. If you start with the download, you can type make in the top-level directory and it will build both the client and server. Remember: You must have installed libtide.a and its associated headers before using swiftysockets!

4 thoughts on “TCP Sockets with Swift on Linux”

  1. error: The package at `/Packages/Tide’ has no Package.swift for the specific version: 0.1.0

    swiftysockets create a empty Package.swift in Tide , how to make a Package.swift non empty?

Leave a Reply

Your email address will not be published. Required fields are marked *