(轉)開源項目t-io


石墨文檔:https://shimo.im/docs/tHwJJcvKl2AIiCZD/

(二期)18、開源t-io項目解讀

【課程18】BIO、...AIO.xmind0.4MB

【課程18】t-io簡介.xmind0.2MB

【課程18】兩個官方例子.xmind0.3MB

【課程18】同步異...阻塞.xmind0.3MB

【課程18預習】百萬...t-io.xmind0.3MB

 

t-io是什么

官網相關

官網:https://t-io.org/

宣傳:不僅僅是百萬級網絡通信框架,讓天下沒有難開發的網絡通信

git地址:https://gitee.com/tywo45/t-io

t-io手冊:https://t-io.org/doc/index.html

 

t-io.pdf3MB

 

t-io與websocket

t-io:是一個網絡框架,從這一點來說是有點像 netty 的,但 t-io 為常見和網絡相關的業務(如 IM、消息推送、RPC、監控)提供了近乎於現成的解決方案,即豐富的編程 API,極大減少業務層的編程難度。

 

websocket: WebSocket協議是基於TCP的一種新的網絡協議。它實現了瀏覽器與服務器全雙工(full-duplex)通信——允許服務器主動發送信息給客戶端。

 

預備知識AIO、NIO、Socket
同步、異步、阻塞、非阻塞

先來個例子理解一下概念,以銀行取款為例: 

  • 同步 : 自己親自出馬持銀行卡到銀行取錢(使用同步IO時,Java自己處理IO讀寫);
  • 異步 : 委托一小弟拿銀行卡到銀行取錢,然后給你(使用異步IO時,Java將IO讀寫委托給OS處理,需要將數據緩沖區地址和大小傳給OS(銀行卡和密碼),OS需要支持異步IO操作API);

  • 阻塞 : ATM排隊取款,你只能等待(使用阻塞IO時,Java調用會一直阻塞到讀寫完成才返回);
  • 非阻塞 : 櫃台取款,取個號,然后坐在椅子上做其它事,等號廣播會通知你辦理,沒到號你就不能去,你可以不斷問大堂經理排到了沒有,大堂經理如果說還沒到你就不能去(使用非阻塞IO時,如果不能讀寫Java調用會馬上返回,當IO事件分發器會通知可讀寫時再繼續進行讀寫,不斷循環直到讀寫完成)

 

IO的方式通常分為幾種,同步阻塞的BIO、同步非阻塞的NIO、異步非阻塞的AIO。

同步阻塞BIO
  • 一個連接一個線程

在JDK1.4出來之前,我們建立網絡連接的時候采用BIO模式,需要先在服務端啟動一個ServerSocket,然后在客戶端啟動Socket來對服務端進行通信,默認情況下服務端需要對每個請求建立一堆線程等待請求,而客戶端發送請求后,先咨詢服務端是否有線程相應,如果沒有則會一直等待或者遭到拒絕請求,如果有的話,客戶端會線程會等待請求結束后才繼續執行。

 

優化:弄一個線程池來管理線程。即偽異步阻塞IO

同步非阻塞NIO
  • 一個請求一個線程

NIO本身是基於事件驅動思想來完成的,其主要想解決的是BIO的大並發問題:每個客戶端請求必須使用一個線程單獨來處理。問題在於系統本身對線程總數有一定限制,容易癱瘓。

 

NIO,當socket有流可讀或可寫入socket時,操作系統會相應的通知引用程序進行處理,應用再將流讀取到緩沖區或寫入操作系統。  也就是說,這個時候,已經不是一個連接就要對應一個處理線程了,而是有效的請求,對應一個線程,當連接沒有數據時,是沒有工作線程來處理的。

 

BIO與NIO一個比較重要的不同,是我們使用BIO的時候往往會引入多線程,每個連接一個單獨的線程;而NIO則是使用單線程或者只使用少量的多線程,每個連接共用一個線程。

 

** NIO的最重要的地方是當一個連接創建后,不需要對應一個線程,這個連接會被注冊到多路復用器上面,所以所有的連接只需要一個線程就可以搞定,當這個線程中的多路復用器進行輪詢的時候,發現連接上有請求的話,才開啟一個線程進行處理,也就是一個請求一個線程模式。

異步非阻塞AIO

對於讀操作而言,當有流可讀取時,操作系統會將可讀的流傳入read方法的緩沖區,並通知應用程序;對於寫操作而言,當操作系統將write方法傳遞的流寫入完畢時,操作系統主動通知應用程序。  即可以理解為,read/write方法都是異步的,完成后會主動調用回調函數。

 

總結

Java對BIO、NIO、AIO的支持:

  • Java BIO : 同步並阻塞,服務器實現模式為一個連接一個線程,即客戶端有連接請求時服務器端就需要啟動一個線程進行處理,如果這個連接不做任何事情會造成不必要的線程開銷,當然可以通過線程池機制改善。
  • Java NIO : 同步非阻塞,服務器實現模式為一個請求一個線程,即客戶端發送的連接請求都會注冊到多路復用器上,多路復用器輪詢到連接有I/O請求時才啟動一個線程進行處理。
  • Java AIO(NIO.2) : 異步非阻塞,服務器實現模式為一個有效請求一個線程,客戶端的I/O請求都是由OS先完成了再通知服務器應用去啟動線程進行處理

BIO、NIO、AIO適用場景分析:

  • BIO方式適用於連接數目比較小且固定的架構,這種方式對服務器資源要求比較高,並發局限於應用中,JDK1.4以前的唯一選擇,但程序直觀簡單易理解。
  • NIO方式適用於連接數目多且連接比較短(輕操作)的架構,比如聊天服務器,並發局限於應用中,編程比較復雜,JDK1.4開始支持。
  • AIO方式使用於連接數目多且連接比較長(重操作)的架構,比如相冊服務器,充分調用OS參與並發操作,編程比較復雜,JDK7開始支持。
t-io框架
使用場景

常用關鍵類
  • ChannelContext(通道上下文)
  • 每一個 tcp 連接的建立都會產生一個 ChannelContext 對象
  • (1)ServerChannelContext
  • ChannelContext 的子類,當用 tio 作 tcp 服務器時,業務層接觸的是這個類的實例。
  • (2)ClientChannelContext
  • ChannelContext 的子類,當用 tio 作 tcp 客戶端時,業務層接觸的是這個類的實例

  • GroupContext(服務配置與維護)
  • GroupContext 就是用來配置線程池、確定監聽端口,維護客戶端各種數據等的
  • ClientGroupContext
  • ServerGroupContext

我們在寫 TCP Server 時,都會先選好一個端口以監聽客戶端連接,再創建 N 組線程池來執行相關的任 務,譬如發送消息、解碼數據包、處理數據包等任務,還要維護客戶端連接的各種數據,為了和業務互動, 還要把這些客戶端連接和各種業務數據綁定起來,譬如把某個客戶端綁定到一個群組,綁定到一個 userid, 綁定到一個 token 等。GroupContext 就是用來配置線程池、確定監聽端口,維護客戶端各種數據等的。

 

GroupContext 是個抽象類,如果你是用 tio 作 tcp 客戶端,那么你需要創建 ClientGroupContext,如 果你是用 tio 作 tcp 服務器,那么你需要創建 ServerGroupContext

  • AioHandler(消息處理接口)
  • 處理消息的核心接口,它有兩個子接口
  • ClientAioHandler
  • ServerAioHandler
  • AioListener(通道監聽者)
  • 處理事件監聽的核心接口,它有兩個子接口,
  • ClientAioListener
  • ServerAioListener
  • Packet(應用層數據包)
  • TCP 層過來的數據,都會被 tio 要求解碼成 Packet 對象,應用都需要繼承這個類,從而實現自己的業務 數據包。

 

用於應用層與傳輸層的數據傳遞

傳輸層在往應用層傳遞數據時,並不保證每次傳遞的數據是一個完整的應用層數據包(以 http 協議為 例,就是並不保證應用層收到的數據剛好可以組成一個 http 包),這就是我們經常提到的半包粘包。傳輸層只負責傳遞 byte[]數據,應用層需要自己對 byte[]數據進行解碼,以 http 協議為例,就是把 byte[] 解碼成 http 協議格式的字符串。

 

  • AioServer(tio 服務端入口類)

 

  • AioClient(tio 客戶端入口類)

 

  • ObjWithLock(自帶讀寫鎖的對象)
  • 是一個自帶了一把(讀寫)鎖的普通對象(一般是集合對象),每當要對 這個對象進行同步安全操作(並發下對集合進行遍歷或對集合對象進行元素修改刪除增加)時,就得用這個 鎖。

t-io是基於tcp層協議的一個網絡框架,所以在應用層與tcp傳輸層之間設計到一個數據的編碼與解碼問題,t-io讓我們能自定義數據協議,所以需要我們自己手動去編碼解碼過程。

入門例子HelloWord
  • git項目地址

https://gitee.com/tywo45/tio-showcase

 

git里面有個幾個例子,首先我們看helloword的例子:

 

業務邏輯

本例子演示的是一個典型的TCP長連接應用,大體業務簡介如下。

  • 分為server和client工程,server和client共用common工程
  • 服務端和客戶端的消息協議比較簡單,消息頭為4個字節,用以表示消息體的長度,消息體為一個字符串的byte[]
  • 服務端先啟動,監聽6789端口
  • 客戶端連接到服務端后,會主動向服務器發送一條消息
  • 服務器收到消息后會回應一條消息
  • 之后,框架層會自動從客戶端發心跳到服務器,服務器也會檢測心跳有沒有超時(這些事都是框架做的,業務層只需要配一個心跳超時參數即可)
  • 框架層會在斷鏈后自動重連(這些事都是框架做的,業務層只需要配一個重連配置對象即可)

具體類說明:

server端
  • 導入核心包
<dependency>
   <groupId>org.t-io</groupId>
   <artifactId>tio-core</artifactId>
</dependency>
  • HelloServerStarter

構造ServerGroupContext,main方法啟動服務

  • HelloServerAioHandler

實現ServerAioHandler接口,重寫decodeencode,handler方法。

common
  • HelloPacket

繼承Packet類。自定義數據包的內容

  • Const

常量類,配置ip,端口等信息

client端
  • HelloClientStarter

構造clientGroupContext,連接節點,使用信息發送消息

  • HelloClientAioHandler

實現ClientAioHandler接口,重寫decode,encode,handler方法。

流程圖
  • idea請安裝PlantUML intergration插件

客戶端與服務端溝通時序圖.puml0.8KB

Server端初始化時序圖.puml0.6KB

 

(初始化服務器)

 

 

(客戶端與服務端通訊流程)

 

入門例子showcase
  • git項目地址

https://gitee.com/tywo45/tio-showcase

 

上面講的是helloword的例子,比較簡單,接下來的是showcase的例子,結合實際場景的一個例子。

 

業務邏輯

tio的框架初始化使用過程是一樣的。不過因為showcase中涉及到的交互越來越多,因此不能像helloword例子中的HelloPacket只有body這么簡單了,我們至少要加上一個type參數(消息類型),這樣的話服務器獲取到數據包之后再更加type來選擇消息處理類,從而拓展系統可用性。

 

(客戶端與服務器溝通流程)

客戶端與服務端溝通時序圖.puml1.4KB

 

 

客戶端發起登錄操作

  • org.tio.examples.showcase.client.ShowcaseClientStarter#processCommand
LoginReqBody loginReqBody = new LoginReqBody();
loginReqBody.setLoginname(loginname);
loginReqBody.setPassword(password);
 
          
ShowcasePacket reqPacket = new ShowcasePacket();
#這里指定消息類型
reqPacket.setType(Type.LOGIN_REQ);
reqPacket.setBody(Json.toJson(loginReqBody).getBytes(ShowcasePacket.CHARSET));
 
          
Tio.send(clientChannelContext, reqPacket);
  • LoginReqBody:登錄請求參數封裝類,繼承BaseBody
  • ShowcasePacket:數據包,繼承Packet,和ByteBuffer相互轉換
  • clientChannelContext:連接上下文通道

服務端接受請求操作

  • org.tio.examples.showcase.server.ShowcaseServerAioHandler#handler
ShowcasePacket showcasePacket = (ShowcasePacket) packet;
#獲取消息類型
Byte type = showcasePacket.getType();
#根據消息類型找到對應的消息處理類
AbsShowcaseBsHandler<?> showcaseBsHandler = handlerMap.get(type);
if (showcaseBsHandler == null) {
   log.error("{}, 找不到處理類,type:{}", channelContext, type);
   return;
}
#執行消息處理。消息處理類必須繼承AbsShowcaseBsHandler
showcaseBsHandler.handler(showcasePacket, channelContext);
  • handlerMap:存有當前所有消息處理類的map。數據包中包含消息類型,會根據消息類型獲取對應的消息處理類,而這個消息處理類會調用handler()方法處理數據。
  • AbsShowcaseBsHandler:消息處理抽象類,繼承這個類的處理類會對一種消息類型進行處理,並且一般專門處理一種消息封裝類(繼承BaseBody的封裝類)。

消息類型對應消息處理類的初始化

private static Map<Byte, AbsShowcaseBsHandler<?>> handlerMap = new HashMap<>();
static {
   #把消息類型與消息處理類映射起來 
   handlerMap.put(Type.GROUP_MSG_REQ, new GroupMsgReqHandler());
   handlerMap.put(Type.HEART_BEAT_REQ, new HeartbeatReqHandler());
   handlerMap.put(Type.JOIN_GROUP_REQ, new JoinGroupReqHandler());
   handlerMap.put(Type.LOGIN_REQ, new LoginReqHandler());
   handlerMap.put(Type.P2P_REQ, new P2PReqHandler());
}

 

如果接收到的消息類型是P2P_REQ,那么處理類就是P2PReqHandler:

  • org.tio.examples.showcase.server.handler.P2PReqHandler#handler
log.info("收到點對點請求消息:{}", Json.toJson(bsBody));
 
          
ShowcaseSessionContext showcaseSessionContext = (ShowcaseSessionContext) channelContext.getAttribute();
 
          
P2PRespBody p2pRespBody = new P2PRespBody();
p2pRespBody.setFromUserid(showcaseSessionContext.getUserid());
p2pRespBody.setText(bsBody.getText());
 
          
ShowcasePacket respPacket = new ShowcasePacket();
respPacket.setType(Type.P2P_RESP);
respPacket.setBody(Json.toJson(p2pRespBody).getBytes(ShowcasePacket.CHARSET));
Tio.sendToUser(channelContext.groupContext, bsBody.getToUserid(), respPacket);
springboot集成t-io

項目運行:https://github.com/fanpan26/SpringBootLayIM.git

由於layim是付費產品,所以在網絡上找了一個。(僅供學習哈)

需要放到項目目錄下面

static/js/layui/lay/modules/layim.js

 

layim.js33.3KB

 

項目結構

項目集成:

因為這里需要和瀏覽器之間進行通訊,所以需要用到websocket機制。tio有集成websocket的框架,所以直接導入即可。

<dependency>
    <groupId>org.t-io</groupId>
    <artifactId>tio-websocket-server</artifactId>
    <version>0.0.5-tio-websocket</version>
</dependency>

代碼結構

Guava - EventBus(事件總線)

項目使用Guava的EventBus替代了Spring的ApplicationEvent事件機制。

Guava的EventBus使用介紹如下:

 

事件定義

EventBus為我們提供了register方法來訂閱事件,不需要實現任何的額外接口或者base類,只需要在訂閱方法上標注上@Subscribe和保證只有一個輸入參數的方法就可以搞定。

new Object() {
 
          
    @Subscribe
    public void lister(Integer integer) {
        System.out.printf("%d from int%n", integer);
    }
}

 

事件發布

對於事件源,則可以通過post方法發布事件。 正在這里對於Guava對於事件的發布,是依據上例中訂閱方法的方法參數類型決定的,換而言之就是post傳入的類型和其基類類型可以收到此事件。

//定義事件
final EventBus eventBus = new EventBus();
//注冊事件
eventBus.register(new Object() {
 
          
    //使用@Subscribe說明訂閱事件處理方法
    @Subscribe
    public void lister(Integer integer) {
        System.out.printf("%s from int%n", integer);
    }
 
          
    @Subscribe
    public void lister(Number integer) {
        System.out.printf("%s from Number%n", integer);
    }
 
          
    @Subscribe
    public void lister(Long integer) {
        System.out.printf("%s from long%n", integer);
    }
});
 
          
//發布事件
eventBus.post(1);
eventBus.post(1L);

 

項目的而運用

 

主要處理事件:包含了申請通知,添加好友成功通知

關鍵類:

  • com.fyp.layim.common.event.bus.EventUtil:封裝了事件的監聽注冊,以及發布動作
  • com.fyp.layim.common.event.bus.body.EventBody:發布的內容封裝類,包含消息類型和消息內容字段
  • com.fyp.layim.common.event.bus.handler.AbsEventHandler:事件處理抽象類,具體處理器需要繼承這個重寫handler()方法
  • com.fyp.layim.common.event.bus.handler.AddFriendEventHandler:添加好友成功通知處理類。
  • com.fyp.layim.common.event.bus.LayimEventType:消息類型常量

調用:

  • com.fyp.layim.web.biz.UserController#handleFriendApply:好友同意好友請求之后發布事件

邏輯:

事件的處理其實是給申請人發起好友同意通知

  • com.fyp.layim.im.common.util.PushUtil#pushAddFriendMessage
/**
 * 添加好友成功之后向對方推送消息
 * */
public static void pushAddFriendMessage(long applyid){
    if(applyid==0){
        return;
    }
    Apply apply = applyService.getApply(applyid);
    ChannelContext channelContext = getChannelContext(""+apply.getUid());
    //先判斷是否在線,再去查詢數據庫,減少查詢次數
    if (channelContext != null && !channelContext.isClosed()) {
        LayimToClientAddFriendMsgBody body = new LayimToClientAddFriendMsgBody();
        User user = getUserService().getUser(apply.getToid());
        if (user==null){return;}
        //對方分組ID
        body.setGroupid(apply.getGroup());
 
          
        //當前用戶的基本信息,用於調用layim.addList
        body.setAvatar(user.getAvatar());
        body.setId(user.getId());
        body.setSign(user.getSign());
        body.setType("friend");
        body.setUsername(user.getUserName());
 
          
        push(channelContext, body);
    }
}
 
          

最后是通過Aio.send發送消息。

  • com.fyp.layim.im.common.util.PushUtil#push
/**
 * 服務端主動推送消息
 * */
private static void push(ChannelContext channelContext,Object msg) {
    try {
        WsResponse response = BodyConvert.getInstance().convertToTextResponse(msg);
        Aio.send(channelContext, response);
    }catch (IOException ex){
 
          
    }
}

 

功能結構
  • 登錄功能  
  • 單聊功能
  • 群聊功能
  • 其他自定義消息提醒功能
  • 等等。。。。

登錄的目的是過濾非法請求,如果有一個非法用戶請求websocket服務,直接返回403或者401即可。

單聊,群聊這個就不用解釋了

其他自定義消息提醒,比如:時時加好友消息,廣播消息,審核消息等。

項目設計-前端

前端的框架用到是layim,layim已經幫我們做好了界面和封裝了接口,所以我們的工作只需要按照layim來寫好接口提供結果就可以了。

 

首先看初始化:

  • templates/index.html

 

layim.config({
    //初始化接口
    init: {
        url: '/layim/base'
    }
    //查看群員接口
    ,members: {
        url: '/layim/members'
    }
    //上傳圖片接口
    ,uploadImage: {url: '/upload/file'}
    //上傳文件接口
    ,uploadFile: {url: '/upload/file'}
 
          
    ,isAudio: true //開啟聊天工具欄音頻
    ,isVideo: true //開啟聊天工具欄視頻
    ,initSkin: '5.jpg' //1-5 設置初始背景
    ,notice: true //是否開啟桌面消息提醒,默認false
    ,msgbox: '/layim/msgbox'
    ,find: layui.cache.dir + 'css/modules/layim/html/find.html' //發現頁面地址,若不開啟,剔除該項即可
    ,chatLog: layui.cache.dir + 'css/modules/layim/html/chatLog.html' //聊天記錄頁面地址,若不開啟,剔除該項即可
 
          
});
 
          

因為需要進行通訊,所以websocket的信息也需要初始化。都是在layim里面一起初始化的。

socket.config({
    log:true,
    token:'/layim/token',
    server:'ws://127.0.0.1:8888'
});

 

  • templates/layim/msgbox.html
項目設計-后端
基本相關類

控制器:

com.fyp.layim.web.api.GroupController:群組相關

com.fyp.layim.web.biz.AccountController:賬戶相關,登錄注冊

com.fyp.layim.web.biz.UploadController:上傳文件、圖片

com.fyp.layim.web.biz.UserController:用戶基礎數據

 

攔截器:

com.fyp.layim.web.filter.TokenVerifyInterceptor:校驗token,與userid轉換

com.fyp.layim.web.filter.LayimAspect:給所有請求設置當前用戶的值,放session當中

 

全局異常處理:

com.fyp.layim.web.filter.GlobalExceptionHandler:異常處理

 

網絡編程主要代碼

com.fyp.layim.im.common.*:定義消息類型,消息處理類的公共包。

com.fyp.layim.im.packet.*:這是數據包,因為協議是websocket,所以返回的不是packet,最后要返回的其實是符合websocket定義的org.tio.websocket.common.WsResponse。而這些數據是放在body中。

com.fyp.layim.im.server.*:t-io的服務配置相關,包括t-io服務的啟動,啟動之前需要ServerAioHandler、ServerAioListener、ServerGroupContext等參數。

 

所以總體邏輯是這樣的:

 

步驟一、配置了springboot的模塊自動加載機制。

  • META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration= com.fyp.layim.im.server.LayimServerAutoConfig

 

LayimServerAutoConfig配置將會被自動裝載。

  • com.fyp.layim.im.server.LayimServerAutoConfig:自動裝載的配置類
  • com.fyp.layim.im.server.config.LayimServerConfig:服務的ip、端口、心跳時間等基本配置
  • com.fyp.layim.im.server.LayimWebsocketStarter:初始化配置,啟動t-io服務,其中配置初始化和啟動都是委托給com.fyp.layim.im.server.LayimServerStarter完成。
  • com.fyp.layim.im.server.LayimServerStarter:根據配置初始化serverGroupContext、初始化消息處理器
//初始化t-io的serverGroupContext
//還有消息處理器與消息類型的映射關系
public LayimServerStarter(LayimServerConfig wsServerConfig, IWsMsgHandler wsMsgHandler, TioUuid tioUuid, SynThreadPoolExecutor tioExecutor, ThreadPoolExecutor groupExecutor) throws Exception {
    this.layimServerConfig = wsServerConfig;
    this.wsMsgHandler = wsMsgHandler;
 
          
    layimServerAioHandler = new LayimServerAioHandler(wsServerConfig, wsMsgHandler);
    layimServerAioListener = new LayimServerAioListener();
 
          
    serverGroupContext = new ServerGroupContext(layimServerAioHandler, layimServerAioListener, tioExecutor, groupExecutor);
    //心跳時間,暫時設置為0
    serverGroupContext.setHeartbeatTimeout(wsServerConfig.getHeartBeatTimeout());
    serverGroupContext.setName("Tio Websocket Server for LayIM");
 
          
    aioServer = new AioServer(serverGroupContext);
    serverGroupContext.setTioUuid(tioUuid);
    //initSsl(serverGroupContext);
    
    
    //初始化消息處理器
    LayimMsgProcessorManager.init();
}

接下來看下與serverGroupContext相關的消息處理咧、事件監聽類

  • com.fyp.layim.im.server.handler.LayimServerAioHandler:繼承ServerAioHandler,完成消息的解碼、編碼、消息處理過程
  • com.fyp.layim.im.server.listener.LayimServerAioListener:繼承ServerAioListener,完成事件監聽

綁定用戶信息

  • com.fyp.layim.im.common.processor.ClientCheckOnlineMsgProcessor#process
SetWithLock<ChannelContext> checkChannelContexts =
      Aio.getChannelContextsByUserid(channelContext.getGroupContext(),body.getId());

 

綁定用戶上線信息。

  • com.fyp.layim.im.server.handler.LayimMsgHandler#handleHandshakeUserInfo
private HttpResponse handleHandshakeUserInfo(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) throws Exception {
    UserService userService = getUserService();
    //增加token驗證方法
 
          
    String path = httpRequest.getRequestLine().getPath();
    String token = URLDecoder.decode(path.substring(1),"utf-8");
 
          
    String userId = TokenVerify.IsValid(token);
 
          
    if (userId == null) {
        //沒有token 未授權
        httpResponse.setStatus(HttpResponseStatus.C401);
    } else {
        long uid = Long.parseLong(userId);
        //解析token
        LayimContextUserInfo userInfo = userService.getContextUserInfo(uid);
        if (userInfo == null) {
            //沒有找到用戶
            httpResponse.setStatus(HttpResponseStatus.C404);
        } else {
            channelContext.setAttribute(userId, userInfo.getContextUser());
            //綁定用戶ID
            Aio.bindUser(channelContext, userId);
            //綁定用戶群組
            List<String> groupIds = userInfo.getGroupIds();
            //綁定用戶群信息
            if (groupIds != null) {
                groupIds.forEach(groupId -> Aio.bindGroup(channelContext, groupId));
            }
            //通知所有好友本人上線了
            notify(channelContext,true);
        }
    }
    return httpResponse;
}

 

 

Aio.java的api:

 

實現  IWsMsgHandler接口:用於websocket握手,響應,關閉通道等過程的業務處理。

握手邏輯首先是在:com.fyp.layim.im.server.handler.LayimServerAioHandler中。

 

wsMsgHandler.handshake 方法,這里一般直接返回默認的 httpReponse即可,代表(框架層)握手成功。但是我們可以在接口中自定義一些業務邏輯,比如用戶判斷之類的邏輯,然后決定是否同意握手流程。

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM