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:
- from
server
Client requestJSON
data - Received and used within the proxy method
JSON
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:
- Open the connection to the chat room server
- Allows access to chat rooms by providing a name
- Users can send and receive messages
- 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.
- First, you have an input and output stream. Using this pair of classes allows you to create classes based on app and
server
The socket of. Naturally, you send messages through the output stream, and the output stream receives messages. - Next, you define
username
Variable is used to store the name of the current user - And finally we define
maxReadLength
. 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:
- In the first section, two uninitialized socket streams are created that do not automatically manage memory
- 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 possiblekCFAllocatorDefault
But 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
- First, messages are constructed using a simple chat protocol
- Then, the name just passed in is saved and can be used later when sending messages
- 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
- First, you create a buffer that can be used to read the message bytes
- Next, loop until there are no more bytes read from the input stream
- In each step of the loop, call
read(_:maxLength:)
Method reads a byte from the stream and puts it into the buffer passed in - 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
- First, initialize one with a buffer and length
String
Object. Sets the object toASCII
Code 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. - 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.
- Finally, use the string construct you just obtained
Message
Object 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?
createChatRoomDelegate
agreement
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