Socket Programming, NetService, GCDAsyncSocket, Bonjour for iOS

Dhaval Trivedi
9 min readFeb 4, 2020

--

Play TicTacToe with TCP/IP socket connection.

Hi guys, In this tutorial we will see about how socket connection is working in iOS and what are the main components working behind the scene.

The idea of this tutorial is connecting two devices with socket and play Tic Tac Toe game remotely.

What is socket programming?

Simply, its a connection between client/server, uses two-way communication over network with specified IP address. Where one is listening port and one is writing port. Socket is interface for sending and receiving data on specific port.

Socket connection within same network

When connection get establish between two sockets, it starts send and receive packets.

It may be TCP socket or UDP socket according to requirements. Here we will see about TCP/IP socket connection.

Well.. that’s enough about socket over view. We will not go deep in this topic, because mainly we will focus on how to connect sockets in iOS and which native framework, libraries will work behind the scene.

If you want to understand in detail about socket programming, refer this link. https://medium.com/swlh/understanding-socket-connections-in-computer-networking-bac304812b5c

CFNetwork

In iOS CFNetwork framework is responsible for establishing socket connection. It is Core Services framework that provides a library of abstractions for network protocols. It can perform a variety of network tasks, such as:

  • Working with BSD sockets
  • Creating encrypted connections using SSL or TLS
  • Resolving DNS hosts
  • Working with HTTP, authenticating HTTP and HTTPS servers
  • Working with FTP servers
  • Publishing, resolving and browsing Bonjour services

Where our focus is on Working with BCD sockets and Publishing, resolving and browsing Bonjour services from the above list.

CFNetwork framework will provide extension of BSD sockets, which avoids direct interaction with BCD sockets and make easier to manage socket connection process.

Publishing, resolving and browsing Bonjour services

Bonjour is known as zero configuration networking, which can automatically discovers devices and services within local network using standard internet protocols. Bonjour is mainly covers these three areas.

  • Addressing allowing IP address to the hosts
  • Naming using names to refer hosting instead of IP address
  • Service discovery finding services on the network automatically

Now the path seems clear, that we have to use Bonjour service for publish host socket and discover with client socket. Then connect these sockets for communication. Now the thing is which method or class are used for publishing and discovering that services. The answer is NSNetService.

For Swift, it’s same which is called just NetService.

NSNetService

It represents the instances of Bonjour network services. You can use the NSNetService class as either a publisher of a service or a client of a service.

So in summary NSNetService can be used either,

  • Publisher of service
  • Client of that published service

Note here, when we talk about only service, that means it is bonjour service.

Use service as publisher

If we publishes a service, our code must need port and prepare a socket to communication with client socket. Once that socket is ready, we can use NSNetService to notify clients that service is ready.

We have to initialise NSNetService object with service name, domain, type, port information. There are various methods with variety of options for that. After creation of this object, we can call publish method with this instance for broadcasting your service information to the Network.

Use service as client of publisher

If we have exact host and port information of the publisher we can create direct instance of NSNetService object with this, and make connection. But remember, main use of Bonjour service is discovering the published services automatically. So…. For this we will use NSNetServiceBrowser.

We can use object of NSNetServiceBowser for listing published services.

Take a note that all NSNetService process is totally asynchronous and depends on delegates for getting responses. So, NSNetServiceBowser is also fetching responses through delegation pattern.

NSNetServiceBrowser

This will come in use only on client side process. Here we have to locate service on the network first. Then it obtains corresponding NSNetService objects from that network with the delegate method. If it’s have “address” property, means it contains the socket address for the service. With this socket address we can establish connection with our client socket.

Finally….. The theoretical overview is completed :).

Let’s move to the coding.

Let’s move to the coding.

Starting from the home screen.

Home View Controller

Here we have Home screen with two buttons. One is “HOST” and another is “JOIN”. Take two other view controllers in story board, name it HostVC and JoinVC.Then perform push action with respective button action.

HostVC is working as a publisher and JoinVC will work as a client of that publisher.

There is generalised library named GCDAsyncSocket used for performing NSNetService operations. Which makes easier to manage net service operations. This is objective c based library so we need to use bridging header as our app is working on swift. So, just add GCDAsyncSocket.h and GCDAsyncSocket.m files. Then add header file.

Get the full source code here. https://github.com/Dhaval1094/DTSocketTicTacToe.

I followed MVP architecture. So, we have also presenter and service class. Where presenter will represents the services of the service class. Service class contains only business logic and return data to the HostVC with call back events. We have HostPresenter.swift and HostService.swift class here.

You can check my GitHub repository for basic understanding about MVP in iOS. https://github.com/Dhaval1094/MVP-UnitTesting-Basics-Swift

startBroadcast() method will publish NetService and get specific event changes with that service with callback events from the service class.

removeSocket() will remove instance of GCDAsyncSocket object and remove delegates from the service instance.

incomingValue(str: String) will be called when any get any event from the client socket.

HostPresenterDelegate will provide net service status & socket status to the Host View Controller.

Let’s look inside HostService class.

var service = NetService()

var hostSocket = GCDAsyncSocket().

These are variables responsible for publishing net service. Where others variables are just call back events, working as notifier to the presenter to the Host view controller.

In startBroadcast method we are initialising hostSocket and service instance. “service” will provide information with NetServiceDelegate. Where GCDAsyncSocket instance “hostSocketwill provide information with GCDAsyncSocketDelegate.

NetServiceDelegate

func netServiceDidPublish(_ sender: NetService) -> This will notify when current NetService instance publishes service. Look at service.publish() in the broadcast method.

Now what is that message looking in above image?

service = NetService(domain: “local.”, type: Constants.NETSERVICE.type, name: Constants.NETSERVICE.name, port: Int32(hostSocket.localPort))

Here NetService instance is created, and when we get call back in netServiceDidPublish method, it will print this message with these properties.

service.domain

service.type

service.name

service.port

We have to predefine these parameters which are used for create NetService instance.

func netService(_ sender: NetService, didNotPublish errorDict: [String : NSNumber]) -> This will be called when any error occurred in publishing with current NetService.

GCDAsyncSocketDelegate

func socket(_ sock: GCDAsyncSocket, didAcceptNewSocket newSocket: GCDAsyncSocket) -> This will be called when published service will be accepted by client socket. Suddenly after this hostSocket will send some first value to the client socket for letting client know that i got your response, now we are successfully connected. Now we can start game play.

func socket(_ sock: GCDAsyncSocket, didRead data: Data, withTag tag: Int) -> This delegate method will be called when get any incoming value from the client socket.

func socket(_ sock: GCDAsyncSocket, didWriteDataWithTag tag: Int) -> It will notify that current socket did write data, means send value to the connected socket.

func socket(_ sock: GCDAsyncSocket, didConnectToHost host: String, port: UInt16) -> This will notify that socket is connected with the client.

func socketDidDisconnect(_ sock: GCDAsyncSocket, withError err: Error?) -> This will notify when disconnect with the client.

Here host side process is done. Now let’s look to the Joining side.

As we used MVP architecture, we have JoinPresenter and JoinService class.

As in HostPresenter we have start and stop broadCasting methods, here we will use start and stop browsing methods.

Same as HostService class here first four variables are only responsible for discovering and connecting services. Where others are call back events, and just work as notifier to the presenter to Join View Controller.

“services” is array of NetService objects discovered with browsing.

“serviceBrowser” is instance of NetServiceBrowser. Which delegate methods will notifies for available services of predefine type in domain as below.

serviceBrowser.searchForServices(ofType: Constants.NETSERVICE.type, inDomain: Constants.NETSERVICE.domain)

NetServiceBrowserDelegate will used for discovering available services and all. Where NetServiceDelegate and GCDAsyncSocketDelegate are working same as Host Service class.

func serviceSelected(service: NetService)

This method will be called when user will select service from the list.

service.resolve(withTimeout: 30.0)

Starts a resolve process of a finite duration for the service. And if we pass 0.0, it means no timeout and a resolve process of indefinite duration.

NetServiceBrowserDelegate

func netServiceBrowser(_ browser: NetServiceBrowser, didFind service: NetService, moreComing: Bool) -> It will find all the NetServices covered within the network.

Note: Bonjour is discovering services for devices with the same wi-fi network. So, we will get only published services within same network.

These NetServices with this delegate method will append to “services”, which is instance of NetService array.

var services = [NetService]()

Then it will notify to the join presenter with this list of services. Join View Controller contains table view, which will be reload with this service list. User can select service from the list. We are showing service.name property in the list.

NetServiceDelegate

func netServiceDidResolveAddress(_ sender: NetService) -> Informs that the address for a given service was resolved.

func connect(with service: NetService?) -> Bool , this will check if the socket is already connected, and if not connected, then it will find “addresses” from the service, it will make connection. Here “addresses” is is read-only array of Data which contains socket address for the services.

joinSocket.connect(toAddress: address!)

It will make join to host socket connection with the socket address.

GCDAsyncSocketDelegate

func socket(_ sock: GCDAsyncSocket, didConnectToHost host: String, port: UInt16) -> It will be called when sockets are connected. We are sending suddenly another value to the connected host socket for notify it that connection is successful and ready to start the game.

We are sending “Join_soc” .utf8 string, which is converted to Data from the Join side. It means when host socket will read this data, it will convert it to normal string and match it with keyword “Join_soc”. If it’s matched, on the host side Host View Controller’s navigation will push to the Game View Controller

Same as above when we get acceptance in the host service class we are sending “Host_soc” string as data. Then on the join side it will be read by join socket and join View Controller’s navigation will push to the Game View Controller.

Now we completed publish, join, browsing, read, write data with the sockets. All is set now for send receive data with sockets.

Now we have to just focus on code logic for the Tic Tac Toe.

Remember that we are sending data with GCDAsyncSocket object and receiving data with GCDAsyncSocketDelegate. So, we required that same instance in Game View Controller. If it is client side app then this instance will be passed through join service class, as well as if its host side app then instance will be passed through host service class.

func incomingActionWith(str: String)

func sendValue(str: String)

These two are main methods for send receive socket events in GameVC.

Our goal was focusing on send and receive events remotely with sockets. So you can check my source code on GitHub for understanding code logic and UI setup for Tic tac toe game. https://github.com/Dhaval1094/DTSocketTicTacToe

Please add star in this GitHub repository if you find it helpful.

Thanks for reading this article. I like to hear feedback from you. Please share your feedback. Feel free to ask any questions :)

--

--

Dhaval Trivedi
Dhaval Trivedi

Responses (1)

Write a response