前言
這是我的畢業設計。
剛開始確定這個課題的時候是因為以前有稍微研究過一些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
