天天看點

[iOS]從零開始開發一個即時通訊APP

前言

這是我的畢業設計。

剛開始确定這個課題的時候是因為以前有稍微研究過一些XMPP協定,在這個基礎上做起來應該不難。然後開始選技術的時候還有半年,我想為什麼不從更底層做起呢!那就不用XMPP,當時接觸過相關的即時通訊技術還有WebSocket,那為什麼直接從更底層的Socket開始封裝呢

服務端就用Go語言吧,用來做IM伺服器和HTTP伺服器都很好。

技術選型

既然是基于Socket,iOS端我并不準備中C語言的Socket開發封裝起,而是使用一個第三方庫CocoaAsyncSocket。XMPP的iOS framework也是從這個庫開始封裝。而Go語言的IM服務端則直接使用原生開發即可,無論是UDP還是TCP都已經封裝的很好。

HTTP伺服器使用的架構是Gin,已經相當成熟,可以用于大型服務端的開發了。

關于傳輸的資料格式,XMPP使用的是XML,但是體積太大,備援過多不必要的資料,考慮了很久好像也沒必要自己封裝二進制的資料格式,我用的是Google的protocol buffer。HTTP伺服器還是使用JSON。

我還需要存儲用戶端的IP位址,由于需要快速讀寫,我使用的是Redis。

AccessToken驗證方式使用的是JSON Web Token(JWT)

實作思路

我的想法是使用UDP Socket來傳輸資料,至于為什麼使用UDP呢,一開始的想法是UDP比TCP快,雖然可能會丢包但是可以試着優化。關于使用UDP來做IM這個想法也被一些大神噴過,但是這都是我自己的想法,就這樣做着先。

使用UDP會丢包,是以我想需要一個回執機制,接收端收到了消息後就給發送端發送一個回執,這個回執包括這條消息的ID,如果發送方過一段時間還沒有接受到回執的時候則重新發送。而且這個回執還不能丢,是以我使用TCP來發送回執。

UDP是無連接配接性的,還是要使用TCP來連接配接服務端,表明登入狀态。是以TCP的作用是連接配接和發送回執。

具體思路是當用戶端登入和重新連接配接的時候,用戶端使用UDP Socket綁定端口,然後使用TCP Socket來發送UDP 位址給服務端,服務端把使用者的ID和UDP位址存進Redis,等發送方發送的消息包含接收端的使用者ID,服務端再從Redis取出接收方的UDP位址進行轉發。

發送圖檔我是這樣實作的,我會把圖檔上傳到七牛雲,發圖檔的URL來發送,接收端隻需要使用URL來加載圖檔即可

簡單封裝一個通訊協定

就叫簡單的即時通訊協定,Simple Instant Messaging Protocol,簡稱SIMP

我想是基于連接配接的,是以一個使用者對應一個 SIMPConnection,每一個SIMPConnection是一個單例,使用代理進行回調

- (BOOL)connectionToRemoteHost:(NSString *)host port:(NSInteger)port forUser:(NSString *)userID;
           

連接配接需要使用者ID和伺服器的位址和端口

在連接配接的時候就建立TCP和UDP Socket 進行連接配接,TCP Socket要發送連接配接的資料,包括UDP Socket的位址

- (BOOL)connectionToRemoteHost:(NSString *)host port:(NSInteger)port forUser:(NSString *)userID {
    self.host = host;
    self.port = port;
    self.userID = userID;
    self.tcpSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)];
    self.udpSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
    return [self connect];
}

- (BOOL)connect {
        NSError *error;
        BOOL tcpSuccess = [self.tcpSocket connectToHost:self.host onPort:self.port error:&error];
        CheckError(@"TCPSocketConnectToHost", &error);


        BOOL udpSuccess = [self.udpSocket connectToHost:self.host onPort:self.port + 1 error:&error];

        CheckError(@"UDPSocketConnectToHost", &error);
        [self.udpSocket beginReceiving:&error];
        CheckError(@"beginReceiving", &error);

        [self sendConnectData];
    return tcpSuccess && udpSuccess;
}
           

還有封裝一個 SIMPMessage

裡面包含protobuf的資料

我的protobuf資料是這樣的,版本,消息的ID,時間,文字内容,圖檔URL,發送方的ID和接收方的ID,消息類型,圖檔的比例

syntax = "proto3";
message Message {
 float version = 1;
 uint64 messageId = 2;
 uint64 time = 3;
 string content = 4;
 string imageURL = 5;
 string fromUser = 6;
 string toUser = 7;
 MessageType type = 8;
 float imageScale = 9;
 enum MessageType {
     TEXT = 0;
     IMAGE = 1;
     AUDIO = 2;
     CONNECT = 3;
     RECEIPT = 4;
    }
}
           

還有消息隊列,群聊等一些我已經有想法但是還沒實作的功能

架構

關于整個APP的流程如下

關于iOS端,使用了MVVM設計模式結合RAC,在Controller裡面隻需要組合一下視圖和布局,綁定資料即可,把處理資料和大部分邏輯都放在了ViewModel裡面,結構還算清晰。

關于資料管理,我使用了一個Redux思想的全局資料排程中心,實作了單向資料流,資料的持久化等。資料持久化用到了FMDB。但是大部分代碼是一個大神寫的,很屌。

效果和下一步

目前實作傳輸文字和圖檔,好友添加還是在背景添加(前端還沒做),動态子產品等。

登入

通訊錄

詳細資料

個人資料

聊天界面

Demo

先上傳到了github,目前功能還不完善,還會持續開發

https://github.com/AscenZ/Hey

[iOS]從零開始開發一個即時通訊APP