Building a simple TCP chat for iOS using Swift.

Аλέξιος
5 min readJan 9, 2022

--

Picture 1 — The iMessage icon by Apple.

Last month, I was assigned the task that involved the chat application implementation for iOS on a commercial project using raw TCP sockets. I hesitated because I was a neophyte in sockets programming. Although I had this conundrum and convoluted assignment, I have completed it! Besides, the TCP internals sounds more candid for me now than before, so I will be grateful to share my knowledge about it here.

Let us start with the definition of TCP. From the rfc793 document the acronym TCP stands for:

The Transmission Control Protocol (TCP) is intended for use as a highly reliable host-to-host protocol between hosts in packet-switched computer communication networks, and in interconnected systems of such networks.

Therefore, it can be used as a reliable alternative for UDP (User Datagram Protocol) because of the three-way handshake way of connection establishment as it is depicted on the diagram below.

Picture 2— The TCP three-way handshake connection diagram.

Hence, there are the core principles of the TCP explained down here.

1. The fundamentals of TCP connection.

In fact, a three-way handshake is a method used in a TCP/IP network to create a client-server connection that requires both the client and the server to exchange SYN (synchronization) and ACK (acknowledgment) packets before the actual data transmission starts.

Here is the algorithm of the TCP three-way handshake:

  • At first, the client sends the SYN data packet via an IP network to the server. The purpose of this packet is to ask or infer if the server is open for new connections.
  • Secondly, the addressed server has to contain opened ports for inbound connections initialization. As soon as the server receives the SYN packet from the client node, it responds and returns a confirmation — the ACK packet or SYN/ACK packet.
  • In the end, the client node receives the SYN/ACK from the server and responds with an ACK packet.

Thus, the theory part is over. Let us dive into implementation!

2. The TCP reference from the Foundation library.

Initially, the mandatory methods from the Foundation library have to be imported into the project for TCP connection functions management.

As the “Stream Programming Guide for Cocoa” states:

A stream is a fundamental abstraction in programming: a sequence of bits transmitted serially from one point to another point. Cocoa provides three classes to represent streams and facilitate their use in your programs: NSStream, NSInputStream, and NSOutputStream. With the instances of these classes you can read data from, and write data to, files and application memory. You can also use these objects in socket-based connections to exchange data with remote hosts. You can also subclass the stream classes to obtain specialized stream behavior.

Consequently, here is the scheme of sockets connection using Apple’s Foundation library for better comprehension.

Picture 3— The sockets’ communication diagram from the “Stream Programming Guide for Cocoa”.

3. The TCP connection implementation in iOS using Swift.

In fact, in order to initiate the TCP connection, the designated class has to conform to the “StreamDelegate” protocol and needs to inherit the “NSObject” class. Furthermore, it is compulsory to define properties such as read/write and input/output streams as it is written below.

var readStream: Unmanaged<CFReadStream>?    
var writeStream: Unmanaged<CFWriteStream>?
var inputStream: InputStream?
var outputStream: OutputStream?

Optionally, the URL server address and port could be defined in the class initializer as stated in this part of the code.

private var url: URL;    
private var port: UInt32;
init(url: URL, port: UInt32)
{
self.url = url;
self.port = port;
}

In addition, there are several steps to start the TCP connection to the server.

  • Firstly, you have to call the “CFStreamCreatePairWithSocketToHost” for server-client binding.
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (url.absoluteString as CFString), port, &readStream, &writeStream);
  • Secondly, the input and output streams have to be initialized and their delegates should be assigned to self for proper functioning.
outputStream = writeStream?.takeRetainedValue()        
inputStream = readStream?.takeRetainedValue() outputStream?.delegate = self;
inputStream?.delegate = self;
  • Thirdly, it is mandatory for streams to be scheduled for receiving stream events on a run loop. By doing this, the delegate evades being blocked when there is no data on the stream to read. If streaming is taking place on another thread, be sure to schedule the stream object on that thread’s run loop. You should never attempt to access a scheduled stream from a thread different than the one owning the stream’s run loop.
outputStream?.schedule(in: RunLoop.current, forMode: RunLoop.Mode.default);        
inputStream?.schedule(in: RunLoop.current, forMode: RunLoop.Mode.default);
  • Finally, you are ready to open the streams’ session via these lines of code.
outputStream?.open();        
inputStream?.open();
  • Moreover, the “StreamDelegate” protocol contains the method for handling stream events and it serves as a stream condition tracker.
func stream(_ aStream: Stream, handle eventCode: Stream.Event)

After a stream object is sent open, you can find out about its status, whether it has bytes available to read, and the nature of any error with the following messages:

  • streamStatus
  • hasBytesAvailable
  • streamError

The returned status is a StreamStatus constant indicating that the stream is opening, reading, at the end of the stream, and so on.

Lastly, there is the send method for messages transmission over raw TCP sockets to the server.

func send(message: String){         
let response = "msg:\(message)"
let buff = [UInt8](message.utf8)
if let _ = response.data(using: .ascii) { outputStream?.write(buff, maxLength: buff.count)
}

Ultimately, there is the algorithm from “Stream Programming Guide for Cocoa” for recapitulation of all TCP information written above and the demo picture with code attached illustrating the TCP chat created between the iOS application and the Packet SenderSoftware.

In Cocoa, reading from an InputStream instance consists of several steps:

  1. Create and initialize an instance of InputStream from a source of data.
  2. Schedule the stream object on a run loop and open the stream.
  3. Handle the events that the stream object reports to its delegate.
  4. When there is no more data to read, dispose of the stream object.
Picture 4— The iOS chat application test using “Packet Sender” by anoop4real.

Happy coding!

--

--