精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

消息隊列輕松實現分布式 webSocket

云計算 分布式
如果我們的項目是分布式環境,登錄的用戶被Nginx的反向代理分配到多個不同服務器,那么在其中一個服務器建立了WebSocket連接的用戶如何給在另外一個服務器上建立了WebSocket連接的用戶發送消息呢?

上周知識星球中的球友問了一個關于websocket的問題,大致如下:

圖片圖片

簡單的概括一下:如果我們的項目是分布式環境,登錄的用戶被Nginx的反向代理分配到多個不同服務器,那么在其中一個服務器建立了WebSocket連接的用戶如何給在另外一個服務器上建立了WebSocket連接的用戶發送消息呢?

今天就來解答一下球友的問題:其實,要解決這個問題就需要實現分布式WebSocket,而分布式WebSocket一般可以通過以下兩種方案來實現:

  1. 將消息(<用戶id,消息內容>)統一推送到一個消息隊列(Redis、Kafka等)的的topic,然后每個應用節點都訂閱這個topic,在接收到WebSocket消息后取出這個消息的“消息接收者的用戶ID/用戶名”,然后再比對自身是否存在相應用戶的連接,如果存在則推送消息,否則丟棄接收到的這個消息(這個消息接收者所在的應用節點會處理)
  2. 在用戶建立WebSocket連接后,使用Redis緩存記錄用戶的WebSocket建立在哪個應用節點上,然后同樣使用消息隊列將消息推送到接收者所在的應用節點上面(實現上比方案一要復雜,但是網絡流量會更低)

實現方案

下面將以第一種方案來具體實現,實現方式如下

1. 定義一個WebSocket Channel枚舉類

public enum WebSocketChannelEnum {
    //測試使用的簡易點對點聊天
    CHAT("CHAT", "測試使用的簡易點對點聊天", "/topic/reply");
 
    WebSocketChannelEnum(String code, String description, String subscribeUrl) {
        this.code = code;
        this.description = description;
        this.subscribeUrl = subscribeUrl;
    }
 
    /**
     * 唯一CODE
     */
    private String code;
    /**
     * 描述
     */
    private String description;
    /**
     * WebSocket客戶端訂閱的URL
     */
    private String subscribeUrl;
 
    public String getCode() {
        return code;
    }
 
    public String getDescription() {
        return description;
    }
 
    public String getSubscribeUrl() {
        return subscribeUrl;
    }
 
    /**
     * 通過CODE查找枚舉類
     */
    public static WebSocketChannelEnum fromCode(String code){
        if(StringUtils.isNoneBlank(code)){
            for(WebSocketChannelEnum channelEnum : values()){
                if(channelEnum.code.equals(code)){
                    return channelEnum;
                }
            }
        }
 
        return null;
    }
 
}

2. 配置基于Redis的消息隊列

需要注意的是,在大中型正式項目中并不推薦使用Redis實現的消息隊列,因為經過測試它并不是特別可靠,所以應該考慮使用Kafka、rabbitMQ等專業的消息隊列中間件

@Configuration
@ConditionalOnClass({JedisCluster.class})
public class RedisConfig {
 
    @Value("${spring.redis.timeout}")
    private String timeOut;
 
    @Value("${spring.redis.cluster.nodes}")
    private String nodes;
 
    @Value("${spring.redis.cluster.max-redirects}")
    private int maxRedirects;
 
    @Value("${spring.redis.jedis.pool.max-active}")
    private int maxActive;
 
    @Value("${spring.redis.jedis.pool.max-wait}")
    private int maxWait;
 
    @Value("${spring.redis.jedis.pool.max-idle}")
    private int maxIdle;
 
    @Value("${spring.redis.jedis.pool.min-idle}")
    private int minIdle;
 
    @Value("${spring.redis.message.topic-name}")
    private String topicName;
 
    @Bean
    public JedisPoolConfig jedisPoolConfig(){
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(maxActive);
        config.setMaxIdle(maxIdle);
        config.setMinIdle(minIdle);
        config.setMaxWaitMillis(maxWait);
 
        return config;
    }
 
    @Bean
    public RedisClusterConfiguration redisClusterConfiguration(){
        RedisClusterConfiguration configuration = new RedisClusterConfiguration(Arrays.asList(nodes));
        configuration.setMaxRedirects(maxRedirects);
 
        return configuration;
    }
 
    /**
     * JedisConnectionFactory
     */
    @Bean
    public JedisConnectionFactory jedisConnectionFactory(RedisClusterConfiguration configuration,JedisPoolConfig jedisPoolConfig){
        return new JedisConnectionFactory(configuration,jedisPoolConfig);
    }
 
    /**
     * 使用Jackson序列化對象
     */
    @Bean
    public Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer(){
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
 
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(objectMapper);
 
        return serializer;
    }
 
    /**
     * RedisTemplate
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(JedisConnectionFactory factory, Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer){
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);
 
        //字符串方式序列化KEY
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
 
        //JSON方式序列化VALUE
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
 
        redisTemplate.afterPropertiesSet();
 
        return redisTemplate;
    }
 
    /**
     * 消息監聽器
     */
    @Bean
    MessageListenerAdapter messageListenerAdapter(MessageReceiver messageReceiver, Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer){
        //消息接收者以及對應的默認處理方法
        MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(messageReceiver, "receiveMessage");
        //消息的反序列化方式
        messageListenerAdapter.setSerializer(jackson2JsonRedisSerializer);
 
        return messageListenerAdapter;
    }
 
    /**
     * message listener container
     */
    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory
            , MessageListenerAdapter messageListenerAdapter){
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        //添加消息監聽器
        container.addMessageListener(messageListenerAdapter, new PatternTopic(topicName));
 
        return container;
    }
 
}

需要注意的是,這里使用的配置如下所示:

spring:
  ...
  #redis
  redis:
      cluster:
        nodes: namenode22:6379,datanode23:6379,datanode24:6379
        max-redirects: 6
      timeout: 300000
      jedis:
        pool:
          max-active: 8
          max-wait: 100000
          max-idle: 8
          min-idle: 0
      #自定義的監聽的TOPIC路徑
      message:
        topic-name: topic-test

3. 定義一個Redis消息的處理者

@Component
public class MessageReceiver {
    private final Logger logger = LoggerFactory.getLogger(getClass());
 
    @Autowired
    private SimpMessagingTemplate messagingTemplate;
 
    @Autowired
    private SimpUserRegistry userRegistry;
 
    /**
     * 處理WebSocket消息
     */
    public void receiveMessage(RedisWebsocketMsg redisWebsocketMsg) {
        logger.info(MessageFormat.format("Received Message: {0}", redisWebsocketMsg));
        //1. 取出用戶名并判斷是否連接到當前應用節點的WebSocket
        SimpUser simpUser = userRegistry.getUser(redisWebsocketMsg.getReceiver());
 
        if(simpUser != null && StringUtils.isNoneBlank(simpUser.getName())){
            //2. 獲取WebSocket客戶端的訂閱地址
            WebSocketChannelEnum channelEnum = WebSocketChannelEnum.fromCode(redisWebsocketMsg.getChannelCode());
 
            if(channelEnum != null){
                //3. 給WebSocket客戶端發送消息
                messagingTemplate.convertAndSendToUser(redisWebsocketMsg.getReceiver(), channelEnum.getSubscribeUrl(), redisWebsocketMsg.getContent());
            }
        }
 
    }
}

4. 在Controller中發送WebSocket消息

@Controller
@RequestMapping(("/wsTemplate"))
public class RedisMessageController {
    private final Logger logger = LoggerFactory.getLogger(getClass());
 
    @Value("${spring.redis.message.topic-name}")
    private String topicName;
 
    @Autowired
    private SimpMessagingTemplate messagingTemplate;
 
    @Autowired
    private SimpUserRegistry userRegistry;
 
    @Resource(name = "redisServiceImpl")
    private RedisService redisService;
 
    /**
     * 給指定用戶發送WebSocket消息
     */
    @PostMapping("/sendToUser")
    @ResponseBody
    public String chat(HttpServletRequest request) {
        //消息接收者
        String receiver = request.getParameter("receiver");
        //消息內容
        String msg = request.getParameter("msg");
        HttpSession session = SpringContextUtils.getSession();
        User loginUser = (User) session.getAttribute(Constants.SESSION_USER);
 
        HelloMessage resultData = new HelloMessage(MessageFormat.format("{0} say: {1}", loginUser.getUsername(), msg));
        this.sendToUser(loginUser.getUsername(), receiver, WebSocketChannelEnum.CHAT.getSubscribeUrl(), JsonUtils.toJson(resultData));
 
        return "ok";
    }
 
    /**
     * 給指定用戶發送消息,并處理接收者不在線的情況
     * @param sender 消息發送者
     * @param receiver 消息接收者
     * @param destination 目的地
     * @param payload 消息正文
     */
    private void sendToUser(String sender, String receiver, String destination, String payload){
        SimpUser simpUser = userRegistry.getUser(receiver);
 
        //如果接收者存在,則發送消息
        if(simpUser != null && StringUtils.isNoneBlank(simpUser.getName())){
            messagingTemplate.convertAndSendToUser(receiver, destination, payload);
        }
        //如果接收者在線,則說明接收者連接了集群的其他節點,需要通知接收者連接的那個節點發送消息
        else if(redisService.isSetMember(Constants.REDIS_WEBSOCKET_USER_SET, receiver)){
            RedisWebsocketMsg<String> redisWebsocketMsg = new RedisWebsocketMsg<>(receiver, WebSocketChannelEnum.CHAT.getCode(), payload);
 
            redisService.convertAndSend(topicName, redisWebsocketMsg);
        }
        //否則將消息存儲到redis,等用戶上線后主動拉取未讀消息
        else{
            //存儲消息的Redis列表名
            String listKey = Constants.REDIS_UNREAD_MSG_PREFIX + receiver + ":" + destination;
            logger.info(MessageFormat.format("消息接收者{0}還未建立WebSocket連接,{1}發送的消息【{2}】將被存儲到Redis的【{3}】列表中", receiver, sender, payload, listKey));
 
            //存儲消息到Redis中
            redisService.addToListRight(listKey, ExpireEnum.UNREAD_MSG, payload);
        }
 
    }
 
 
    /**
     * 拉取指定監聽路徑的未讀的WebSocket消息
     * @param destination 指定監聽路徑
     * @return java.util.Map<java.lang.String,java.lang.Object>
     */
    @PostMapping("/pullUnreadMessage")
    @ResponseBody
    public Map<String, Object> pullUnreadMessage(String destination){
        Map<String, Object> result = new HashMap<>();
        try {
            HttpSession session = SpringContextUtils.getSession();
            //當前登錄用戶
            User loginUser = (User) session.getAttribute(Constants.SESSION_USER);
 
            //存儲消息的Redis列表名
            String listKey = Constants.REDIS_UNREAD_MSG_PREFIX + loginUser.getUsername() + ":" + destination;
            //從Redis中拉取所有未讀消息
            List<Object> messageList = redisService.rangeList(listKey, 0, -1);
 
            result.put("code", "200");
            if(messageList !=null && messageList.size() > 0){
                //刪除Redis中的這個未讀消息列表
                redisService.delete(listKey);
                //將數據添加到返回集,供前臺頁面展示
                result.put("result", messageList);
            }
        }catch (Exception e){
            result.put("code", "500");
            result.put("msg", e.getMessage());
        }
 
        return result;
    }
 
}

5. WebSocket相關配置

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer{
    @Autowired
    private AuthHandshakeInterceptor authHandshakeInterceptor;
 
    @Autowired
    private MyHandshakeHandler myHandshakeHandler;
 
    @Autowired
    private MyChannelInterceptor myChannelInterceptor;
 
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/chat-websocket")
                .addInterceptors(authHandshakeInterceptor)
                .setHandshakeHandler(myHandshakeHandler)
                .withSockJS();
    }
 
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        //客戶端需要把消息發送到/message/xxx地址
        registry.setApplicationDestinationPrefixes("/message");
        //服務端廣播消息的路徑前綴,客戶端需要相應訂閱/topic/yyy這個地址的消息
        registry.enableSimpleBroker("/topic");
        //給指定用戶發送消息的路徑前綴,默認值是/user/
        registry.setUserDestinationPrefix("/user/");
    }
 
    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(myChannelInterceptor);
    }
 
}

6. 示例頁面

<head>
    <meta content="text/html;charset=UTF-8"/>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <title>Chat With STOMP Message</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.1.4/sockjs.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
    <script th:src="@{/layui/layui.js}"></script>
    <script th:src="@{/layui/lay/modules/layer.js}"></script>
    <link th:href="@{/layui/css/layui.css}" rel="stylesheet">
    <link th:href="@{/layui/css/modules/layer/default/layer.css}" rel="stylesheet">
    <link th:href="@{/css/style.css}" rel="stylesheet">
    <style type="text/css">
        #connect-container {
            margin: 0 auto;
            width: 400px;
        }
 
        #connect-container div {
            padding: 5px;
            margin: 0 7px 10px 0;
        }
 
        .message input {
            padding: 5px;
            margin: 0 7px 10px 0;
        }
 
        .layui-btn {
            display: inline-block;
        }
    </style>
    <script type="text/javascript">
        var stompClient = null;
 
        $(function () {
            var target = $("#target");
            if (window.location.protocol === 'http:') {
                target.val('http://' + window.location.host + target.val());
            } else {
                target.val('https://' + window.location.host + target.val());
            }
        });
 
        function setConnected(connected) {
            var connect = $("#connect");
            var disconnect = $("#disconnect");
            var echo = $("#echo");
 
            if (connected) {
                connect.addClass("layui-btn-disabled");
                disconnect.removeClass("layui-btn-disabled");
                echo.removeClass("layui-btn-disabled");
            } else {
                connect.removeClass("layui-btn-disabled");
                disconnect.addClass("layui-btn-disabled");
                echo.addClass("layui-btn-disabled");
            }
 
            connect.attr("disabled", connected);
            disconnect.attr("disabled", !connected);
            echo.attr("disabled", !connected);
        }
 
        //連接
        function connect() {
            var target = $("#target").val();
 
            var ws = new SockJS(target);
            stompClient = Stomp.over(ws);
 
            stompClient.connect({}, function () {
                setConnected(true);
                log('Info: STOMP connection opened.');
 
                //連接成功后,主動拉取未讀消息
                pullUnreadMessage("/topic/reply");
 
                //訂閱服務端的/topic/reply地址
                stompClient.subscribe("/user/topic/reply", function (response) {
                    log(JSON.parse(response.body).content);
                })
            },function () {
                //斷開處理
                setConnected(false);
                log('Info: STOMP connection closed.');
            });
        }
 
        //斷開連接
        function disconnect() {
            if (stompClient != null) {
                stompClient.disconnect();
                stompClient = null;
            }
            setConnected(false);
            log('Info: STOMP connection closed.');
        }
 
        //向指定用戶發送消息
        function sendMessage() {
            if (stompClient != null) {
                var receiver = $("#receiver").val();
                var msg = $("#message").val();
                log('Sent: ' + JSON.stringify({'receiver': receiver, 'msg':msg}));
 
                $.ajax({
                    url: "/wsTemplate/sendToUser",
                    type: "POST",
                    dataType: "json",
                    async: true,
                    data: {
                        "receiver": receiver,
                        "msg": msg
                    },
                    success: function (data) {
 
                    }
                });
            } else {
                layer.msg('STOMP connection not established, please connect.', {
                    offset: 'auto'
                    ,icon: 2
                });
            }
        }
 
        //從服務器拉取未讀消息
        function pullUnreadMessage(destination) {
            $.ajax({
                url: "/wsTemplate/pullUnreadMessage",
                type: "POST",
                dataType: "json",
                async: true,
                data: {
                    "destination": destination
                },
                success: function (data) {
                    if (data.result != null) {
                        $.each(data.result, function (i, item) {
                            log(JSON.parse(item).content);
                        })
                    } else if (data.code !=null && data.code == "500") {
                        layer.msg(data.msg, {
                            offset: 'auto'
                            ,icon: 2
                        });
                    }
                }
            });
        }
 
        //日志輸出
        function log(message) {
            console.debug(message);
        }
    </script>
</head>
<body>
    <noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websockets rely on Javascript being
        enabled. Please enable
        Javascript and reload this page!</h2></noscript>
    <div>
        <div id="connect-container" class="layui-elem-field">
            <legend>Chat With STOMP Message</legend>
            <div>
                <input id="target" type="text" class="layui-input" size="40" style="width: 350px" value="/chat-websocket"/>
            </div>
            <div>
                <button id="connect" class="layui-btn layui-btn-normal" onclick="connect();">Connect</button>
                <button id="disconnect" class="layui-btn layui-btn-normal layui-btn-disabled" disabled="disabled"
                        onclick="disconnect();">Disconnect
                </button>
 
            </div>
            <div class="message">
                <input id="receiver" type="text" class="layui-input" size="40" style="width: 350px" placeholder="接收者姓名" value=""/>
                <input id="message" type="text" class="layui-input" size="40" style="width: 350px" placeholder="消息內容" value=""/>
            </div>
            <div>
                <button id="echo" class="layui-btn layui-btn-normal layui-btn-disabled" disabled="disabled"
                        onclick="sendMessage();">Send Message
                </button>
            </div>
        </div>
    </div>
</body>
</html>

責任編輯:武曉燕 來源: 碼猿技術專欄
相關推薦

2023-07-26 07:28:55

WebSocket服務器方案

2022-06-28 08:37:07

分布式服務器WebSocket

2024-11-14 11:56:45

2022-12-13 09:19:26

分布式消息隊列

2024-06-07 08:06:36

2015-11-02 16:38:09

C#分布式消息隊列

2024-06-13 09:34:35

JWTTokenSpring

2024-06-06 08:40:07

2017-07-27 14:32:05

大數據分布式消息Kafka

2022-06-27 08:21:05

Seata分布式事務微服務

2021-10-30 19:30:23

分布式Celery隊列

2023-11-01 18:02:33

RayPython分布式

2020-09-23 09:52:01

分布式WebSocketMQ

2022-01-10 11:58:51

SpringBootPulsar分布式

2019-09-05 09:02:45

消息系統緩存高可用

2016-09-23 10:51:23

騰訊云

2018-07-31 16:29:57

京東云

2024-08-02 09:00:17

NettyWebSocketNIO

2013-12-18 17:10:56

分布式多核

2023-05-12 08:02:43

分布式事務應用
點贊
收藏

51CTO技術棧公眾號

日韩在线综合网| 91视频99| 情侣偷拍对白清晰饥渴难耐| 在线成人免费| 婷婷国产v国产偷v亚洲高清| 日韩激情视频| 国产高潮流白浆喷水视频| 悠悠资源网久久精品| 亚洲视频欧美视频| 超碰中文字幕在线观看| 久久电影tv| 亚洲精品中文在线影院| 久久成人资源| 国产富婆一级全黄大片| 石原莉奈在线亚洲三区| xxx欧美精品| 亚洲av片不卡无码久久| 另类视频一区二区三区| 色综合欧美在线| 亚洲高潮无码久久| 国产区视频在线播放| 成人h精品动漫一区二区三区| 国产精品免费久久久| 国产又大又黑又粗免费视频| 91精品在线观看国产| 亚洲欧美另类中文字幕| 图片区偷拍区小说区| 不卡亚洲精品| 色婷婷亚洲综合| 僵尸世界大战2 在线播放| 日本综合在线| 国产欧美日产一区| 久久精品中文字幕一区二区三区| 99久久精品国产一区二区成人| 老司机午夜精品视频在线观看| 久久久噜噜噜久久中文字免| 青娱乐在线视频免费观看| 成人情趣视频网站| 国产亚洲福利一区| 国产制服丝袜在线| 美女呻吟一区| 亚洲精品久久久久国产| 成人黄色一级大片| 国产亚洲精彩久久| 欧美午夜免费电影| 日韩视频在线免费看| 久草在线中文最新视频| 亚洲高清视频在线| 欧美久久久久久久久久久久久久| 老司机av在线免费看| 国产精品久久久久久久久免费相片| 开心色怡人综合网站| 少妇高潮一区二区三区69| 国产成人av福利| 不卡一区二区三区四区五区| 亚洲国产精品久久久久久6q| 国产精品亚洲一区二区三区在线| 亚洲一区二区三区视频播放| 99久久精品日本一区二区免费| 精品一区二区三区免费| 亚洲自拍另类欧美丝袜| 国产丰满果冻videossex| 国产91在线|亚洲| 国产乱码精品一区二区三区日韩精品| 国 产 黄 色 大 片| 99热这里都是精品| 欧美日本国产精品| 午夜国产福利在线| 亚洲免费资源在线播放| 欧美日韩dvd| 岛国av在线网站| 日韩欧美在线一区| 色婷婷综合网站| 99久热在线精品视频观看| 91精品福利在线一区二区三区| 久久久久久国产精品日本| 哺乳一区二区三区中文视频| 亚洲精品福利在线观看| 五月天精品视频| 四虎8848精品成人免费网站| 欧美精品手机在线| 久久久久久久久影院| 日韩电影一二三区| 91网站在线看| 四虎成人免费在线| 国产精品卡一卡二卡三| 久久这里只有精品18| 最新中文字幕在线播放| 欧美日韩亚洲高清一区二区| 亚洲成人av免费观看| 欧美男人操女人视频| 中文字幕欧美精品日韩中文字幕| 欧美精品videos极品| 先锋影音久久| 成人性生交大片免费看视频直播 | 色av成人天堂桃色av| 天天影视色综合| 久久免费视频66| 日韩亚洲欧美成人| 免费观看成人毛片| 国产综合色产在线精品| 蜜桃传媒视频麻豆一区 | 欧美激情精品在线| 日韩中文字幕高清| 成人精品国产一区二区4080| 亚洲mv在线看| 538在线视频| 欧美精品久久99| 成年人网站免费在线观看| 亚洲精品国产偷自在线观看| 国产99久久精品一区二区永久免费| 国产精品欧美亚洲| 久久九九99视频| 久久国产精品网| 国产精品**亚洲精品| 亚洲午夜激情免费视频| 久久精品视频日本| 国产一区视频导航| 色一情一乱一伦一区二区三欧美 | 亚洲一区二区偷拍| 经典一区二区| 97久久超碰福利国产精品…| 91免费视频播放| 久久久久久免费毛片精品| 成人午夜精品久久久久久久蜜臀| 日本国产亚洲| 尤物tv国产一区| 日韩精品一区二区三区视频 | 成a人v在线播放| 国产欧美日韩在线看| 鲁一鲁一鲁一鲁一色| 视频一区视频二区欧美| 日韩中文字幕精品| 中文永久免费观看| 国产日产欧美精品一区二区三区| 欧美,日韩,国产在线| 综合激情五月婷婷| 精品中文字幕在线| 99国产在线播放| 日韩一区中文字幕| 91亚洲精品久久久蜜桃借种| 日韩大片在线观看| 国产精品久久久久久五月尺| 国外av在线| 欧美性开放视频| 国产呦小j女精品视频| 国产欧美精品久久| 久久精品成人一区二区三区蜜臀| 两个人看的在线视频www| 欧美精品一区二区在线播放| 国产第一页在线播放| 成人午夜视频福利| 国产素人在线观看| 欧洲亚洲一区二区三区| 欧美亚洲视频在线观看| 欧美18xxxxx| 91国偷自产一区二区使用方法| 亚洲国产av一区| 日本午夜精品视频在线观看| 亚洲草草视频| 久久国产精品美女| 九色成人免费视频| 欧美一级淫片免费视频魅影视频| 亚洲国产日韩a在线播放| 伊人网综合视频| 校园春色综合网| 特级西西444www大精品视频| 精品久久福利| 欧美多人爱爱视频网站| 亚洲av激情无码专区在线播放| 色综合久久久久综合体| 亚洲天堂精品一区| 国产激情一区二区三区| 国产精品333| 三级电影一区| 俄罗斯精品一区二区| 蜜桃视频m3u8在线观看| 国产一区二区三区网站| 国产视频一二三四区| 亚洲高清一区二区三区| 国产一区二区三区精品在线| 国产一区二区在线电影| 欧美成人精品免费| 国产精品免费不| 亚洲一区二区三区四区视频| 91色在线看| 在线观看中文字幕亚洲| 亚洲第一视频在线| 色88888久久久久久影院野外 | 欧美一区二区三区成人片在线| 欧美性生交xxxxxdddd| av黄色免费在线观看| 成人av在线播放网址| 日本黄大片一区二区三区| 亚洲无线一线二线三线区别av| 欧美精品中文字幕一区二区| 精品视频一区二区三区在线观看| 欧美综合一区第一页| 久草中文在线| 亚洲网址你懂得| 日本高清视频免费看| 欧美精品777| 在线免费黄色av| 一区二区三区日韩精品视频| 一区二区三区久久久久| 国产成人aaa| 嫩草影院国产精品| aa国产精品| 国产制服91一区二区三区制服| 一区三区在线欧| 成人91免费视频| 欧洲精品久久久久毛片完整版| 97精品一区二区三区| 69成人在线| 中文字幕在线成人| 免费看男男www网站入口在线| 精品日韩成人av| 国产精品国产三级国产普通话对白| 精品久久在线播放| 一区二区三区免费高清视频| 国产精品高潮呻吟| 久久国产柳州莫菁门| 成人激情文学综合网| 992kp免费看片| 麻豆免费看一区二区三区| 男人揉女人奶房视频60分| 国色天香一区二区| 最新中文字幕久久| 成人羞羞网站入口免费| 欧美精品成人一区二区在线观看| av综合网页| 99精彩视频| 麻豆一二三区精品蜜桃| 国产免费一区视频观看免费 | 成人av在线网址| 婷婷激情一区| 亲爱的老师9免费观看全集电视剧| √天堂8资源中文在线| 欧美激情国产精品| dy888亚洲精品一区二区三区| 中文字幕欧美亚洲| 电影在线一区| 中文字幕一区二区三区电影| av片在线免费观看| 在线视频日韩精品| 国产日本在线观看| 少妇激情综合网| 日本视频在线播放| 日韩视频在线观看免费| 欧美日韩在线资源| 久久视频在线看| 黄色视屏免费在线观看| 久久精品男人天堂| 麻豆av在线免费看| 久久99国产综合精品女同| 天堂av资源在线观看| 欧美激情视频一区二区三区不卡 | 日本一区二区免费高清| 特级西西444www大精品视频| 99视频精品全部免费在线视频| 精品久久免费观看| 欧美~级网站不卡| 日韩精品免费一区| 国产亚洲精品v| 中文字幕在线观看第三页| 日本 国产 欧美色综合| 想看黄色一级片| 成人avav在线| 国产av自拍一区| 亚洲视频小说图片| 久久综合成人网| 91久久免费观看| 国产一区二区波多野结衣| 日韩欧美国产不卡| 黄色片一区二区三区| 亚洲色图av在线| 尤物网在线观看| 欧美极品美女视频网站在线观看免费| 男女羞羞视频在线观看| 日本精品免费一区二区三区| 亚洲成人精品综合在线| 国产日韩久久| 操欧美老女人| 黄色一级片黄色| 米奇777在线欧美播放| 一级网站在线观看| 久久久精品黄色| 亚洲欧美一区二区三区四区五区| 精品动漫一区二区| 怡红院成永久免费人全部视频| 精品久久久久久久久久久久久久久| 欧美色18zzzzxxxxx| 精品国产视频在线| 日本在线啊啊| 91在线播放国产| 午夜欧洲一区| 日韩video| 日本亚洲视频在线| 国产精品久久久久久在线观看| 国产精品网站导航| 日本天堂在线视频| 欧美日韩高清一区二区不卡| 完全免费av在线播放| 亚洲欧美自拍另类日韩| 成人免费高清在线观看| 国产极品视频在线观看| 欧美午夜片在线免费观看| 国产免费无遮挡| 亚洲一品av免费观看| av资源在线看片| 亚洲综合精品一区二区| 日本在线电影一区二区三区| 欧美久久久久久久久久久久久 | 91亚洲人电影| 日韩欧美一区二区三区在线视频 | 日本婷婷久久久久久久久一区二区| 欧洲一区在线观看| 国产一级aa大片毛片| 欧美视频中文字幕| 日韩电影网址| 欧美精品videossex性护士| 国产成+人+综合+亚洲欧美| 精品一卡二卡三卡四卡日本乱码| 影音先锋成人在线电影| 五月天av在线播放| 国产欧美一区二区精品婷婷| 日韩精品在线免费看| 欧美一级片在线观看| 成人免费在线电影| 国产精品流白浆视频| 亚洲综合图色| 久久久999视频| 91香蕉视频mp4| 在线看成人av| 精品毛片乱码1区2区3区| 成人影院在线看| 91久久中文字幕| 国产高清久久| 亚洲va在线va天堂va偷拍| 国产精品麻豆视频| 在线观看中文字幕av| 一区二区欧美在线| 国产精品成人国产| 视频一区二区综合| 免费在线观看成人| 美女100%露胸无遮挡| 欧美视频精品在线观看| 97超碰国产一区二区三区| 国产精品吹潮在线观看| 成人三级视频| 日本美女视频一区| 亚洲欧美二区三区| 不卡的日韩av| 欧美激情一区二区三级高清视频 | 6080日韩午夜伦伦午夜伦| 69视频在线| 99热国产免费| 日韩视频一区| 熟女俱乐部一区二区视频在线| 色综合av在线| 在线免费观看黄| 91系列在线观看| 亚洲一级一区| 亚欧洲乱码视频| 欧美日韩一区二区三区高清| 日本三级视频在线观看| 超碰国产精品久久国产精品99| 亚洲精品资源| 国产jjizz一区二区三区视频| 欧美色成人综合| 在线免费观看的av| 精品久久久久久综合日本 | 热久久这里只有精品| 欧美日韩国产免费观看视频| 中文字幕在线视频精品| 亚洲自拍偷拍图区| 国产综合视频一区二区三区免费| 国产精品人成电影| 欧美三级不卡| mm131丰满少妇人体欣赏图| 欧美日韩精品二区第二页| 视频在线这里都是精品| 欧美日韩国产免费一区二区三区 | 日本一区二区在线观看视频| 日韩欧美国产一区二区| 亚洲视频tv| 国产伦精品一区二区三区视频黑人 | 26uuu另类欧美| 国产又黄又大又爽| 国内精品久久影院| 色中色综合网| 亚洲蜜桃精久久久久久久久久久久| 欧美日韩一二三区| jizz一区二区三区| 亚洲激情电影在线| www.亚洲国产| 国产精品国产三级国产普通话对白| 91极品视频在线| 亚洲老妇激情| 性猛交ⅹxxx富婆video|