preface

Real-time Communication with Streams Tutorial for iOS

IOS streaming im tutorial

Since the beginning of time, people have dreamed of better ways to communicate with their distant brothers. From carrier pigeons to radio waves, we’re constantly trying to make communication clearer and more efficient.

In modern times, one technology has become an important tool in our quest for mutual understanding: the simple network socket.

The fourth layer of the modern network infrastructure, sockets are at the heart of everything from text editing to online communication in games.

Why sockets

You may be wondering, “Why not use URLSession in preference to the low-level API?” . If you’re not surprised, pretend you think……

URLSession communication is based on the HTTP network protocol. With HTTP, communication is request-response. This means that most network code in most apps follows the following pattern:

  1. fromserverClient requestJSONdata
  2. Received and used within the proxy methodJSON

But what happens when you want the server to tell the App something? HTTP does not handle this very well. Sure, you can do this by constantly asking the server to see if there are updates, also known as polling, or you can be more subtle with long polling, but these techniques don’t feel natural and have their drawbacks. Finally, why limit yourself to using the request-response paradigm if it’s not the right tool?

Note: long polling —- original text is not available

Long polling is a variation of the traditional round-robin technique that simulates the push of information from the server to the client. With long polling, the client requests the server like a normal poll. But when the server does not have any information to send to the server, the server holds the request and waits for available information instead of sending an empty message to the client. As soon as the server has information to send (or times out), it sends a response to the client. The client usually receives the message immediately after requesting the server, so that the service almost always has a waiting list to respond to the client’s request. In Web /AJAX, long connections are called Comet.

Long polling is not a push technique per se, but can be used in situations where long connections are not possible.

In this streaming tutorial, you will learn how to create a live chat application directly using sockets.

Instead of each client checking for updates on the server, the program uses an input and output stream that persists during the chat.

Start ~

To start, download the startup package, which contains the chat App and server code written in Go. You don’t have to worry about writing Go code, just start the server to interact with the client.

Up and runningserver

The server code is written using Go and compiled for you. If you don’t trust a compiled executable downloaded from the Internet, the source code is in the folder and you can compile it yourself.

To run the compiled server, open your terminal, cut to the downloaded folder and type the following command, followed by your boot password:

sudo ./server
Copy the code

After you have entered your password, you should see Listening on 127.0.0.1:80. Chat Server is up and running now you can move on to the next chapter.

If you want to compile Go code yourself, you’ll need to install Go with Homebrew.

If you don’t have the Homebrew tool, you’ll need to install it first. Open the terminal and paste the following command into the terminal.

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)
Copy the code

Then, install Go using the following command:

brew install go
Copy the code

Once the installation is complete, cut to the downloaded code location and use the following compile command at the terminal:

go build server.go
Copy the code

Finally, you can start the server using the code above for starting the server.

Take a look at your existing apps

Next, open the DogeChat project, compile and run it, and you should see the interface that has been written for you:

As shown above, DogeChat has been written to allow users to enter a name and enter a chat room. Unfortunately, the previous engineer didn’t know how to write a chat App so he wrote all the interfaces and basic jumps, leaving the network layer part for you.

Create a chat room

Before start coding, cut to ChatRoomViewController. The swift document. You can see that you have an interface handler that can receive information from the input field and display messages by configuring the Cell’s TableView with a Message object.

Now that you have the ViewController, you just need to create a ChatRoom to handle the heavy lifting.

Before I start writing a new class, I want to quickly list the functionality of the new class. For it, we want to be able to handle these things:

  1. Open the connection to the chat room server
  2. Allows access to chat rooms by providing a name
  3. Users can send and receive messages
  4. Close the connection when finished

Now that you know what to do, hit Command+N to create a new file. Select Cocoa Touch Class and name it ChatRoom.

Create input and output streams

Now proceed and replace the contents in the file as follows:

import UIKit

class ChatRoom: NSObject {
  / / 1
  var inputStream: InputStream!
  var outputStream: OutputStream!
  
  / / 2
  var username = ""
  
  / / 3
  let maxReadLength = 4096
  
}
Copy the code

Here, you define the ChatRoom class and declare properties to make communication more efficient.

  1. First, you have an input and output stream. Using this pair of classes allows you to create classes based on app andserverThe socket of. Naturally, you send messages through the output stream, and the output stream receives messages.
  2. Next, you defineusernameVariable is used to store the name of the current user
  3. And finally we definemaxReadLength. This variable limits the amount of data you can send a message at a time

Then, cut to ChatRoomViewController. Swift and add classes on the internal commercial ChatRoom attributes:

let chatRoom = ChatRoom(a)Copy the code

Now that you’ve built the class infrastructure, it’s time to start with the first item in your list of class functions-opening the connection between server and App.

Open the connection

Go back to the chatroom. swift file and add the following code below the property definition:

func setupNetworkCommunication(a) {
  / / 1
  var readStream: Unmanaged<CFReadStream>?
  var writeStream: Unmanaged<CFWriteStream>?

  / / 2
  CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault,
                                     "localhost" as CFString.80,
                                     &readStream,
                                     &writeStream)
}
Copy the code

Here’s what happened:

  1. In the first section, two uninitialized socket streams are created that do not automatically manage memory
  2. Connect the read/write socket and connect it to the socket on the host, where port number is 80.

    This function takes four arguments, the first of which is the allocation type you want to use to initialize the stream. Use as much as possiblekCFAllocatorDefaultBut there are other options if you want it to behave differently.

Next, you specify hostname. At this point you only need to connect to the local machine, but if you have a remote service that has an IP address, you can use it here.

Then, you specify the connection through port 80, which is a port number specified on the server side.

Finally, you pass in Pointers to read and write streams. This method initializes them using connected internal read and write streams.

Now that you have the stream after the accident, you can store references to them by adding the following two lines:

inputStream = readStream! .takeRetainedValue() outputStream = writeStream! .takeRetainedValue()Copy the code

Calling takeRetainedValue() on unmanaged objects allows you to obtain a retained reference synchronically and remove an unbalanced retained memory so that memory does not leak later. Now you can use streams when you need them.

Next, in order for the app to properly respond to network events, these streams need to be added to the Runloop. In internal setupNetworkCommunication function finally add the following two lines of code:

inputStream.schedule(in: .current, forMode: .commonModes)
outputStream.schedule(in: .current, forMode: .commonModes)
Copy the code

You are ready to open the door of the “flood” to start, add the following code (or last) in internal setupNetworkCommunication function:

inputStream.open()
outputStream.open()
Copy the code

That’s all. We went back to ChatRoomViewController. Swift class, within the viewWillAppear function to add the following code:

chatRoom.setupNetworkCommunication()
Copy the code

On the local server, you now have client and server connections open. Compile and run the code again, and you’ll see exactly the same interface as before you wrote the code.

Participate in the chat

Now that you’re connected to the server, it’s time to send some messages ~ the first thing you might say is who am I. After that, you’ll want to start sending messages to other people.

This raises an important question: Because you have two kinds of messages, you need to figure out a way to distinguish them.

Communication protocol

One of the benefits of dropping down to the TCP layer is that you can define your own protocol to determine whether a message is valid or not. For HTTP, you need to think of these annoying actions: Get, PUT, and PATCH. You need to construct urls and use appropriate headers and all sorts of things.

Here we have the next two messages you can send:

iam:Luke
Copy the code

Enter the chat room and inform the world of your name. You can say:

msg:Hey, how goes it mang?
Copy the code

To send a message to anyone in the chat room.

It’s pure and simple.

It’s obviously not safe, so don’t use it at work.

Now that you know the server’s expected format, you can write a method in ChatRoom to enter the ChatRoom. The only argument is the name.

To implement this, add the following method to the one you just added:


funcfunc  joinChatjoinChat(username: String)(username: String) {{/ / / / 1
     letlet data =  data = "iam:"iam:\(username)\(username)"".data(using: .ascii)!
  .data(using: .ascii)!   / / 2 / / 2
     selfself.username = username
  
  .username = username      / / 3 / / 3
     __ = data.withUnsafeBytes { outputStream.write($ = data.withUnsafeBytes { outputStream.write($00, maxLength: data., maxLength: data.countcount) }
}) } }
Copy the code
  1. First, messages are constructed using a simple chat protocol
  2. Then, the name just passed in is saved and can be used later when sending messages
  3. Finally, the message is written to the output stream. It’s a little more complicated than you might think,write(_:maxLength:)Method takes an unsafe pointer reference as its first argument.withUnsafeBytes(of:_:)Method provides a convenient way to handle unsafe Pointers to some data within the security scope of a closure.

Method has been ready to return to ChatRoomViewController. Swift and in viewWillAppear (_) method in the last add method calls into the chat rooms.

chatRoom.joinChat(username: username)
Copy the code

Now compile and run, enter your name to go to the screen and see:

Again, nothing happened?

Hold on, let me explain. Let’s go to the terminal. At the bottom of Listening on 127.0.0.1:80, you will see Luke has joined, or something else if your name is not Luke.

That’s good news, but you’d prefer to see signs of success on a mobile screen.

Respond to incoming messages

Fortunately, the server receives a message just like the one you just sent and sends it to everyone in the chat, including you. Fortunately, the app already displays incoming messages on the Table interface of the ChatRoomViewController.

All you have to do is use inputStream to capture these messages, convert them to a Message object, and pass them out for the table to display.

The first thing you need to do in response to a message is to make ChatRoom a proxy for the input stream. First, add the following extension to the very bottom of chatroom.swift:

extension ChatRoom: StreamDelegate {}Copy the code

Now that ChatRoom has adopted the StreamDelegate protocol, it can be declared as a proxy for inputStream.

Add the following code to setupNetworkCommunication () method, and just in the schedule (before _ : forMode:) method.

inputStream.delegate = self
Copy the code

Next, add an implementation of stream(_:handle:) to the extension:

func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
    switch eventCode {
    case Stream.Event.hasBytesAvailable:
      print("new message received")
    case Stream.Event.endEncountered:
      print("new message received")
    case Stream.Event.errorOccurred:
      print("error occurred")
    case Stream.Event.hasSpaceAvailable:
      print("has space available")
    default:
      print("some other event...")
      break}}Copy the code

Here you handle upcoming events that may occur on the stream. You should be most interested in a Stream. The Event. HasBytesAvailable, because it means the information need you to read

Next, write a method that handles the incoming message. Add under the following method:

private func readAvailableBytes(stream: InputStream) {
  / / 1
  let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: maxReadLength)
  
  / / 2
  while stream.hasBytesAvailable {
    / / 3
    let numberOfBytesRead = inputStream.read(buffer, maxLength: maxReadLength)
    
    / / 4
    if numberOfBytesRead < 0 {
      if let _ = stream.streamError {
        break}}//Construct the Message object}}Copy the code
  1. First, you create a buffer that can be used to read the message bytes
  2. Next, loop until there are no more bytes read from the input stream
  3. In each step of the loop, callread(_:maxLength:)Method reads a byte from the stream and puts it into the buffer passed in
  4. If the number of bytes read is less than 0, an error has occurred and exits

This method needs to be called when input stream of bytes are available, so in the stream (stream within the _ : handle:). The Event. HasBytesAvailable calls this method:

readAvailableBytes(stream: aStream as! InputStream)
Copy the code

At this point, you have a buffer full of bytes! Before completing this method, you need to write another helper method to program the buffer to the Message object.

Place the following code after readAvailableBytes(_:) :

private func processedMessageString(buffer: UnsafeMutablePointer
       
        , length: Int)
        -> Message? {
  / / 1
  guard let stringArray = String(bytesNoCopy: buffer,
                                 length: length,
                                 encoding: .ascii,
                                 freeWhenDone: true)? .components(separatedBy:":"),
    let name = stringArray.first,
    let message = stringArray.last else {
      return nil
  }
  / / 2
  let messageSender:MessageSender = (name == self.username) ? .ourself : .someoneElse
  / / 3
  return Message(message: message, messageSender: messageSender, username: name)
}
Copy the code
  1. First, initialize one with a buffer and lengthStringObject. Sets the object toASCIICode and tell the object to release the buffer when it is finished using it and use it:Symbol to split the message, so you can get the name and the message separately.
  2. Next, you know that you or someone else sent a message based on the name. In a real app, you might want to have a unique token to distinguish between different people, but here that’s fine.
  3. Finally, use the string construct you just obtainedMessageObject and return

Add the following if-let code to the end of the readAvailableBytes(_:) method to use the message-constructing method:

if let message = processedMessageString(buffer: buffer, length: numberOfBytesRead) {
  //Notify interested parties
  
}
Copy the code

At this point, you are ready to send Message to someone, but who?

createChatRoomDelegateagreement

OK, are you sure you want to tell ChatRoomViewController. Swift, the new message coming, but you didn’t its reference. Because it holds a strong reference to ChatRoom, you don’t want to explicitly declare a ChatRoomViewController property to create a reference loop.

This is a great time to use a proxy protocol. ChatRoom doesn’t care which object wants to know what’s new, it just tells someone.

At the top of chatroom.swift, add the following simple protocol definition:

protocol ChatRoomDelegate: class {
  func receivedMessage(message: Message)
}
Copy the code

Next, add the weak optional attribute to preserve a reference to any object that you want to be a ChatRoom proxy.

weak var delegate: ChatRoomDelegate?
Copy the code

Now go back to the readAvailableBytes(_:) method and add the following code to the if-let:

delegate? .receivedMessage(message: message)Copy the code

To complete it and return to ChatRoomViewController. Swift in MessageInputDelegate expansion agent and adding below for ChatRoomDelegate extension

extension ChatRoomViewController: ChatRoomDelegate {
  func receivedMessage(message: Message) {
    insertNewMessageCell(message)
  }
}
Copy the code

As I said before, the rest of the work has already been done for you, and the insertNewMessageCell(_:) method will receive your message and properly add the appropriate cell to the table.

Now, after calling its super code in viewWillAppear(_:), set the interface controller as the ChatRoom proxy.

chatRoom.delegate = self
Copy the code

To compile and run again, enter your name to enter the chat page:

The chat room now successfully displays a cell indicating that you have entered the room. You have formally sent a message and received a message from a socket based TCP server.

Send a message

It’s time to allow users to send real text messages

Go back to chatroom.swift and add the following code at the bottom of the class definition:

func sendMessage(message: String) {
  let data = "msg:\(message)".data(using: .ascii)!
  
  _ = data.withUnsafeBytes { outputStream.write($0, maxLength: data.count)}}Copy the code

This method, like the joinChat(_:) method I wrote earlier, converts the MSG you sent into text as a real message.

Because you hope to tell ChatRoomViewController inputBar users click on the Send button when sending a message, return to ChatRoomViewController. Swift and find MessageInputDelegate extension.

Here you’ll find an empty method called sendWasTapped(_:). To actually send the message, pass it directly to chatRoom.

chatRoom.sendMessage(message: message)
Copy the code

This is all about sending. The server will receive the message and forward it to anyone, and ChatRoom will be notified of the message by joining the room.

Run again and send a message:

If you want to see other people chatting here, open a new terminal and type:

telnet localhost 80
Copy the code

This allows you to connect to the TCP server from the command line. Now you can send the same command as app:

iam:gregg
Copy the code

Then, send a message:

msg:Ay mang, wut's good?
Copy the code

Congratulations, you have successfully created the chat client ~

The cleanup

If you’ve written any programming about files before, you should know the good habits when files run out. It turns out that, like everything else in Unix, an open socket connection is represented by a file handle, which means that like any other file, you need to close it when you’re done using it.

Add the following method after the sendMessage(_:) method

func stopChatSession(a) {
  inputStream.close()
  outputStream.close()
}
Copy the code

As you might guess, this method closes the stream and makes it impossible for messages to be received or sent out. This also removes the stream from the runloop that was added earlier.

For the final finish it in the Stream. The Event. EndEncountered code added to invoke this method under the branch:

stopChatSession()
Copy the code

Then, back to ChatRoomViewController. Swift and in viewWillDisappear (_) and add the code above.

In this way, we are done

Where to go

For the full code, click here

Now that you’ve mastered (or at least seen a simple example) the basics of socket networking, there are several ways to expand your horizons.

UDP socket

This tutorial is an example of TCP communication, which establishes a connection and keeps packets as reachable as possible. Alternatively, you can use UDP, or packet socket communication. These sockets do not have such a guaranteed transmission, which means they are faster and less expensive. They are very useful in games. Do you experience delays? That means you have a bad connection and many packets that should be received are discarded.

WebSockets

Another technique for using HTTP for applications in this way is called WebSockets. Unlike traditional TCP sockets, WebSockets at least maintain a relationship to HTTP and can be used to achieve the same real-time communication goals as traditional sockets, all from the comfort and security of the browser. WebSockets are also available on iOS, and we happen to have this tutorial if you want to learn more about it.

Beej’s Guide to Web programming

Finally, if you really want to learn more about the web, check out the free online book, Beej’s Guide to Web Programming. Strange nicknames aside, this book provides very detailed and well-written socket programming. If you’re afraid of C, this book is scary, but maybe today is the time to face your fears :]

Hope you enjoyed this streaming tutorial and, as always, feel free to let me know or leave a comment below if you have any questions