iOS Close Real-time Communication Solution


Recently, we have studied the close real-time communication between iOS devices, and have understood its solution. The following is arranged:

AirDrop is often used to share pictures and videos between iOS/OS X systems, but its real-time performance is poor.
CoreBluetooth has a smaller bandwidth.
GameKit has been discarded;
Socket scheme requires iOS devices in the same LAN.
External Accessory does not apply to scenarios between iOS devices;
Multipeer Connectivity is more in line with the requirements of close real-time communication from the information we know. This paper introduces how to use the Multipeer Connectivity framework.


Real-time communication with Multipeer Connectivity is divided into two steps, one is to establish a binary channel, the other is to carry out protocol communication.

1. Establishment of circulation channels

Demo needs to use two iOS devices (mobile A and mobile B), named server (mobile A) and client (mobile B). In order to be easy to learn, demo is divided into two projects (server and client). The actual development should be the same project, which can be distinguished by different Role s.
The process of establishing circulation channels is as follows:

Establishment process of circulation channel

1. Mobile A Launches Broadcasting

As a server, mobile A needs to start broadcasting first.
MCPeerID is the identification of the device in the connection, and the length can not exceed 63 bytes (UTF-8 encoding).
MCAdvertiser Assistant is a broadcasting management class, which provides broadcasting initiation interface and broadcasting agent callback.
To initiate broadcasting, you need to create MCPeerID and MCA dvertiser Assistant first.

    MCPeerID *peerId = [[MCPeerID alloc] initWithDisplayName:@"server"];
    self.mSession = [[MCSession alloc] initWithPeer:peerId];
    self.mSession.delegate = self;
    self.mAdvertiserAssistant = [[MCAdvertiserAssistant alloc] initWithServiceType:@"connect" discoveryInfo:nil session:self.mSession];
    self.mAdvertiserAssistant.delegate = self;

Once created, you can call startServer to initiate broadcasting.

- (void)startServer {
    [self.mAdvertiserAssistant start];
2. Mobile B Search Broadcasting

Mobile B, as a client, needs to search and request to establish a connection. The MCPeerID and MCSession also need to be created before the connection is established.

    MCPeerID *peerId = [[MCPeerID alloc] initWithDisplayName:@"client"];
    self.mSession = [[MCSession alloc] initWithPeer:peerId];
    self.mSession.delegate = self;

MCBrowserViewController is a system-provided VC for establishing connections. It automatically searches nearby broadcasts and displays them in the list. After clicking, it can request to establish connections.

- (void)startClient {
    if (!self.mBrowserVC) {
        self.mBrowserVC = [[MCBrowserViewController alloc] initWithServiceType:@"connect" session:self.mSession];
        self.mBrowserVC.delegate = self;
    [self presentViewController:self.mBrowserVC animated:YES completion:nil];
3. Mobile A Accepts Connections

When cell phone B requests to establish a connection, cell phone A pops up the request to establish a connection, as follows:

Click Accept to complete the connection establishment process.
When the connection is successfully established, the MCSession calls back the MCSessionStateConnected.

- (void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state {
    if (session == self.mSession) {
        NSString *str;
        switch (state) {
            case MCSessionStateConnected:
                str = @"Successful connection.";
            case MCSessionStateConnecting:
                str = @"on connection...";
                str = @"connection failed.";
        NSLog(@"id:%@, changeState to:%@", peerID.displayName, str);
4. Mobile A Creates Output Stream

As a server, mobile A actively establishes the output stream.
Note that you need to put mOutputStream into RunLoop and call open.

    if (!self.mOutputStream) {
        self.mOutputStream = [self.mSession startStreamWithName:@"delayTestServer" toPeer:[self.mSession.connectedPeers firstObject] error:nil];
        self.mOutputStream.delegate = self;
        [self.mOutputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
        [self.mOutputStream open];
5. Mobile B accepts input stream and creates output stream

As a client, mobile B accepts the server's output stream and creates the client's output stream.
Here are two points to note:

  • The output stream of server is represented as input stream in client.
  • The following callback method is in the sub-thread, so adding the main thread is [NSRunLoop mainRunLoop], not [NSRunLoop current RunLoop];
- (void)    session:(MCSession *)session
   didReceiveStream:(NSInputStream *)stream
           withName:(NSString *)streamName
           fromPeer:(MCPeerID *)peerID {
    if (self.mSession == session) {
        NSLog(@"didReceiveStream:%@, named:%@ from id:%@", [stream description], streamName, peerID.displayName);

        if (self.mInputStream) {
            [self.mInputStream close];
        self.mInputStream = stream;
        self.mInputStream.delegate = self;
        [self.mInputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
        [self.mInputStream open];
6. Mobile A accepts input stream

As a server, mobile A receives the client's output stream and completes the establishment of the flow channel.

II. Protocol Communication

After establishing the binary channel, server and client can communicate.
The basis of communication is Protocal protocol. In order to simplify, Int32 is used in all protocols.
The simple delay test protocol in ProtocolType.h is as follows:

typedef NS_ENUM(int32_t, ProtocolType) {
    ProtocolTypeNone = 0,
    //ProtocolTypeDelay A sends a message to B, B returns immediately, A receives the returned message and calculates the delay of two messages.
    ProtocolTypeDelayReq = 11,
    ProtocolTypeDelayRsp = 12,

The whole delay test is divided into three steps: mobile A sends a message to mobile B, mobile B returns the package immediately after receiving the message, mobile A receives the message from B, calculates the time consuming of the whole process, and gets the size of RTT(Round-Trip Time).

1. req, Delay Test Protocol for Mobile A Sending

Mobile A, as a server, initiates delayed testing on its own initiative.
When sending the ProtocolTypeDelayReq protocol, we also record the time mDelayStartDate to calculate the delay.

    int32_t type = ProtocolTypeDelayReq;
    self.mDelayStartDate = [NSDate dateWithTimeIntervalSinceNow:0];
    [self.mOutputStream write:(uint8_t *)&type maxLength:4];

2. Mobile phone B receives the delay test protocol req and returns the packet immediately.

Mobile B as a client, after receiving the message, first parse the protocol type.

- (void)onInputDataReady {
    ProtocolType type = 0;
    [self.mInputStream read:(unsigned char *)&type maxLength:sizeof(type)];
    [self handleProtocolWithType:type];

When receiving the ProtocolTypeDelayReq protocol, return to the ProtocolTypeDelayRsp protocol.

- (void)handleProtocolWithType:(ProtocolType)type {
    if (type == ProtocolTypeDelayReq) {
        int32_t type = ProtocolTypeDelayRsp;
        [self.mOutputStream write:(uint8_t *)&type maxLength:4];

3. Mobile A Receives Packets and Calculates RTT Time-consuming

Mobile A receives messages and parses them as well.
When receiving the ProtocolTypeDelayRsp protocol, the round trip time is calculated to get the RTT size.

- (void)handleProtocolWithType:(ProtocolType)type {
    if (type == ProtocolTypeDelayRsp) {
        NSDate *rspDate = [ NSDate dateWithTimeIntervalSinceNow:0];
        NSTimeInterval delay = [rspDate timeIntervalSinceDate:self.mDelayStartDate];
        self.mAverageDelayTime += delay * 1000;
        NSLog(@"delay test with %.2lfms,  average delay time:%.2lfms", delay * 1000, self.mAverageDelayTime / self.mDelayCount);


demo has two interesting points, one is the establishment of connection process of Multipeer Connectivity, the other is the transmission and analysis of communication protocol.
Multipeer Connectivity is similar to TCP's three handshakes, and it feels wonderful.
The sending and parsing of communication protocol is essentially the processing of binary stream data. In the actual development process, more interesting contents will be added, such as protocol header, protocol tail, check field, buffer processing, sticky package processing, etc.

Write a simple article to introduce the Multipeer Connectivity framework, and then write a practical application of accessing Multipeer Connectivity in the project.
demo address

Reference resources

iOS Near Field Communication (Bluetooth Development, WiFi Development)

Keywords: Mobile Session iOS OS X

Added by michaewlewis on Sun, 19 May 2019 18:54:18 +0300