第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

IM - 優(yōu)化

1. 前言

前面兩個章節(jié)分別實現(xiàn)控制臺版本的單聊和群聊的功能實現(xiàn),通過這兩個小案例來體驗 Netty 的真實開發(fā)場景,其實在真實 Netty 開發(fā)當中,是非常的簡單的,但是前面花那么多的篇幅去講解 Netty 的核心組件和理論,主要目的是讓大家可以了解其原理,才能寫出高質(zhì)量的 Netty 代碼。雖然 Netty 的性能很高,但是不代表作出來的項目是性能很高的,本節(jié)主要講解如何去優(yōu)化 Netty。

2. 常見優(yōu)化方案

2.1 優(yōu)化架構(gòu)圖

圖片描述

2.2 優(yōu)化細節(jié)說明

常見的細節(jié)優(yōu)化

  1. 心跳檢測,主要是避免連接假死現(xiàn)象;
  2. 連接斷開,則刪除通道綁定屬性、刪除對應的映射關系,這些信息都是保存在內(nèi)存當中的,如果不刪除則造成資源浪費;
  3. 性能問題,用戶 ID 和 Channel 的關系綁定存在內(nèi)存當中,比如:Map<Integer,Channel>,key 是用戶 ID,value 是 Channel,如果用戶量多的情況(客戶端數(shù)量過多),那么服務端的內(nèi)存將被消耗殆盡;
  4. 性能問題,每次服務端往客戶端推送消息,都需要從 Map 里面查找到對應的 Channel,如果數(shù)量比較大和查詢頻繁的情況下如何保證查詢性能;
  5. 安全性問題,HashMap 是線程不安全的,并發(fā)情況下,我們?nèi)绾稳ケWC線程安全;
  6. 身份校驗,如何 LoginHandler 是負責登錄認證的業(yè)務 Handler,AuthHandler 是負責每次請求時校驗該請求是否已經(jīng)認證了,這些 Handler 在鏈接就緒時已經(jīng)被添加到 Pipeline 管道當中,其實,我們可以采用熱插拔的方式去把一些在做業(yè)務操作時用不到的 Handler 給剔除掉。

以上是開發(fā)當中,需要去注意的點,當然還有很多其他的細節(jié),比如:線程池這塊,需要大家慢慢去從實戰(zhàn)中積累。

本節(jié)主要的內(nèi)容主要是在單聊和群聊基礎上完善幾點內(nèi)容,具體如下:

  1. 無論客戶端還是服務端都分別只有一個 Handler,這樣的話,業(yè)務越來越多,Handler 里面的代碼就會越來越臃腫,我們應該想辦法把 Handler 拆分成各個獨立的 Handler;
  2. 如何拆分的 Handler 很多,每次有連接進來,那么都會觸發(fā) initChannel () 方法,所有的 Handler 都得被 new 一遍,我們應該把這些 Handler 改成單例模式。 不需要每次都 new,提高效率;
  3. 發(fā)送消息,無論是單聊還是群聊,對方不在線,則把消息緩存起來,等待其上線再推送給他;
  4. 連接斷開,無論是主動和被動,需要刪除 Channel 屬性、刪除用戶和 Channel 映射關系。

3. 業(yè)務拆分以及單例模式

主要優(yōu)化細節(jié)如下:

  1. 自定義 Handler 繼承 SimpleChannelInboundHandler,那么解碼的時候,會自動根據(jù)數(shù)據(jù)格式類型轉(zhuǎn)到相應的 Handler 去處理;
  2. @Shareable 修飾 Handler,保證 Handler 是可共享的,避免每次都創(chuàng)建一個實例。

3.1 登錄 Handler

@ChannelHandler.Sharable
public class ClientLogin2Handler extends SimpleChannelInboundHandler<LoginResBean> {
    //1.構(gòu)造函數(shù)私有化,避免創(chuàng)建實體
   	private ClientLogin2Handler(){}
    //2.定義一個靜態(tài)全局變量
    public static ClientLogin2Handler instance=null;
    //3.獲取實體方法
    public static ClientLogin2Handler getInstance(){
        if(instance==null){
            synchronized (ClientLogin2Handler.class){
                if(instance==null){
                    instance=new ClientLogin2Handler();
                }
            }
        }
        return instance;
    }
    
    protected void channelRead0(
        ChannelHandlerContext channelHandlerContext, 
        LoginResBean loginResBean) throws Exception {
        
        //具體業(yè)務代碼,參考之前
    }
}

3.2 消息發(fā)送 Handler

@ChannelHandler.Sharable
public class ClientMsgHandler extends SimpleChannelInboundHandler<MsgResBean> {
    //1.構(gòu)造函數(shù)私有化,避免創(chuàng)建實體
   	private ClientMsgHandler(){}
    //2.定義一個靜態(tài)全局變量
    public static ClientMsgHandler instance=null;
    //3.獲取實體方法
    public static ClientMsgHandler getInstance(){
        if(instance==null){
            synchronized (ClientMsgHandler.class){
                if(instance==null){
                    instance=new ClientMsgHandler();
                }
            }
        }
        return instance;
    }
    
    protected void channelRead0(
        ChannelHandlerContext channelHandlerContext, 
        MsgResBean msgResBean) throws Exception {

        //具體業(yè)務代碼,參考之前
    }
}

3.3 initChannel 方法

.handler(new ChannelInitializer<SocketChannel>() {
    @Override
    public void initChannel(SocketChannel ch) {
        //1.拆包器
        ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,5,4));
		//2.解碼器
        ch.pipeline().addLast(new MyDecoder());
        //3.登錄Handler,使用單例獲取
        ch.pipeline().addLast(ClientLogin2Handler.getInstance());
        //4.消息發(fā)送Handler,使用單例獲取
        ch.pipeline().addLast(ClientMsgHandler.getInstance());
        //5.編碼器
        ch.pipeline().addLast(new MyEncoder());
    }
});

總結(jié),這種模式是開發(fā)當中常用的,可以更好的維護代碼以及提高應用性能。

4. 數(shù)據(jù)緩存

為了提高用戶體驗,在發(fā)送消息(推送消息)時,如果接收方不在線,則應該把消息緩存起來,等對方上線時,再推送給他。

4.1 數(shù)據(jù)緩存到集合

//1.定義一個集合存放數(shù)據(jù)(真實項目可以存放數(shù)據(jù)庫或者redis緩存),這樣數(shù)據(jù)比較安全。
private List<Map<Integer,String>> datas=new ArrayList<Map<Integer,String>>();

//2.服務端推送消息
private void pushMsg(MsgReqBean bean,Channel channel){
    Integer touserid=bean.getTouserid();
    Channel c=map.get(touserid);

    if(c==null){//對方不在線
        //2.1存放到list集合
        Map<Integer,String> data=new HashMap<Integer, String>();
        data.put(touserid,bean.getMsg());
		datas.add(data);
        
        //2.2.給消息“發(fā)送人”響應
        MsgResBean res=new MsgResBean();
        res.setStatus(1);
        res.setMsg(touserid+">>>不在線");
        channel.writeAndFlush(res);
        
    }else{//對方在線
        //2.3.給消息“發(fā)送人”響應
        MsgResBean res=new MsgResBean();
        res.setStatus(0);
        res.setMsg("發(fā)送成功);
        channel.writeAndFlush(res);
        
        //2.4.給接收人推送消息
        MsgRecBean res=new MsgRecBean();
        res.setFromuserid(bean.getFromuserid());
        res.setMsg(bean.getMsg());
        c.writeAndFlush(res);
    }
}

4.2 上線推送

private void login(LoginReqBean bean, Channel channel){
    Channel c=map.get(bean.getUserid());
    LoginResBean res=new LoginResBean();
    if(c==null){
        //1.添加到map
        map.put(bean.getUserid(),channel);
        //2.給通道賦值
        channel.attr(AttributeKey.valueOf("userid")).set(bean.getUserid());
        //3.登錄響應
        res.setStatus(0);
        res.setMsg("登錄成功");
        res.setUserid(bean.getUserid());
        channel.writeAndFlush(res);
        
        //4.根據(jù)user查找是否有尚未推送消息
		//思路:根據(jù)userid去lists查找.......
        
    }else{
        res.setStatus(1);
        res.setMsg("該賬戶目前在線");
        channel.writeAndFlush(res);
    }
}

5. 連接斷開事件處理

如果客戶端網(wǎng)絡故障導致連接斷開了(非主動下線),那么服務端就會監(jiān)聽到連接的斷開,此時應該刪除對應的 map 映射關系,否則影響客戶端的下次同一個賬號登錄,以及大量的客戶端掉線,但是映射關系沒有刪除掉,導致服務器資源沒有得到釋放。

5.1 正確寫法

實例:

public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {
    //映射關系
    private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();
    
    //連接斷開,觸發(fā)該事件
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        //1.獲取Channel
        Channel channel=ctx.channel();

        //2.從map里面,根據(jù)Channel找到對應的userid
        Integer userid=null;
        for(Map.Entry<Integer, Channel> entry : map.entrySet()){
            Integer uid=entry.getKey();
            Channel c=entry.getValue();
            if(c==channel){
                userid=uid;
            }
        }
		//3.如果userid不為空,則需要做以下處理
        if(userid!=null){
            //3.1.刪除映射
            map.remove(userid);
            //3.2.移除標識
            ctx.channel().attr(AttributeKey.valueOf("userid")).remove();
        }
    }
}

5.2 錯誤寫法

Channel 斷開,服務端監(jiān)聽到連接斷開事件,但是此時 Channel 所綁定的屬性已經(jīng)被移除掉了,因此這里無法直接獲取的到 userid。

實例:

public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {
    //映射關系
    private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();
    
    //連接斷開,觸發(fā)該事件
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        //1.獲取Channel綁定的userid
        Object userid=channel.attr(AttributeKey.valueOf("userid")).get();

        //2.如果userid不為空
        if(userid!=null){
            //1.刪除映射
            map.remove(userid);
            //2.移除標識
            ctx.channel().attr(AttributeKey.valueOf("userid")).remove();
        }
    }
}

6. 小結(jié)

本節(jié)內(nèi)容還是相對容易理解的,主要是優(yōu)化前面實現(xiàn)的聊天功能,主要優(yōu)化是業(yè)務 Handler 的拆分以及使用單例模式、接受人不在線則緩存數(shù)據(jù),等其上線再推送、監(jiān)聽連接斷開刪除對應的映射關系。