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

 

前言

這是個人畢業設計。
剛開始肯定這個課題的時候是由於之前有稍微研究過一些XMPP協議,在這個基礎上作起來應該不難。而後開始選技術的時候還有半年,我想爲何不從更底層作起呢!那就不用XMPP,當時接觸過相關的即時通信技術還有WebSocket,那爲何直接從更底層的Socket開始封裝呢
服務端就用Go語言吧,用來作IM服務器和HTTP服務器都很好。前端

技術選型

既然是基於Socket,iOS端我並不許備中C語言的Socket開發封裝起,而是使用一個第三方庫CocoaAsyncSocket。XMPP的iOS framework也是從這個庫開始封裝。而Go語言的IM服務端則直接使用原生開發便可,不管是UDP仍是TCP都已經封裝的很好。vue

HTTP服務器使用的框架是Gin,已經至關成熟,能夠用於大型服務端的開發了。git

關於傳輸的數據格式,XMPP使用的是XML,可是體積太大,冗餘過多沒必要要的數據,考慮了好久好像也不必本身封裝二進制的數據格式,我用的是Google的protocol buffer。HTTP服務器仍是使用JSON。github

我還須要存儲客戶端的IP地址,因爲須要快速讀寫,我使用的是Redisredis

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

實現思路

個人想法是使用UDP Socket來傳輸數據,至於爲何使用UDP呢,一開始的想法是UDP比TCP快,雖然可能會丟包可是能夠試着優化。關於使用UDP來作IM這個想法也被一些大神噴過,可是這都是我本身的想法,就這樣作着先。服務器

使用UDP會丟包,因此我想須要一個回執機制,接收端收到了消息後就給發送端發送一個回執,這個回執包括這條消息的ID,若是發送方過一段時間尚未接受到回執的時候則從新發送。並且這個回執還不能丟,因此我使用TCP來發送回執。markdown

UDP是無鏈接性的,仍是要使用TCP來鏈接服務端,代表登陸狀態。因此TCP的做用是鏈接和發送回執。架構

具體思路是當客戶端登陸和從新鏈接的時候,客戶端使用UDP Socket綁定端口,而後使用TCP Socket來發送UDP 地址給服務端,服務端把用戶的ID和UDP地址存進Redis,等發送方發送的消息包含接收端的用戶ID,服務端再從Redis取出接收方的UDP地址進行轉發。app

發送圖片我是這樣實現的,我會把圖片上傳到七牛雲,發圖片的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

相關文章
相關標籤/搜索