<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Jack Jiang

    我的最新工程MobileIMSDK:http://git.oschina.net/jackjiang/MobileIMSDK
    posts - 494, comments - 13, trackbacks - 0, articles - 1

    本文由作者“大白菜”分享,有較多修訂和改動。注意:本系列是給IM初學者的文章,IM老油條們還望海涵,勿噴!

    1、引言

    接上兩篇《IM系統設計篇》、《編碼實踐篇(單聊功能)》,本篇主要講解的是通過實戰編碼實現IM的群聊功能,內容涉及群聊技術實現原理、編碼實踐等知識。

    學習交流:

    本文已同步發布于:http://www.52im.net/thread-3981-1-1.html

    2、寫在前面

    建議你在閱讀本文之前,務必先讀本系列的前兩篇《IM系統設計篇》、《編碼實踐篇(單聊功能)》,在著重理解IM系統的理論設計思路之后,再來閱讀實戰代碼則效果更好。

    最后,在開始本文之前,請您務必提前了解Netty的相關基礎知識,可從本系列首篇《IM系統設計篇》中的“知識準備”一章開始。

    3、系列文章

    本文是系列文章的第3篇,以下是系列目錄:

    4、本篇概述

    在上篇《編碼實踐篇(單聊功能)》中,我們主要實現了IM的單聊功能,本節主要是實現IM群聊功能。

    本篇涉及的群聊核心功能,大致如下所示:

    • 1)登錄:每個客戶端連接服務端的時候,都需要輸入自己的賬號信息,以便和連接通道進行綁定;
    • 2)創建群組:輸入群組 ID 和群組名稱進行創建群組。需要先根據群組 ID 進行校驗,判斷是否已經存在了;
    • 3)查看群組:查看目前已經創建的群組列表;
    • 4)加入群組:主要參數是群組 ID 和用戶 ID,用戶 ID 只需從 Channel 的綁定屬性里面獲取即。主要是判斷群組 ID 是否存在,如果存在還需要判斷該用戶 ID 是否已經在群組里面了;
    • 5)退出群組:主要是判斷群組 ID 是否存在,如果存在則刪除相應的關系;
    • 6)查看組成員:根據群組 ID 去查詢對應的成員列表;
    • 7)群發消息:選擇某個群進行消息發送,該群下的成員都能收到信息。主要判斷群組 ID 是否存在,如果存在再去獲取其對應的成員列表。

    5、群聊原理

    其實群聊和單聊,整體上原理是一樣的,只是做了一下細節上的升級。

    在首篇《IM系統設計篇》的“6、IM群聊思路設計”設計部分也做了詳細的說明了。

    群聊的大概流程就是:根據群組 ID 查找到所有的成員集合,然后再遍歷找到每個成員對應的連接通道。

    具體的群聊架構思路如下圖:

    如上圖所示,群聊通訊流程技術原理如下:

    • 1)群聊和單聊整體上的思路一致:需要保存每個用戶和通道的對應關系,方便后期通過用戶 ID 去查找到對應的通道,再跟進通道推送消息;
    • 2)群聊把消息發送給群員的原理:其實很簡單,服務端再保存另外一份映射關系,那就是聊天室和成員的映射關系。發送消息時,首先根據聊天室 ID 找到對應的所有成員,然后再跟進各個成員的 ID 去查找到對應的通道,最后由每個通道進行消息的發送;
    • 3)群成員加入某個群聊聊的時候:往映射表新增一條記錄,如果成員退群的時候則刪除對應的映射記錄。

    6、運行效果

    補充說明:因為本系列文章主要目的是引導IM初學者在基于Netty的情況下,如何一步一步從零寫出IM的邏輯和思維能力,因而為了簡化編碼實現,本篇中編碼實現的客戶端都是基于控制臺實現的(希望不要被嫌棄),因為理解技術的本質顯然比炫酷的外在表現形式更為重要。

    用戶登錄效果圖:

    群組操作效果圖:

    7、實體定義實戰

    7.1 服務端實體

    服務端映射關系的管理,分別是:

    • 1)登錄信息(用戶 ID 和通道);
    • 2)群組信息(群組 ID 和群組成員關系)。

    主要通過兩個 Map 去維護,具體如下:

    public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {

        private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();

        private static Map<Integer, Group> groups=new HashMap<Integer, Group>();

    }

    //組和成員列表關系實體

    @Data

    public class Group implements Serializable {

        private String groupName;

        private List<GroupMember> members=new ArrayList<GroupMember>();

    }

    //成員和連接通道的關系實體

    public class GroupMember implements Serializable {

        private Integer userid;

        private Channel channel;

    }

    7.2 實體和指令關系

    我們準備好相應的實體,以及實體和指令的映射關系,具體如下所示:

    private static Map<Byte, Class<? extends BaseBean>> map=new HashMap<Byte,Class<? extends BaseBean>>();

        static{

            //登錄的請求和響應實體

            map.put(1, LoginReqBean.class);

            map.put(2, LoginResBean.class);

     

            //創建群組的請求和響應實體

            map.put(3, GroupCreateReqBean.class);

            map.put(4, GroupCreateResBean.class);

     

            //查看群組的請求和響應實體

            map.put(5, GroupListReqBean.class);

            map.put(6, GroupListResBean.class);

     

            //加入群組的請求和響應實體

            map.put(7,GroupAddReqBean.class);

            map.put(8,GroupAddResBean.class);

     

            //退出群組的請求和響應實體

            map.put(9,GroupQuitReqBean.class);

            map.put(10,GroupQuitResBean.class);

     

            //查看成員列表的請求和響應實體

            map.put(11,GroupMemberReqBean.class);

            map.put(12,GroupMemberResBean.class);

     

            //發送響應的實體(發送消息、發送響應、接受消息)

            map.put(13,GroupSendMsgReqBean.class);

            map.put(14,GroupSendMsgResBean.class);

            map.put(15,GroupRecMsgBean.class);

        }

    通過下面這張圖,能看的更清晰一些:

     

    8、Handler定義實戰

    IM群聊功能的實現,我們需要兩個兩個業務 Handler:

    • 1)分別是客戶端(ClientChatGroupHandler);
    • 2)服務端(ServerChatGroupHandler)。

    8.1 客戶端 Handler

    客戶端 Handler,主要是通過判斷實體類型來做不同的業務操作,當然也可以使用 SimpleChannelInboundHandler 去進行 Handler 拆分。

    public class ClientChatGroupHandler extends ChannelInboundHandlerAdapter {

        @Override

        public void channelActive(ChannelHandlerContext ctx) throws Exception {

            //在鏈接就緒時登錄

            login(ctx.channel());

        }

     

        //主要是“接受服務端”的響應信息

        @Override

        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

            if(msg instanceof LoginResBean){

                LoginResBean res=(LoginResBean) msg;

                System.out.println("登錄響應:"+res.getMsg());

                if(res.getStatus()==0){

                    //登錄成功

     

                    //1.給通道綁定身份

                    ctx.channel().attr(AttributeKey.valueOf("userid")).set(res.getUserid());

     

                    //2.顯示操作類型【請看下面】

                    deal(ctx.channel());

                }else{

                    //登錄失敗,繼續登錄

                    login(ctx.channel());

                }

            }else if(msg instanceof GroupCreateResBean){

                GroupCreateResBean res=(GroupCreateResBean)msg;

                System.out.println("創建響應群組:"+res.getMsg());

     

            }else if(msg instanceofGroupListResBean){

                GroupListResBean res=(GroupListResBean)msg;

                System.out.println("查看群組列表:"+res.getLists());

     

            }elseif(msg instanceofGroupAddResBean){

                GroupAddResBean res=(GroupAddResBean)msg;

                System.out.println("加入群組響應:"+res.getMsg());

     

            }elseif(msg instanceof GroupQuitResBean){

                GroupQuitResBean res=(GroupQuitResBean)msg;

                System.out.println("退群群組響應:"+res.getMsg());

     

            }else if(msg instanceof GroupMemberResBean){

                GroupMemberResBean res=(GroupMemberResBean)msg;

                if(res.getCode()==1){

                    System.out.println("查看成員列表:"+res.getMsg());

                }else{

                    System.out.println("查看成員列表:"+res.getLists());

                }

     

            }else if(msg instanceof GroupSendMsgResBean){

                GroupSendMsgResBean res=(GroupSendMsgResBean)msg;

                System.out.println("群發消息響應:"+res.getMsg());

     

            }else if(msg instanceof GroupRecMsgBean){

                GroupRecMsgBean res=(GroupRecMsgBean)msg;

                System.out.println("收到消息fromuserid="+

                                   res.getFromuserid()+

                                   ",msg="+res.getMsg());

            }

        }

    }

    通過子線程循環向輸出控制臺輸出操作類型的方法,以下方法目前都是空方法,下面將詳細講解。

    private void deal(final Channel channel){

            final Scanner scanner=new Scanner(System.in);

            new Thread(new Runnable() {

                public void run() {

                    while(true){

                        System.out.println("請選擇類型:0創建群組,1查看群組,2加入群組,3退出群組,4查看群成員,5群發消息");

                        int type=scanner.nextInt();

                        switch(type){

                            case 0:

                                createGroup(scanner,channel);

                                break;

                            case 1:

                                listGroup(scanner,channel);

                                break;

                            case 2:

                                addGroup(scanner,channel);

                                break;

                            case 3:

                                quitGroup(scanner,channel);

                                break;

                            case 4:

                                listMembers(scanner,channel);

                                break;

                            case 5:

                                sendMsgToGroup(scanner,channel);

                                break;

                            default:

                                System.out.println("輸入的類型不存在!");

                        }

                    }

                }

            }).start();

        }

    8.2 服務端 Handler

    服務端 Handler,主要是通過判斷實體類型來做不同的業務操作,當然也可以使用 SimpleChannelInboundHandler 去進行 Handler 拆分。

    以下方法目前都是空方法,下面將詳細講解。

    public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {

        private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();

        private static Map<Integer, Group> groups=new HashMap<Integer, Group>();

     

        @Override

        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

            if(msg instanceof LoginReqBean) {

                //登錄

                login((LoginReqBean) msg, ctx.channel());

            }else if(msg instanceof GroupCreateReqBean){

                //創建群組

                createGroup((GroupCreateReqBean)msg,ctx.channel());

            }else if(msg instanceof GroupListReqBean){

                //查看群組列表

                listGroup((GroupListReqBean)msg,ctx.channel());

            }else if(msg instanceof GroupAddReqBean){

                //加入群組

                addGroup((GroupAddReqBean)msg,ctx.channel());

            }else if(msg instanceof GroupQuitReqBean){

                //退出群組

                quitGroup((GroupQuitReqBean)msg,ctx.channel());

            }else if(msg instanceof GroupMemberReqBean){

                //查看成員列表

                listMember((GroupMemberReqBean)msg,ctx.channel());

            }else if(msg instanceof GroupSendMsgReqBean){

                //消息發送

                sendMsg((GroupSendMsgReqBean) msg,ctx.channel());

            }

        }

    }

    9、具體功能編碼實戰

    9.1 創建群組

    客戶端請求:

    private void createGroup(Scanner scanner,Channel channel){

            System.out.println("請輸入群組ID");

            Integer groupId=scanner.nextInt();

            System.out.println("請輸入群組名稱");

            String groupName=scanner.next();

     

            GroupCreateReqBean bean=new GroupCreateReqBean();

            bean.setGroupId(groupId);

            bean.setGroupName(groupName);

            channel.writeAndFlush(bean);

        }

    服務端處理:

    public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {

        private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();

        private static Map<Integer, Group> groups=new HashMap<Integer, Group>();

     

        private void createGroup(GroupCreateReqBean bean,Channel channel){

            //定義一個響應實體

            GroupCreateResBean res=new GroupCreateResBean();

            //查詢groups是否已經存在

            Group group=groups.get(bean.getGroupId());

            //判斷是否已經存在

            if(group==null){

                //定義群組實體

                Group g=new Group();

                //定義一個集合,專門存儲成員

                List<GroupMember> members=new ArrayList<GroupMember>();

                //屬性賦值

                g.setGroupName(bean.getGroupName());

                g.setMembers(members);

                //添加到Map里面

                groups.put(bean.getGroupId(),g);

     

                //響應信息

                res.setCode(0);

                res.setMsg("創建群組成功");

            }else{

                res.setCode(1);

                res.setMsg("該群組已經存在!");

            }

            channel.writeAndFlush(res);

        }

    }

    9.2 查看群組

    客戶端請求:

    private void listGroup(Scanner scanner,Channel channel){

        GroupListReqBean bean=new GroupListReqBean();

        bean.setType("list");

        channel.writeAndFlush(bean);

    }

    服務端處理:

    public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {

        private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();

        private static Map<Integer, Group> groups=new HashMap<Integer, Group>();

     

        private void listGroup(GroupListReqBean bean,Channel channel){

            if("list".equals(bean.getType())){

                //定義一個響應實體

                GroupListResBean res=new GroupListResBean();

                //定義一個集合

                List<GroupInfo> lists=new ArrayList<GroupInfo>();

                //變量groups Map集合

                for(Map.Entry<Integer, Group> entry : groups.entrySet()){

                    Integer mapKey = entry.getKey();

                    Group mapValue = entry.getValue();

                    GroupInfo gi=new GroupInfo();

                    gi.setGroupId(mapKey);

                    gi.setGroupName(mapValue.getGroupName());

                    lists.add(gi);

                }

                //把集合添加到響應實體里面

                res.setLists(lists);

                //開始寫到客戶端

                channel.writeAndFlush(res);

            }

        }

    }

    9.3 加入群組

    客戶端請求:

    private void addGroup(Scanner scanner,Channel channel){

        System.out.println("請輸入加入的群組ID");

        int groupId=scanner.nextInt();

        Integer userId=(Integer) channel.attr(AttributeKey.valueOf("userid")).get();

     

        GroupAddReqBean bean=new GroupAddReqBean();

        bean.setUserId(userId);

        bean.setGroupId(groupId);

        channel.writeAndFlush(bean);

    }

    服務端處理:

    public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {

        private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();

        private static Map<Integer, Group> groups=new HashMap<Integer, Group>();

     

        private void addGroup(GroupAddReqBean bean,Channel channel){

            GroupAddResBean res=new GroupAddResBean();

            //1.根據“群組ID”獲取對應的“組信息”

            Group group=groups.get(bean.getGroupId());

            //2.“群組”不存在

            if(group==null){

                res.setCode(1);

                res.setMsg("groupId="+bean.getGroupId()+",不存在!");

                channel.writeAndFlush(res);

                return;

            }

            //3.“群組”存在,則獲取其底下的“成員集合”

            List<GroupMember> members=group.getMembers();

            boolean flag=false;

            //4.遍歷集合,判斷“用戶”是否已經存在了

            for(GroupMember gm:members){

                if(gm.getUserid()==bean.getUserId()){

                    flag=true;

                    break;

                }

            }

            if(flag){

                res.setCode(1);

                res.setMsg("已經在群組里面,無法再次加入!");

            }else{

                //1.用戶信息

                GroupMember gm=new GroupMember();

                gm.setUserid(bean.getUserId());

                gm.setChannel(channel);

     

                //2.添加到集合里面

                members.add(gm);

     

                //3.給“群組”重新賦值

                group.setMembers(members);

     

                res.setCode(0);

                res.setMsg("加入群組成功");

            }

            channel.writeAndFlush(res);

        }

    }

    9.4 退出群組

    客戶端請求:

    private void quitGroup(Scanner scanner,Channel channel){

        System.out.println("請輸入退出的群組ID");

        int groupId=scanner.nextInt();

        Integer userId=(Integer) channel.attr(AttributeKey.valueOf("userid")).get();

     

        GroupQuitReqBean bean=new GroupQuitReqBean();

        bean.setUserId(userId);

        bean.setGroupId(groupId);

        channel.writeAndFlush(bean);

    }

    服務端處理:

    public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {

        private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();

        private static Map<Integer, Group> groups=new HashMap<Integer, Group>();

     

        private void quitGroup(GroupQuitReqBean bean,Channel channel){

            GroupQuitResBean res=new GroupQuitResBean();

     

            //1.根據“群組ID”獲取對應的“組信息”

            Group group=groups.get(bean.getGroupId());

            if(group==null){

                //2.群組不存在

                res.setCode(1);

                res.setMsg("groupId="+bean.getGroupId()+",不存在!");

                channel.writeAndFlush(res);

                return;

            }

            //3.群組存在,則獲取其底下“成員集合”

            List<GroupMember> members=group.getMembers();

            //4.遍歷集合,找到“當前用戶”在集合的序號

            int index=-1;

            for(inti=0;i<members.size();i++){

                if(members.get(i).getUserid()==bean.getUserId()){

                    index=i;

                    break;

                }

            }

            //5.如果序號等于-1,則表示“當前用戶”不存在集合里面

            if(index==-1){

                res.setCode(1);

                res.setMsg("userid="+bean.getUserId()+",不存在該群組里面!");

                channel.writeAndFlush(res);

                return;

            }

            //6.從集合里面刪除“當前用戶”

            members.remove(index);

            //7.給“群組”的“成員列表”重新賦值

            group.setMembers(members);

            res.setCode(0);

            res.setMsg("退出群組成功");

            channel.writeAndFlush(res);

        }

    }

    9.5 查看群組成員

    客戶端請求:

    private void listMembers(Scanner scanner,Channel channel){

        System.out.println("請輸入群組ID:");

        int groupId=scanner.nextInt();

     

        GroupMemberReqBean bean=new GroupMemberReqBean();

        bean.setGroupId(groupId);

        channel.writeAndFlush(bean);

    }

    服務端處理:

    public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {

        private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();

        private static Map<Integer, Group> groups=new HashMap<Integer, Group>();

     

        private void listMember(GroupMemberReqBean bean,Channel channel){

            GroupMemberResBean res=new GroupMemberResBean();

            List<Integer> lists=new ArrayList<Integer>();

            //1.根據“群組ID”獲取對應的“組信息”

            Group group=groups.get(bean.getGroupId());

            if(group==null){

                //2.查詢的群組不存在

                res.setCode(1);

                res.setMsg("groupId="+bean.getGroupId()+",不存在!");

                channel.writeAndFlush(res);

            }else{

                //3.群組存在,則變量其底層的成員

                for(Map.Entry<Integer, Group> entry : groups.entrySet()){

                    Group g = entry.getValue();

                    List<GroupMember> members=g.getMembers();

                    for(GroupMember gm:members){

                        lists.add(gm.getUserid());

                    }

                }

     

                res.setCode(0);

                res.setMsg("查詢成功");

                res.setLists(lists);

                channel.writeAndFlush(res);

             }

        }

    }

    9.6 群發消息

    客戶端請求:

    private void sendMsgToGroup(Scanner scanner,Channel channel){

        System.out.println("請輸入群組ID:");

        int groupId=scanner.nextInt();

     

        System.out.println("請輸入發送消息內容:");

        String msg=scanner.next();

     

        Integer userId=(Integer) channel.attr(AttributeKey.valueOf("userid")).get();

     

        GroupSendMsgReqBean bean=new GroupSendMsgReqBean();

        bean.setFromuserid(userId);

        bean.setTogroupid(groupId);

        bean.setMsg(msg);

        channel.writeAndFlush(bean);

    }

    服務端處理:

    public class ServerChatGroupHandler extends ChannelInboundHandlerAdapter {

        private static Map<Integer, Channel> map=new HashMap<Integer, Channel>();

        private static Map<Integer, Group> groups=new HashMap<Integer, Group>();

     

        privatevoidsendMsg(GroupSendMsgReqBean bean,Channel channel){

            GroupSendMsgResBean res=new GroupSendMsgResBean();

     

            //1.根據“群組ID”獲取對應的“組信息”

            Group group=groups.get(bean.getTogroupid());

     

            //2.給“發送人”響應,通知其發送的消息是否成功

            if(group==null){

                res.setCode(1);

                res.setMsg("groupId="+bean.getTogroupid()+",不存在!");

                channel.writeAndFlush(res);

                return;

            }else{

                res.setCode(0);

                res.setMsg("群發消息成功");

                channel.writeAndFlush(res);

            }

            //3.根據“組”下面的“成員”,變量并且逐個推送消息

            List<GroupMember> members=group.getMembers();

            for(GroupMember gm:members){

                GroupRecMsgBean rec=new GroupRecMsgBean();

                rec.setFromuserid(bean.getFromuserid());

                rec.setMsg(bean.getMsg());

                gm.getChannel().writeAndFlush(rec);

            }

        }

    }

    10、本篇小結

    本篇中涉及的功能點稍微有點多,主要是實現了群聊的幾個核心功能,分別是:創建群組、查看群組列表、加入群組、退出群組、查看成員列表、群發消息。

    這些功能經過拆解,看起來就不是那么復雜了,希望大家都可以親自動手實現一遍,加深理解,提高學習效果。

    實際上,真正的產品級IM中,群聊涉及的技術細節是非常多的,有興趣可以詳讀下面這幾篇:

    11、參考資料

    [1] 手把手教你用Netty實現心跳機制、斷線重連機制

    [2] 自已開發IM很難?手把手教你擼一個Andriod版IM

    [3] 基于Netty,從零開發一個IM服務端

    [4] 拿起鍵盤就是干,教你徒手開發一套分布式IM系統

    [5] 正確理解IM長連接、心跳及重連機制,并動手實現

    [6] 手把手教你用Go快速搭建高性能、可擴展的IM系統

    [7] 手把手教你用WebSocket打造Web端IM聊天

    [8] 萬字長文,手把手教你用Netty打造IM聊天

    [9] 基于Netty實現一套分布式IM系統

    [10] 基于Netty,搭建高性能IM集群(含技術思路+源碼)

    [11] SpringBoot集成開源IM框架MobileIMSDK,實現即時通訊IM聊天功能

    本文已同步發布于:http://www.52im.net/thread-3981-1-1.html



    作者:Jack Jiang (點擊作者姓名進入Github)
    出處:http://www.52im.net/space-uid-1.html
    交流:歡迎加入即時通訊開發交流群 215891622
    討論:http://www.52im.net/
    Jack Jiang同時是【原創Java Swing外觀工程BeautyEye】【輕量級移動端即時通訊框架MobileIMSDK】的作者,可前往下載交流。
    本博文 歡迎轉載,轉載請注明出處(也可前往 我的52im.net 找到我)。


    只有注冊用戶登錄后才能發表評論。


    網站導航:
     
    Jack Jiang的 Mail: jb2011@163.com, 聯系QQ: 413980957, 微信: hellojackjiang
    主站蜘蛛池模板: 成人免费毛片观看| 亚洲美日韩Av中文字幕无码久久久妻妇| 亚洲影视自拍揄拍愉拍| 国产青草视频免费观看97| 五月天婷婷免费视频| 在线观看亚洲一区二区| 国产免费av一区二区三区| 成人爽a毛片免费| 亚洲hairy多毛pics大全| 亚洲人成电影福利在线播放| 色吊丝永久在线观看最新免费| 一级特黄录像免费播放肥| 亚洲人成网站色在线观看| 亚洲五月综合缴情在线观看| 夫妻免费无码V看片| 在线人成免费视频69国产| 日本亚洲中午字幕乱码| 亚洲视频在线观看不卡| 亚洲福利精品一区二区三区| 国产情侣激情在线视频免费看| 精精国产www视频在线观看免费| 亚洲最大av资源站无码av网址| 亚洲第一极品精品无码久久| 在线观看亚洲免费| 日本h在线精品免费观看| 国产免费区在线观看十分钟| 亚洲精品无码中文久久字幕| 亚洲色四在线视频观看| 亚洲日本一区二区三区在线| 国产又粗又长又硬免费视频| 最近高清中文字幕无吗免费看| 黄色免费在线网站| 国产久爱免费精品视频| 色五月五月丁香亚洲综合网| 亚洲www在线观看| 67pao强力打造67194在线午夜亚洲| 狠狠色婷婷狠狠狠亚洲综合 | 午夜无码A级毛片免费视频| 成人福利在线观看免费视频| 亚洲中文字幕无码爆乳app| 亚洲熟妇无码久久精品|