亚洲有码Av一区二区三区_国产高清啪啪免费视频_69色视频国产_国产成人人人爆出白浆_国产精品自在线拍国_一本久久伊人热热精品无码_午夜性刺激在线看免费带字幕_助力高品质欧美狂喷水_亚洲精品日韩无码_精品无码一区二区三区蜜臀_麻豆高清国产AV_熟妇人素无码中文字幕_亚洲a级片在线观看_国产欧美日韩三区_99国产成人高清在线观看

ARTICLE DETAIL

資訊詳情

深耕商務(wù)建站與企業(yè)官網(wǎng)運(yùn)營(yíng)的一線實(shí)戰(zhàn)洞察。

Spring Boot WebSocket客戶端同步回調(diào)實(shí)現(xiàn)

Spring Boot WebSocket客戶端同步回調(diào)實(shí)現(xiàn) 這標(biāo)題看著長(zhǎng)其實(shí)拆開(kāi)就三層Spring Boot 里起一個(gè) WebSocket 服務(wù)端再內(nèi)嵌一個(gè) WebSocket 客戶端然后讓這個(gè)客戶端具備“發(fā)一條消息、同步等結(jié)果”的能力。別小看最后這點(diǎn)大部分網(wǎng)上的示例都卡在廣播和聊天室的階段真正拿進(jìn)業(yè)務(wù)系統(tǒng)里用的時(shí)候誰(shuí)都需要“我這端發(fā)出去一條指令對(duì)面到底處理成什么樣了”這種確定性的返回。這篇文章就把完整做法拆開(kāi)講透從依賴配置、端點(diǎn)編寫、會(huì)話管理到客戶端封裝、同步回調(diào)協(xié)議、超時(shí)清理和重連?;钊拷o出可直接抄的代碼和參數(shù)建議。適合正在做即時(shí)通訊、設(shè)備指令下發(fā)或者想把長(zhǎng)連接能力接進(jìn) Spring Boot 業(yè)務(wù)代碼的朋友。1. 項(xiàng)目?jī)r(jià)值拆解為什么“同步回調(diào)”才是真正的業(yè)務(wù)剛需1.1 這個(gè)項(xiàng)目到底解決了什么問(wèn)題普通的 HTTP 接口天然就是“請(qǐng)求-響應(yīng)”模型你調(diào)一次接口拿到返回值事情就結(jié)束了。WebSocket 不一樣它是全雙工長(zhǎng)連接消息推出去之后對(duì)面的響應(yīng)什么時(shí)候回來(lái)、回沒(méi)回來(lái)都是異步事件。這就帶來(lái)一個(gè)很現(xiàn)實(shí)的矛盾Spring Boot 的業(yè)務(wù)代碼大部分是同步風(fēng)格Service 層調(diào)一個(gè)方法、拿到返回值、再往下走大家早就習(xí)慣了這種寫法。如果 WebSocket 做成了“發(fā)消息 監(jiān)聽(tīng)回調(diào)”業(yè)務(wù)方就要被拆成兩段一段負(fù)責(zé)發(fā)一段負(fù)責(zé)在回調(diào)里繼續(xù)執(zhí)行代碼邏輯四分五裂排查問(wèn)題也很痛苦。標(biāo)題里的“客戶端消息同步回調(diào)”解決的就是這個(gè)矛盾對(duì)外暴露的接口仍然像普通方法一樣調(diào)用方傳參、等待、拿結(jié)果內(nèi)部則通過(guò) WebSocket 長(zhǎng)連接異步收發(fā)再靠請(qǐng)求 ID 把響應(yīng)和請(qǐng)求重新對(duì)齊。說(shuō)白了這是把異步通道“偽裝”回同步語(yǔ)義讓長(zhǎng)連接能力和常規(guī)業(yè)務(wù)代碼無(wú)縫銜接。我見(jiàn)過(guò)不少團(tuán)隊(duì)一開(kāi)始做長(zhǎng)連接模塊都是先寫廣播 demo結(jié)果一接到真實(shí)業(yè)務(wù)里就發(fā)現(xiàn)各種不對(duì)勁核心原因就是沒(méi)想清楚“請(qǐng)求-響應(yīng)要對(duì)得上號(hào)”這件事。這個(gè)項(xiàng)目本質(zhì)上解決的不是連接問(wèn)題而是“確定性”問(wèn)題。1.2 三個(gè)典型業(yè)務(wù)場(chǎng)景我舉三個(gè)最常見(jiàn)的場(chǎng)景基本覆蓋了標(biāo)題里這套方案會(huì)被用到的位置。第一個(gè)是設(shè)備指令下發(fā)。管理平臺(tái)要控制一臺(tái)終端設(shè)備比如“開(kāi)啟攝像頭”“重啟服務(wù)”“調(diào)整參數(shù)”這種指令光發(fā)出去不算完你一定想知道設(shè)備端是否收到、是否執(zhí)行成功、如果失敗了錯(cuò)誤信息是什么。只有同步回調(diào)才能讓指令下發(fā)服務(wù)保持業(yè)務(wù)一致性不然指令狀態(tài)就只能靠輪詢數(shù)據(jù)庫(kù)去猜效率低而且實(shí)時(shí)性差。第二個(gè)是在線用戶操作回執(zhí)。IM 系統(tǒng)里不僅有聊天消息還有“拉黑”“撤回”“已讀回執(zhí)”這類帶狀態(tài)的操作。服務(wù)端需要向指定的在線連接推送一條指令并等待用戶客戶端應(yīng)答。這種場(chǎng)景如果做成異步廣播你根本不知道被拉黑的那個(gè)用戶到底有沒(méi)有收到很多業(yè)務(wù)邏輯就沒(méi)法收斂。第三個(gè)是實(shí)時(shí)業(yè)務(wù)場(chǎng)景比如游戲?qū)纸Y(jié)算、交易確認(rèn)、協(xié)同編輯的版本同步。這些業(yè)務(wù)對(duì)結(jié)果返回的實(shí)時(shí)性要求極高HTTP 輪詢的延遲和開(kāi)銷都不合適WebSocket 長(zhǎng)連接加上同步回調(diào)能把原先“發(fā)了不管”的廣播式通信固化成一個(gè)帶結(jié)果的調(diào)用業(yè)務(wù)代碼寫起來(lái)也自然很多??梢哉f(shuō)只要你的系統(tǒng)里存在“在線設(shè)備或用戶 需要知道處理結(jié)果”的組合這套方案就是繞不開(kāi)的。1.3 標(biāo)題里的三層架構(gòu)每一層都缺一不可標(biāo)題雖然長(zhǎng)但核心架構(gòu)其實(shí)只有三層。服務(wù)端層負(fù)責(zé)連接管理、會(huì)話存儲(chǔ)、消息接收與推送解決的是“誰(shuí)能連、連上了往哪發(fā)”的問(wèn)題??蛻舳藢邮乔度?Spring Boot 工程里的一個(gè) Java WebSocket 客戶端組件它對(duì)外暴露業(yè)務(wù)方法對(duì)內(nèi)維護(hù)長(zhǎng)連接狀態(tài)解決的是“業(yè)務(wù)代碼怎么調(diào)用”的問(wèn)題。同步回調(diào)層則是一套消息協(xié)議和等待機(jī)制通過(guò)請(qǐng)求 ID 把響應(yīng)消息和正在等待的請(qǐng)求重新配對(duì)解決的是“結(jié)果怎么回到調(diào)用方手里”的問(wèn)題。三層缺了任何一層都不完整沒(méi)有服務(wù)端客戶端沒(méi)東西可連沒(méi)有客戶端業(yè)務(wù)代碼就得裸寫一堆 WebSocket 回調(diào)代碼難看還難維護(hù)沒(méi)有同步回調(diào)前面做的所有連接工作都停留在“能通”的水平業(yè)務(wù)根本沒(méi)法用起來(lái)。所以這篇博文會(huì)按這三層逐步拆開(kāi)寫順序剛好就是標(biāo)題的閱讀順序。2. 技術(shù)選型與設(shè)計(jì)思路哪些方案能選、我為什么這樣選2.1 服務(wù)端集成 WebSocket 的三種主流姿勢(shì)Spring Boot 里做 WebSocket 服務(wù)端最常見(jiàn)的有三種路子基于ServerEndpoint注解、基于 Spring 的WebSocketHandler注冊(cè)、以及直接集成 Netty。我給它們拉了張對(duì)比表只看它們?cè)凇翱焖偌伞边@個(gè)場(chǎng)景下的表現(xiàn)方案上手難度代碼量生命周期處理適合場(chǎng)景ServerEndpoint注解低最少注解自帶 OnOpen/OnMessage/OnClose/OnError大多數(shù)業(yè)務(wù)項(xiàng)目快速接入沒(méi)問(wèn)題SpringWebSocketHandler中中等實(shí)現(xiàn)接口方法需要和 Spring MVC 深度整合、做攔截握手時(shí)集成 Netty高多自己管理通道和線程模型超大規(guī)模連接、TCP 層定制要求很高時(shí)標(biāo)題里明確寫了“快速集成”那我的答案很直接選ServerEndpoint注解方案。原因不復(fù)雜一是它完全由 Java 生態(tài)規(guī)范定義和容器兼容性好代碼量最少二是每個(gè)生命周期回調(diào)都有明確的注解位置不需要去記 Spring 的接口簽名三是不需要額外引入路由邏輯路徑參數(shù)直接寫在注解里比如/ws/{userId}在 OnOpen 方法里就能拿到用戶 ID非常貼合業(yè)務(wù)需求。2.2 客戶端選型與依賴引入Java 生態(tài)里做 WebSocket 客戶端我一般直接用 Java-WebSocket 這個(gè)開(kāi)源庫(kù)。它提供了完整的客戶端實(shí)現(xiàn)連接、發(fā)送、監(jiān)聽(tīng)消息、心跳、重連這些能力都覆蓋了而且它是一個(gè)輕量庫(kù)不需要和 Spring 容器做任何綁定非常適合作為獨(dú)立組件封裝進(jìn)業(yè)務(wù)代碼里。相比之下用 Spring 自帶的WebSocketStompClient也不是不行但它強(qiáng)依賴 STOMP 協(xié)議棧如果你的服務(wù)端是自己實(shí)現(xiàn)的普通 WebSocket 端點(diǎn)沒(méi)必要為握手過(guò)程引入這么重的依賴。Maven 坐標(biāo)很簡(jiǎn)單服務(wù)端只需要引入 Spring Boot 的 websocket starter客戶端則額外引入 Java-WebSocket 庫(kù)。注意版本方面選一個(gè)穩(wěn)定版本就行這十年下來(lái)它的 API 變化不大核心用法比較穩(wěn)定。在后面的代碼里我會(huì)把這兩段依賴分開(kāi)寫清楚避免一些人把服務(wù)端和客戶端的依賴混在一起出現(xiàn)莫名奇妙的沖突。2.3 “同步回調(diào)”的本質(zhì)把異步消息拉回到同步時(shí)間線要說(shuō)清楚同步回調(diào)我打個(gè)比方。你去銀行柜臺(tái)辦事取了一個(gè)號(hào)碼牌然后坐下來(lái)等廣播叫號(hào)廣播喊到你的號(hào)你再上去拿結(jié)果。這里的號(hào)碼牌就是請(qǐng)求 ID廣播就是 WebSocket 的消息通知等待區(qū)就是你正在阻塞的業(yè)務(wù)線程。實(shí)現(xiàn)上就是用并發(fā)包里的CompletableFuture作為“等待憑證”每次發(fā)送請(qǐng)求前生成一個(gè)全局唯一的請(qǐng)求 ID同時(shí)創(chuàng)建一個(gè)CompletableFuture放進(jìn)一個(gè)ConcurrentHashMap里key 就是請(qǐng)求 ID。消息發(fā)出去之后調(diào)用方執(zhí)行future.get(timeout)阻塞等待。服務(wù)端處理完業(yè)務(wù)后回包時(shí)必須帶上同一個(gè)請(qǐng)求 ID客戶端在收到消息時(shí)用這個(gè) ID 去 Map 里找到對(duì)應(yīng)的 Future調(diào)用complete()把結(jié)果填進(jìn)去阻塞中的調(diào)用方立刻被喚醒拿到返回值繼續(xù)往下走。這就是“異步轉(zhuǎn)同步”的核心機(jī)制。需要注意這種同步是調(diào)用方線程層面的等待不是整個(gè)系統(tǒng)變成串行服務(wù)端依然是并發(fā)的只是每個(gè)請(qǐng)求在自己的線程里等待自己的結(jié)果彼此之間不干擾。同時(shí)future.get()一定要帶超時(shí)時(shí)間避免服務(wù)端一直不回包把業(yè)務(wù)線程全部打滿這一點(diǎn)在后面的代碼里也會(huì)處理掉。3. 服務(wù)端實(shí)現(xiàn)從握手建連到一對(duì)一消息推送3.1 依賴與基礎(chǔ)配置服務(wù)端的第一步是加依賴。Spring Boot 項(xiàng)目里引入spring-boot-starter-websocket就夠了它會(huì)把 WebSocket 相關(guān)的容器能力帶進(jìn)來(lái)同時(shí)和原有的 Web MVC 共存不會(huì)影響普通 HTTP 接口。dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-websocket/artifactId /dependency有了依賴之后還需要一個(gè)配置類注冊(cè)ServerEndpointExporter。這個(gè)類非常關(guān)鍵它的作用是掃描所有標(biāo)注了ServerEndpoint的類把它們注冊(cè)到底層 WebSocket 容器里讓路徑真正生效。如果少了這一步你寫的端點(diǎn)類不會(huì)報(bào)錯(cuò)但連接時(shí)永遠(yuǎn)是連接失敗這個(gè)坑不少人都踩過(guò)。Configuration public class WebSocketConfig { Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }這一步完成后服務(wù)端的骨架就搭好了。后面寫的端點(diǎn)類、會(huì)話管理工具、消息中心都會(huì)被 Spring Boot 的自動(dòng)配置能力串起來(lái)。3.2 會(huì)話管理并發(fā)安全是第一個(gè)考點(diǎn)WebSocket 服務(wù)端的核心資源就是Session每個(gè)連接對(duì)應(yīng)一個(gè)會(huì)話我們得把“某個(gè)用戶 ID ? 某個(gè) Session”的對(duì)應(yīng)關(guān)系管理好。因?yàn)?WebSocket 連接的建立和斷開(kāi)是隨時(shí)的可能幾十個(gè)線程同時(shí)在線增刪會(huì)話所以這里必須用并發(fā)安全的容器。我用一個(gè)獨(dú)立的工具類來(lái)管理在線會(huì)話內(nèi)部使用ConcurrentHashMap以用戶 ID 為 key以Session對(duì)象為 value。提供四個(gè)最基本的方法加會(huì)話、刪會(huì)話、查會(huì)話、統(tǒng)計(jì)在線數(shù)。為什么單獨(dú)抽一個(gè)類因?yàn)槎它c(diǎn)類和消息中心都會(huì)用到這份數(shù)據(jù)如果堆在前端類里代碼會(huì)越來(lái)越亂還會(huì)出現(xiàn)靜態(tài)變量和實(shí)例變量混淆的問(wèn)題。public class SessionManager { private static final MapString, Session ONLINE_SESSIONS new ConcurrentHashMap(); public static void add(String userId, Session session) { ONLINE_SESSIONS.put(userId, session); } public static void remove(String userId) { ONLINE_SESSIONS.remove(userId); } public static Session get(String userId) { return ONLINE_SESSIONS.get(userId); } public static int onlineCount() { return ONLINE_SESSIONS.size(); } }這里要特別提醒一點(diǎn)同一個(gè)用戶如果重復(fù)連接比如瀏覽器刷新、客戶端斷線重連舊的 Session 沒(méi)有正常關(guān)閉Map 里就會(huì)殘留失效連接。所以在add的時(shí)候建議先判斷一下舊 Session 是否還存在如果存在就先關(guān)掉再放新的進(jìn)去。否則你后續(xù)推送時(shí)可能拿到一個(gè)已經(jīng)關(guān)閉的 Session白白增加很多isOpen()判斷。3.3 WebSocket 端點(diǎn)的完整實(shí)現(xiàn)端點(diǎn)類直接寫在 Spring Boot 工程里加Component和ServerEndpoint兩個(gè)注解。路徑我設(shè)計(jì)成/ws/{userId}這樣連接時(shí)天然帶上了身份信息服務(wù)端不用再通過(guò)消息內(nèi)容去猜是哪個(gè)用戶。Component ServerEndpoint(/ws/{userId}) public class ChatWebSocketEndpoint { OnOpen public void onOpen(Session session, PathParam(userId) String userId) { SessionManager.add(userId, session); System.out.println(連接建立: userId userId , sessionId session.getId()); } OnMessage public void onMessage(String message, Session session) { System.out.println(收到消息: message); // 這里交給業(yè)務(wù)邏輯處理注意線程池隔離 MessageRouter.route(session, message); } OnClose public void onClose(Session session, PathParam(userId) String userId) { SessionManager.remove(userId); System.out.println(連接關(guān)閉: userId userId); } OnError public void onError(Session session, Throwable error) { error.printStackTrace(); } }這段代碼看起來(lái)簡(jiǎn)單但有幾個(gè)細(xì)節(jié)值得說(shuō)。第一PathParam必須和路徑占位符里的名字一致不然后臺(tái)會(huì)一直打參數(shù)解析異常的日志連接成功率看起來(lái)沒(méi)問(wèn)題但用戶 ID 永遠(yuǎn)是空值。第二onError里最好不要把異常只打日志就算了應(yīng)該把當(dāng)前會(huì)話刪掉避免一個(gè)半死不活的連接還掛在在線列表里占位置。第三onMessage收到的消息體格式建議直接約定為 JSON服務(wù)端不要接收裸字符串否則后面做回調(diào)配對(duì)時(shí)請(qǐng)求 ID 都沒(méi)地方放。3.4 消息中心設(shè)計(jì)對(duì)點(diǎn)推送與廣播有了一對(duì)一的 Session 映射下一步就是封裝一個(gè)消息中心讓業(yè)務(wù)代碼不用直接碰Session對(duì)象。推送消息的核心方法是sendToUser它負(fù)責(zé)根據(jù)用戶 ID 找到 Session檢查連接狀態(tài)然后發(fā)送文本消息。public class MessageCenter { public static void sendToUser(String userId, String message) { Session session SessionManager.get(userId); if (session ! null session.isOpen()) { try { session.getBasicRemote().sendText(message); } catch (IOException e) { SessionManager.remove(userId); e.printStackTrace(); } } } public static void broadcast(String message) { for (String userId : SessionManager.allUserIds()) { sendToUser(userId, message); } } }這里我用了getBasicRemote().sendText()它是同步發(fā)送線程會(huì)等消息寫完才返回。如果推送的頻率特別高建議改用getAsyncRemote().sendText()異步發(fā)送不用等 IO 完成但這意味著你要自己處理消息順序和發(fā)送失敗的回調(diào)復(fù)雜度會(huì)上去。另外一個(gè)很多人在推送時(shí)忽略的點(diǎn)同一個(gè)Session不要多個(gè)線程同時(shí)調(diào)用sendText()會(huì)出現(xiàn)消息交錯(cuò)甚至直接拋異常。并發(fā)量大的時(shí)候可以考慮給每個(gè) Session 加一個(gè)寫鎖或者用隊(duì)列把所有推送任務(wù)排隊(duì)處理。我在實(shí)際排查過(guò)的問(wèn)題里見(jiàn)過(guò)好幾次“消息偶爾串線”的詭異現(xiàn)象最后定位就是并發(fā)寫同一個(gè) Session 造成的。3.5 一個(gè)容易踩的坑Spring 容器與端點(diǎn)實(shí)例的隔離ServerEndpoint的端點(diǎn)類和普通的Component有一個(gè)本質(zhì)區(qū)別端點(diǎn)實(shí)例是由 WebSocket 容器創(chuàng)建和管理的每次連接通常對(duì)應(yīng)新的實(shí)例它不是 Spring 的單例。很多人在這里栽過(guò)跟頭在端點(diǎn)類里直接Autowired一個(gè) Service結(jié)果發(fā)現(xiàn)是 null因?yàn)?Spring 根本沒(méi)有管理那個(gè)端點(diǎn)實(shí)例。解決辦法是用靜態(tài)字段加 setter 注入的方式把 Spring 容器里的 Service 暴露到端點(diǎn)類里。具體做法是端點(diǎn)類加Component讓 Spring 掃描到然后在類里定義一個(gè)靜態(tài)的 Service 字段和 setter 方法setter 上加Autowired。這樣 Spring 在初始化時(shí)會(huì)把 Service 注入到 setter 里再賦給靜態(tài)字段端點(diǎn)實(shí)例后續(xù)就可以直接使用這個(gè)靜態(tài)字段了。Component ServerEndpoint(/ws/{userId}) public class ChatWebSocketEndpoint { private static MessageService messageService; Autowired public void setMessageService(MessageService service) { ChatWebSocketEndpoint.messageService service; } OnMessage public void onMessage(String message, Session session) { Object result messageService.handle(message); // 繼續(xù)處理 result ... } }這是很多教程里不會(huì)提到的關(guān)鍵細(xì)節(jié)。如果你打電話報(bào)錯(cuò)找不到 Service十有八九就是這里沒(méi)處理好。3.6 服務(wù)端如何配合同步回調(diào)協(xié)議服務(wù)端本身不一定知道“同步回調(diào)”這回事它只需要遵守一條協(xié)議回包時(shí)必須帶上請(qǐng)求里攜帶的reqId。這個(gè)約定是整個(gè)同步機(jī)制成立的基礎(chǔ)。我建議在項(xiàng)目里提前定好統(tǒng)一的消息外殼格式。請(qǐng)求消息和響應(yīng)消息都包含三個(gè)基礎(chǔ)字段reqId表示請(qǐng)求 IDtype表示消息類型payload放業(yè)務(wù)數(shù)據(jù)。這樣無(wú)論消息走的是同步請(qǐng)求還是異步事件接收方只要掃一眼type就能分流通過(guò)reqId就能配對(duì)。OnMessage public void onMessage(String message, Session session) { JSONObject request JSON.parseObject(message); String reqId request.getString(reqId); String payload request.getString(payload); Object result messageService.handle(payload); JSONObject response new JSONObject(); response.put(reqId, reqId); response.put(status, 200); response.put(data, result); MessageCenter.sendToUser(getUserId(session), response.toJSONString()); }服務(wù)端回包時(shí)只要找回請(qǐng)求里的reqId填進(jìn)響應(yīng)體客戶端的同步等待就能被正確喚醒。如果哪天發(fā)現(xiàn)客戶端一直超時(shí)第一個(gè)排查點(diǎn)就是服務(wù)端有沒(méi)有把reqId原樣返回。這個(gè)看起來(lái)不起眼卻是我見(jiàn)過(guò)最多的失誤原因之一。4. 客戶端封裝與同步回調(diào)落地4.1 客戶端核心骨架與連接管理這里的客戶端指的是嵌入 Spring Boot 工程里的 Java WebSocket 客戶端組件不是瀏覽器頁(yè)面。它會(huì)去連接遠(yuǎn)端的 WebSocket 服務(wù)然后把一個(gè)“同步請(qǐng)求方法”暴露給上層業(yè)務(wù)調(diào)用。依賴如下我選用 Java-WebSocket 庫(kù)來(lái)做客戶端dependency groupIdorg.java-websocket/groupId artifactIdJava-WebSocket/artifactId version1.5.4/version /dependency客戶端組件核心就是創(chuàng)建一個(gè)WebSocket實(shí)例綁定服務(wù)端地址并在onMessage回調(diào)里集中處理收到的消息。連接建立之后上層業(yè)務(wù)不需要關(guān)心連接對(duì)象只需要調(diào)用我們封裝的同步方法。public class WsClient { private WebSocket socket; public void connect(String serverUrl) throws Exception { socket new WebSocket(new URI(serverUrl)) { Override public void onMessage(String message) { handleResponse(message); } Override public void onClose(int code, String reason, boolean remote) { scheduleReconnect(serverUrl); } }; socket.connect(); // 等待連接完成 CountDownLatch latch new CountDownLatch(1); // 這里可做握手完成的標(biāo)志位簡(jiǎn)化示例直接按需等待 } }有一點(diǎn)要注意connect()是非阻塞的真正建立連接需要幾毫秒到幾十毫秒不等。所以如果你的業(yè)務(wù)方剛調(diào)用完connect()立刻就去發(fā)消息很可能會(huì)拋“連接未打開(kāi)”的異常。一個(gè)穩(wěn)妥的做法是在 onOpen 回調(diào)里放一個(gè)門閂等連接建立后再開(kāi)放發(fā)消息。我實(shí)際做的時(shí)候是在內(nèi)部維護(hù)了一個(gè)AtomicBoolean connected標(biāo)志每次發(fā)送前檢查沒(méi)連上就阻塞等待一會(huì)兒。4.2 請(qǐng)求-響應(yīng)對(duì)齊的關(guān)鍵結(jié)構(gòu)整個(gè)同步回調(diào)機(jī)制最關(guān)鍵的數(shù)據(jù)結(jié)構(gòu)有兩塊一個(gè)是請(qǐng)求 ID 生成器一個(gè)是等待響應(yīng)的 Future 容器。請(qǐng)求 ID 我用AtomicLong自增序列來(lái)生成配合一個(gè)簡(jiǎn)單的前綴保證在本進(jìn)程內(nèi)唯一。如果多個(gè)實(shí)例部署建議用 UUID 或者在前綴里加上實(shí)例標(biāo)識(shí)避免把不同實(shí)例的請(qǐng)求響應(yīng)匹配錯(cuò)。public class WsClient { private static final AtomicLong REQ_SEQ new AtomicLong(0); private final MapString, CompletableFutureString pendingMap new ConcurrentHashMap(); private String generateReqId() { return REQ_ REQ_SEQ.incrementAndGet(); } }pendingMap就是那個(gè)“等待區(qū)”。每個(gè)請(qǐng)求發(fā)出前往 Map 里放一個(gè)CompletableFuture收到對(duì)應(yīng)響應(yīng)時(shí)從這個(gè) Map 里取出 Future把它完成掉。Map 用ConcurrentHashMap是因?yàn)榭赡苡卸鄠€(gè)業(yè)務(wù)線程同時(shí)發(fā)起請(qǐng)求、多個(gè)消息回調(diào)線程同時(shí)處理響應(yīng)必須在并發(fā)場(chǎng)景下保證安全。4.3 封裝同步請(qǐng)求方法核心方法sendSync的邏輯這樣設(shè)計(jì)生成請(qǐng)求 ID、封裝消息、注冊(cè) Future、發(fā)送消息、阻塞等待、最后無(wú)論如何都要從 Map 里清理掉這個(gè) Future。清理這一步絕對(duì)不能省否則服務(wù)端一直不回包Map 里就會(huì)堆積大量的 Future 對(duì)象時(shí)間一長(zhǎng)就是內(nèi)存泄漏。public String sendSync(String payload, long timeout, TimeUnit unit) throws Exception { String reqId generateReqId(); CompletableFutureString future new CompletableFuture(); pendingMap.put(reqId, future); JSONObject request new JSONObject(); request.put(reqId, reqId); request.put(type, BIZ_REQUEST); request.put(payload, payload); socket.send(request.toJSONString()); try { return future.get(timeout, unit); } finally { pendingMap.remove(reqId); } }這個(gè)方法的調(diào)用方式非常直白業(yè)務(wù)方傳一段 JSON 字符串傳入超時(shí)時(shí)間然后同步拿到服務(wù)端返回的另一個(gè) JSON 字符串。如果服務(wù)端那邊處理時(shí)間大致可預(yù)估建議把超時(shí)設(shè)置成 3 到 5 秒如果包含復(fù)雜的業(yè)務(wù)鏈路可以放寬到 10 秒但超過(guò) 30 秒我一般不建議因?yàn)殚L(zhǎng)時(shí)間阻塞業(yè)務(wù)線程會(huì)讓整個(gè)應(yīng)用的線程池很快耗盡。4.4 心跳保活與斷線重連長(zhǎng)連接有個(gè)天然的敵人網(wǎng)絡(luò)中間設(shè)備和服務(wù)器本身會(huì)清理閑置連接。Tomcat 默認(rèn)會(huì)有空閑連接超時(shí)Nginx 代理也有讀寫超時(shí)如果客戶端長(zhǎng)時(shí)間不發(fā)送任何數(shù)據(jù)連接就會(huì)被對(duì)端靜默關(guān)閉??蛻舳俗约焊兄坏降鹊较麓伟l(fā)消息才發(fā)現(xiàn)連接已經(jīng)斷了這時(shí)同步請(qǐng)求就直接卡到超時(shí)。所以客戶端必須加心跳。我一般用ScheduledExecutorService每隔 10 秒發(fā)送一個(gè)心跳消息消息體統(tǒng)一成{type:PING}。服務(wù)端收到 PING 時(shí)不需要回業(yè)務(wù)數(shù)據(jù)但可以回一個(gè) PONG或者只要服務(wù)端還在正常讀數(shù)據(jù)客戶端這條心跳就起到了?;钭饔?。private void startHeartbeat() { ScheduledExecutorService executor Executors.newSingleThreadScheduledExecutor(); executor.scheduleAtFixedRate(() - { if (socket ! null socket.isOpen()) { JSONObject ping new JSONObject(); ping.put(type, PING); socket.send(ping.toJSONString()); } }, 10, 10, TimeUnit.SECONDS); }斷線重連方面我會(huì)在onClose里觸發(fā)重連邏輯但重連不能做成無(wú)腦循環(huán)否則服務(wù)端還沒(méi)恢復(fù)客戶端就會(huì)以極高的頻率瘋狂創(chuàng)建連接把服務(wù)端打得更慘。建議做退避重試第一次 1 秒、第二次 2 秒、第三次 4 秒指數(shù)遞增最多間隔 60 秒服務(wù)端恢復(fù)連接后把重連次數(shù)清零。重連期間所有正在等待的sendSync請(qǐng)求會(huì)因?yàn)槌瑫r(shí)統(tǒng)一失敗上層業(yè)務(wù)要接受這種“短暫不可用”的現(xiàn)實(shí)并在失敗后走自己的重試策略。4.5 回調(diào)協(xié)議的最佳實(shí)踐與參數(shù)建議這部分我總結(jié)了幾個(gè)真正算得上經(jīng)驗(yàn)的東西。第一消息格式必須是 JSON并且在項(xiàng)目一開(kāi)始就定好外殼字段不能今天用reqId明天用requestId這種不一致會(huì)讓整個(gè)調(diào)試過(guò)程變成災(zāi)難。建議統(tǒng)一為reqId、type、payload三個(gè)字段同步響應(yīng)再加一個(gè)status表示結(jié)果狀態(tài)碼。第二sendSync返回的響應(yīng)建議不要直接返回原始字符串最好解析成JSONObject再返回同時(shí)把status非 200 的響應(yīng)當(dāng)成業(yè)務(wù)異常拋出去。這樣上層業(yè)務(wù)代碼拿到的是干凈的結(jié)果而不是還要自己判斷一堆狀態(tài)位。第三請(qǐng)求 ID 最好在客戶端生成。如果服務(wù)端也可以生成就會(huì)出現(xiàn)兩邊 ID 體系不一致的問(wèn)題調(diào)試時(shí)很容易看花眼。客戶端生成后原樣傳給服務(wù)端服務(wù)端原樣回傳鏈路清晰問(wèn)題也好定位。第四同步回調(diào)適合“必須有結(jié)果”的操作但不是所有消息都適合同步。像心跳、狀態(tài)上報(bào)、位置推送這類頻率高、不關(guān)心響應(yīng)的消息建議走獨(dú)立的異步通道不要通過(guò)sendSync發(fā)否則線程池重負(fù)載下這些消息也會(huì)排隊(duì)等待白白增加延遲。5. 實(shí)戰(zhàn)避坑與排查實(shí)錄5.1 常見(jiàn)問(wèn)題速查表我把實(shí)際開(kāi)發(fā)中遇到過(guò)的問(wèn)題整理成了一張表按現(xiàn)象排查效率很高現(xiàn)象可能原因解決辦法客戶端始終連接不上服務(wù)端缺少ServerEndpointExporter注冊(cè)檢查配置類是否注冊(cè)了該 Bean連接報(bào) 400 握手失敗路徑參數(shù)不對(duì)或請(qǐng)求路徑與端點(diǎn)不匹配核對(duì)ServerEndpoint路徑和客戶端連接地址請(qǐng)求 100% 超時(shí)reqId沒(méi)有配對(duì)服務(wù)端響應(yīng)里丟了reqId抓包看服務(wù)端回包字段確認(rèn)reqId原樣返回連接頻繁斷開(kāi)沒(méi)有心跳中間設(shè)備回收空閑連接客戶端加心跳建議 10 秒一次消息出現(xiàn)錯(cuò)亂或串線同一 Session 被多個(gè)線程并發(fā)寫加 Session 寫鎖或全部改成異步發(fā)送重連后消息發(fā)不出去重連成功后內(nèi)部連接狀態(tài)沒(méi)更新在 onOpen 回調(diào)里更新連接標(biāo)志位長(zhǎng)時(shí)間運(yùn)行后內(nèi)存增長(zhǎng)明顯pendingMap中超時(shí)的 Future 沒(méi)清理finally塊里執(zhí)行pendingMap.remove(reqId)5.2 幾個(gè)我踩過(guò)的深坑第一個(gè)深坑就是忘記注冊(cè)ServerEndpointExporter。項(xiàng)目啟動(dòng)完全正常沒(méi)有任何報(bào)錯(cuò)但所有 WebSocket 連接都失敗我在瀏覽器控制臺(tái)看到的是 404。排查了半天才發(fā)現(xiàn)是配置類里少了一個(gè) Bean這種問(wèn)題報(bào)錯(cuò)信息不直接非常迷惑人。第二個(gè)深坑是瀏覽器里連接 503。如果你前面掛了 Nginx 之類的反向代理必須在代理配置里顯式開(kāi)啟協(xié)議升級(jí)并且設(shè)置合理的超時(shí)時(shí)間。很多人本地直連正常一到測(cè)試環(huán)境就失敗基本都是這一層沒(méi)配置。生產(chǎn)環(huán)境還要注意負(fù)載均衡器的會(huì)話保持不然同一個(gè)客戶端的多次連接會(huì)被轉(zhuǎn)發(fā)到不通的節(jié)點(diǎn)上。第三個(gè)深坑是普通方法里調(diào)用sendSync導(dǎo)致線程池打滿。我當(dāng)時(shí)在業(yè)務(wù)線程里直接調(diào)同步請(qǐng)求結(jié)果服務(wù)端回包變慢幾十個(gè)請(qǐng)求全部卡在future.get()上業(yè)務(wù)線程全部占滿應(yīng)用開(kāi)始出現(xiàn)連環(huán)超時(shí)。后來(lái)給同步請(qǐng)求單獨(dú)隔離了一個(gè)線程池并且嚴(yán)格限制超時(shí)時(shí)間這個(gè)問(wèn)題才算解決。第四個(gè)深坑是關(guān)于 Resend 或者消息重發(fā)。WebSocket 底層不保證消息不丟有時(shí)候服務(wù)端確實(shí)不會(huì)回包客戶端超時(shí)后會(huì)重試。重試時(shí)不能直接把同一個(gè)reqId重新發(fā)一遍否則如果服務(wù)端前面已經(jīng)處理成功只是回包丟了會(huì)引起業(yè)務(wù)重復(fù)執(zhí)行。建議給每條消息加一個(gè)sequence序號(hào)重試時(shí)用新reqId但在payload里帶上原始業(yè)務(wù)冪等號(hào)服務(wù)端自己決定是否要去重。5.3 分布式多實(shí)例下的拓展思路以上所有方案在單機(jī)模式下都是沒(méi)有問(wèn)題的因?yàn)闀?huì)話信息都存在本地 JVM 的ConcurrentHashMap里。一旦項(xiàng)目從單實(shí)例擴(kuò)展成多實(shí)例部署問(wèn)題就出來(lái)了用戶 A 連接在實(shí)例 1用戶 B 連接在實(shí)例 2實(shí)例 1 想推送消息給 B本地 Session 里根本找不到 B 的連接。常規(guī)解法是把“在線會(huì)話表”從本地內(nèi)存挪到外部存儲(chǔ)比如 Redis 哈希結(jié)構(gòu)里記錄 userId 和節(jié)點(diǎn) ID 的映射。推送時(shí)先查路由表找到目標(biāo)節(jié)點(diǎn)再把消息投遞到對(duì)應(yīng)的實(shí)例由該實(shí)例的本地會(huì)話表完成真正的推送。節(jié)點(diǎn)間通信可以用消息隊(duì)列或者 Redis 發(fā)布訂閱來(lái)做實(shí)例只需消費(fèi)消息并判斷消息里的目標(biāo)節(jié)點(diǎn)是不是自己。同步回調(diào)在這種架構(gòu)下也會(huì)變得復(fù)雜但好在請(qǐng)求 ID 的機(jī)制不受影響只要消息能路由到正確的節(jié)點(diǎn)配對(duì)邏輯依然成立。我通常建議先把單機(jī)版跑通再考慮加分布式路由層不要一上來(lái)就把復(fù)雜度拉滿。最后再分享一點(diǎn)個(gè)人體會(huì)整個(gè)項(xiàng)目跑通之后我最大的感受是同步回調(diào)這個(gè)能力真正難的不是代碼而是協(xié)議先想清楚。你只要把reqId、type、payload、status這套外殼定好后面換服務(wù)端框架、換客戶端引擎都不傷筋動(dòng)骨。我后來(lái)遇到類似的需求第一件事永遠(yuǎn)是先確認(rèn)消息格式而不是先寫連接代碼。另外一個(gè)很實(shí)際的心得是同步語(yǔ)義雖好但不要貪只對(duì)業(yè)務(wù)上確實(shí)需要返回結(jié)果的請(qǐng)求用同步像心跳、狀態(tài)廣播這些應(yīng)該走純事件否則你的線程池很快就會(huì)被占滿。要是后續(xù)打算跨集群擴(kuò)展可以把請(qǐng)求 ID 和路由信息塞進(jìn)消息頭再配一個(gè)消息網(wǎng)關(guān)這套客戶端組件基本不用改服務(wù)端的路由層單獨(dú)替換就行。
返回列表
PREV
查看更多資訊
NEXT
返回資訊列表
色999人与兽| 久操网无码在线| 无码精品久久| 国产无码精品高清| 久偷拍| 亚洲 小说 欧美 激情 另类| 久久久久久大| 伊人丁香五月婷婷| 福利大香蕉| 国产一级不卡在线观看| 99热18这里只有精品| 黄色免费网页无码| 蜜臀久久99精品久久久久久无删减| 成人片在线播放| 91美| 一区,二区,三区视频| 性影在线视频| 伊人操你| 久久久女人| 屌逼麻豆| 亚洲国产一区二区日韩专区| 91福利网在线观看| 久久青娱乐| 婷婷四五区| 91麻豆va国产精品| 国内毛片婷婷六月色| 夜夜国自区| 东北女人av| 国产成人精品日本亚洲语言| 91色拍| 欧美成人精品欧美一级乱黄一区二…| 久区视频| 97美日韩视频| 国产精品乱码久久久、久久| 中文字幕伊人| 久久久青草青青国产亚洲免观精品高清完整版_97久久综合区小说区图片区,国精品 | 亚洲自拍一区夜夜操| 强歼乱伦资源网| 18禁无码永久免费无限制| 亚洲激情视频| 欧美综合传媒| 色97国产69香蕉| 欧美激情精品久久久久久| a男人的天堂| 色情综合| 日韩AV色图| 亚洲有码 欧美精品| 999国产精品999| 久久精品国产Aⅴ| 偷拍新久久| 国际精品久久久| 91熟女网| 久久精品人人做人人看| 亚洲国产精品9999在线观看| 久久精品店| 国产1024在线播放| 99热在线播放| 亚洲欧美日韩免费观看| 探花精品视频| 操穴国产| 国产免费内射视频| 天天视频综合在线观看视频| 99自拍视频| 中文字幕 国产 精品| 婷婷综合网站| 精品射1999| 欧美综合 站| 97色97好| 诱惑人妻欧美一区在线播放| 无码heyzo高清一区| 欧美亚洲厕所精品偷拍91| 欧美最婬乱婬爆婬性视频| 欧美成人综合| 日韩色| 国产精品黄色三级av| 99久久99久久综合| 97欧美色资源| 欧美精品庄| 天天综合91入口| 大香蕉手机在线| 搡老熟女免费视频| JULIA一区二区三区在线播放| 大香蕉欧美伊| 蜜桃精品一区二区三区久在线| 九九九影院| 中文字幕熟女人妻丝袜丝| 91操熟女| 激情综合亚洲| 97欧美色| 中文字幕性感少妇av| 婷婷伊人一区| 好色综合| 99久久九九| 超碰天天操| 色香av| 欧美黄色大片在线观看| 国产精品一二三免费网站| 欧美熟爽综合| 后入式五六区| 久久超碰国产一区二区三区| 久久,精品一二三| 久久精9| 欧美激情精品| 99丝袜福利在线播放| 97爱爱官网| 欧美亚洲尤物久久| 丁香婷婷激情五月天无毒不卡| 91精品国| 激情文学网伊人| 人人妻天天做天天爽| 在线观看亚洲成人精品| 亚洲情色在线| 操逼无码操逼| 玖玖爱一区在线| 思思久热在线精品66| 中文字幕人妻丝袜| 熟妇国产免费一区| 东京热男人天堂| 性爱Av免费| 中文字幕一二三区| 黄色免费一级在线毛片| 欧美日韩美女精品久草一区二区三区| 日本成人A片网站| 丁香婷婷色五月| 国产熟女一区二区丰满| 搡老女人911熟妇老熟女| 操操AV电影| 免费观看一区| 插欧洲美女欧美精品| 伊人96在线| 国产精品久久久久久久黄无码| 黑操B| 四虎精品永久在线播放| 五月综合色| 亚洲男人的天堂网| 国产专区第一页| 欧美国产一区二区三区麻豆传媒 | 91九久| 操国产逼| 91国产丝袜美女| 五月天婷婷色| 五月丁香激情综合| 色综合一区二区三区| 国产suv精品一区二区四| 中文字幕日韩专区精品系列| 超碰97在线中文| 亚洲国产欧美中文永久| 国产精品剧情| 强奸熟女一区二区三区 | 国产人妻精品一区二区三区秋霞| 国产成人久久久精品免费AV| 男人天堂网站| 中文字幕人乱码中文字的预防方法 | 黄色免费网| 欧美成人综合| 超碰av在线| 福利视频香蕉免费一区二区在线| 天美传媒av在线| 久热91| 亚洲国产另类在线中文| 欧美综合狠| www.黄色在线| 好爽免费视频| 9ⅰ久久久天天| 99爱久久视频频| 中文字幕,人妻,日韩| 国产九九九九九九| 亚洲蜜臀懂色| av天天在线观看| 国产成人手机视频激情| 大屁股熟女一区二区三区| 伊人天堂在线| 色婷婷五月综合激情中文字幕| 97欧美| 亚洲第91页 | 亚洲国产蜜臀系列在线观看| 久久夜夜夜夜| 暖暖精品二区三区观看| 99色视频| 欧美 亚洲 偷拍自拍| 免费无码婬片AAAA片直播色戒| 色香综合天天影视综合 | 九九九九精品| 96超碰网| 精品熟妇视频一区二区| 精品中文一区二区| 欧美日韩国产男人| 啊嗯好大视频在线观看| 亚洲色图大香| 欧美中文字幕男人天堂久久精品| 亚洲精品男人的天堂| 久久久久国产精品喷潮免费观看臀| 亚洲国产美女久久久久| 亚洲天天操| 97在线免费视频观看| 美女骚尻视频| 免看60秒涩涩视频| 加勒比少妇AV婷婷六月天超碰超碰| 婷婷成人久久久精品| 曰韩无码777| 精品人妻一区二区三区四区石在线 | 欧美精品成人一区二区在线观看 | 老色69| 伊人五月天| 无套内射性感少妇视频| 日韩午夜啪啪视频| 99精品久久| 97人人模人人爽人人| 欧美少妇性爱网站| 日本123区操B视频| 亚洲色图久久精品蜜| 三级三级三级日本99| 欧美疯狂做爰xxxx| 91chinese在线| 操99| 人人手机欧洲亚洲国产人妻| 97超碰这里只有精品| 九九九九免费视频| 色原狠狠天天天| 97超碰公开| 午夜精品久久久久久久久久久久久| 亚洲欧美不卡线| 国产久久久久久| 欧美精品97| 天天日天天操天天射河南省| 日操粉逼逼| 妺妺跟我一起洗澡没忍住| 91丨九色丨东北熟女| 在线黄页看毛片| 超碰爽人妻熟女Av| 欧美性视频二区三区| 青青草一区二区三区四| 嗯嗯啊啊好爽| 乱伦Av网| 日本熟妇一区二区三区| 99re在线观看| 97超碰超| AV一区观看| 亚洲操逼无码| 精品一区二区综合熟妇| 午夜男人一级A片7777| 91neishe| 99日免费视频中文字幕| 91老熟女| 亚洲中文字幕网| 九九玖玖精品| 国产成人www免费人成看片| 人人色人人操在线| 丰满人妻一区| 国产 日韩 欧美 中文 另类,国产 欧美 另类 制服 变态,高清 日韩 欧美 中文,高 | 亚洲字幕一区二区| 9久久精品| 中文字幕一区二区三区人妻少妇在线| 国产中午字一暮区| 一本一道vs波多野结衣| 真实高潮91| 色一区二区三区综合| 97av在线观看| 国模少妇一区二区三区| 亚洲AO在线| 亚洲欧美变态| 91三级理论片播放器| 亚洲 欧美 中文 日韩超碰| 日韩懂色网| 欧美综合第一页| 日本女优在线视频福利| 午夜久久一区二区无码中出| 91欧美情色| 综合久久久久久久综合网| 欧美综合站| 九九九九九九九九九九九蜜桃| a片久久久久久久久久久久 | 国产 日韩 欧美高清| 婷婷五月丁香五月| 一区二区三区一亚洲中文字幕、综合区灬 | 精品国产一区二区三区在线播出| 91在线限制级| 呦呦一区| 久久国产精品一级二级三级| 久久久久久久| 大香蕉操久久| 男人天堂网址| 午夜天堂精品久久久久91| 精品无码一区二区人妻久久蜜桃| 欧美日韩一区二区三区四区蜜桃| 97欧美色综合| 国产h片在线观看视频| 四虎视频在线观看| 美国黄片aaa| 成人影 天天操 亚洲| 成人性交免费视频| 日韩精品区二区三区不卡| 久热精品在线| 夜夜一区二区| 久久大黄片| 小少妇| 天天久久| 九一亚洲国产免费| 人妻一二三区| 少妇精品久久久| 乱伦av麻豆| 精品人妻伦一区二区三区久久| 伊人网综合在线视频| 少妇色综合| 日本精品无码三级网站| 亚洲AV不卡在线观看| 亚洲中文字母在线播放| 亚洲狠狠入| 综合久久中文字幕综合日韩精品| 男同专区一区二区三区在线| 狠狠操夜夜操蜜桃视频三区| 手机看片91人妻| 黄总AV色图| 亚洲国产奇米影视久久| 久操电影网| 夜夜爽33333| 国产操逼网站亚洲一级黄色| 欧洲一区二区三区免费| 黄片aaaaa一区| 骚鸭AV| 久久伊人在线五区| 福利操逼| 精品久久97| GVH-003 母子姦 青木玲-麻豆视频,麻豆视传媒短视频网站入口,麻豆视传媒官网直 | 国产对白刺激视频| 美女91网址 | 中文?日韩?免费?精品| 91久久久视| 亚洲Av无码成人精品国产| 俞拍久久国应视频| 超碰98综合网| 亚洲天堂中文字幕无码男同| 岛国网址国产| 国产在线播放成人免费| 欧美人妻熟女在线| 婷婷国产精品九区| 大香蕉黄色一区| 99亚洲国产精品色一区二区三区| 亚洲清纯唯美| 久久111| 99操视频| 日本不卡高清视频| 欧美日韩在线小说| 亚洲诱惑天堂| 亚州春色| 国产精品久久9| 日本 欧美 国产一区| 夜夜草天天| 欧美高清16| 亚洲综合性网址| 中文字幕黑人大片| 欧美成人一区二区三区在线播放 | 亚洲欧美综合色| 成人黑料社久久| 亚洲,日韩,欧美,成人播放| 久久亚洲欧美一区二区三区-亚洲国产精品第一区二区 | 老熟女搡BBBB搡BBBB视频| 欧美亚洲首页| 欧美日韩亚洲国产中文永久天天看| 欧美日韩国第一区| 亚洲18禁| 久久久一热在线播放| 日韩性爱视频免费在线| 操逼天美3区| 久久的免费性爱视频| 中文字幕福利视频一区二区三区在线观看| 无码人妻精品一区二区三区九九| 日韩内射视频| 日韩情色视频| 97色在线观看| 黑操B| 麻豆国产尤物AV| 肉嘟嘟www视频在线观看高清| 久久久精品91八戒| 久久精品国产精品一区 | 国产精品久久久鸭无码的功能| 国产中文字幕在线点播| 一级做a爰片性色毛片久久| 久草这里只有精品 | 久久精品国产亚洲AV无码电影| 日本爽爽爽爽爽爽免费视频| 日韩免费中文字幕视频| 日韩性爱小视频| 1769一区| 中文字幕五月婷婷免费| 欧美大香蕉专区网| 入口操逼网站| 成人精品在线| 美女尤物福利视频| 天堂日本亚洲欧美| 九九九九九九九九九九精品视频| 激情视频一二三| 97超视频在线观看| 国产精品高朝久久久久久久| 亚洲 欧美 日本 国内 首页| 熟女熟妇一区二区三四区| 九九操久久国产免费视频| 清纯唯美综合| 中文乱码字字幕在线第5页| 久久精品国产精品一区| 午夜AV人气不卡| 亚洲砖码砖专无区2023| 中文字幕一区av| ss久久| 日韩精品99久久久久久中文字幕 | 日韩簧片免费看| 伦伦成年午夜免费视频| 久久99网站| 欧洲无码一区二区| 天天干1区2区在线| juliaann欧美丝袜办公室| 久久国产成人精品国产成人亚洲 | 亚洲视频一二区| 国产精品电| 国产一区在线播放| 午夜视频好爽啊| 国产粉嫩出水在线播放| 91丝袜美女| 高清不卡 中文 人妻| 中文乱码字幕观看视频| 翔田千里Av在线| 亚洲熟女中文字幕在线| 国产不卡精品91| 日韩精品电影| 久久久精品| 国产超碰人人爽人人做| 玖玖蜜臀资源网| 久久精品女同亚洲女同13| 狠狠操使劲操| 国产激情久久久| 物尤视频一区二区| 国产精品一区二区密臀| 人人妻人人狠人人| 白丝jkav| 欧美日韩在线视频网站| 天天日天天爽| 97久久精品不卡| 人妻精品免费一二三区| www.激情| 任你干在线视频| a片久久久久久久久久久久| 热九九精品| 五月开心久久AV官网| 97超碰超碰| www.99在线| 欧美黑人极品高潮喷吹熟女黑人性暴力日韩在线欧美极品一区二区老师 | 青青国产在线拍揄自揄拍| 校园春色 亚洲| 黄色区免费观看中文字幕| 精品国产一区二区三区久久久蜜臀| 高潮9999外国| 国产免费一区在线观看| 九七毛片九九毛片 | 国产精品suv一区| av黄图片在线观看| 日本久操视频| 欧美色图亚洲色| 色婷婷狠狠| 日韩久射综合| caorenqi shipin| 欧美丝袜91| 亚洲中文字幕三级在线| 爱丝福利| 99re久久| 超碰2017| 婷婷色香| 欧美黄色片在线播放| 好看的91视频| 四虎影视永久在线免费| 丝袜美女诱惑 91 视频| 午夜偷拍久久熟女| 少妇激情AV| 黄网色一区二区三区四区精品| 偷拍片久久| 乱伦熟女区| 91中文字幕在线观看| 第一高清av中文字幕| 久肏视频字幕| 久久麻豆一区二区| 一区AV| 久久久久久91香蕉国产| 色欲无码人妻日韩欧美精品| 少妇人妻激情四射| 99在线精品观看99| 97干97色| 粉嫩小泬久久久一区二区| AV和黑人在线播放| 青青草综合在线| www.夜夜| 日韩激情电影中文字幕| 久久久久久久久久久久久久久久9| 欧美αv.com| 久久激情四射婷婷丁香五月天| 久久久久人妻| 国产精品久久久久无码Av网曝门| 囯产精品强| 五月激情小说| 日韩欧美aⅴ综合网站发布| 看看日B真人视频| 97色碰| 欧美呦呦性爱| 夜夜嗨老熟女AV一区二区三区| 日本片日本片祼观看网站在线看中文版网页在线看 | 香蕉国产97| 91亚州日韩高清| 18禁中文字幕| 亚洲色图91| 人妻超碰青青草98| 精品 码产区一区二-1080P高清在线www-B029AV| 亚洲男人天堂av| 蜜桃色院一区久久| 亚洲.欧美.丝袜.中文.综合| 国产一进一出视频网站| 久久精品国产亚洲av水密被窝| 亚洲熟妇丝袜在线观看| 亚洲AV无码国产精品久久久久| 欧美精品97| 久久草视频污视频| 美国美女AV在线| 蜜乳性色无码专日粉嫩骚逼AV| 校园春色五月天| 97在线资源| 日韩三级网址| 日日干夜夜干| 国产在线视视频有精品| 日本人妻中文字幕精品| 啊啊啊啊啊啊啊啊视频| 久久九九久精品国产尤物|国产精品爽黄69天堂A片潘金莲,国产亚洲精品第一综合 | 欧美三级中文字幕hd| 欧美亚洲自拍另类人妻| 蜜臀久久99精品久久久久久酒店| 少妇丝袜在线观看AV| 熟女在线视频| 久久五月份| 亚洲乱码尤物193YW| 100啪啪视频大全| 亚洲熟女人妻中文字幕一区二区| 97超级色碰碰| 久久久久密臀视频| 东京成人一区| 久久夜色一区二区| 久久99国产综合精品女同| 婷婷五月色| 婷婷丁香成人| 婷婷伊人网| 久久6热视频免费观看| 爽极品影院| 自拍偷拍亚洲熟女妇人精品| 国产辣妈在线视频福利| 九九九九国产| 亚洲中文字幕在线视频一区二区| 精品人妻一区二区三区在| 97视频在线观看免费高清| 人妻少妇久久| 国产精品一区二区麻豆| 中日韩久久久免费看| 九色 蝌蚪 熟女自| 色欧美在线| 欧美性高潮在线| 青青草依人大香蕉| 精品久久97| 日夜精品| 欧美精品精品一区二区| 日韩一级久久毛片| 国产又黄又爽又刺激久久久久久| 日韩黄片视频试看| 国产精品久久99日日| 亚洲1区2区三区高清中文字幕| 操逼逼中文字幕| 天天干人妇| www.99热| 欧美黄色图片| 91精品免费| 大香蕉2017| 亚洲精品九九九| 国产精品直播在线观看直播| 能在线播放的国产三级| 国产一级做a爰大片免费久久| 天天插天天操| 射丝袜高跟鞋99| 美女黑人91神马| 婷婷久久久精品| 欧美性第一页| 色成人Www精品永久观看| 精品一二三区久久AAA片| 色色五月天婷婷| 快点操死我| 欧美大干日韩| 羞涩视频| 日韩人妻中文视频| 中文字幕二区日韩天堂 | 国产宅男宅女在线观看| 激情五月天婷婷| 欧美天天谢综合网| 人人妻人人色一区二区三区| 久久久久亚洲Av无码专区老牛影视 | 日韩国语字幕| 国产99999久久精品| 狠狠爱夜夜| 久久五十路熟女人妻| 国产高潮AA片免费看| 超碰 另类 欧美 | 久久免费精品视频免一| 日韩紧密久久| 久久蜜桃一区二区| se01国产在线视频| 一本久道久久综合狠狠爱| 最新av在线| 精品999一区二区| 国产精品呦一区二区三区| 96精品在线| 久久精品高清无码一区| 囯产精品久久久久久久久久梁医生| 美女操逼福利视频| 婷婷色香| 亚洲综合九九| 大香交伊人网| 999久久芭蕾| 久热在线精品免费观看| 欧美性爱97超碰| 99久久久| 日韩精品视频在线观看一卡二卡| 99热免费精品| 人妻性爱一区二区| 超碰超碰欧美| 综合久久2017| 开心婷婷五月| 久久久久久中文| 亚洲暴力强奸AV| 91九九九逼| 日本一级特级毛片视频| 东京热激情视频一二三区 | 亚洲欧美精品一区天堂久久| 亚洲狠狠入| 一级啊性爱在线视频| 中国黑人三级片网站上区| 欧美日本成人一区二区| 久久久精品国产亚洲伊人| 久久精品成人| 91啪啪视频| 久草综合网| 99久久99九九99九九九| 人妻少妇精品无码专区二区密桃| 亚洲砖码砖专无区2023| 亚洲欧美视| 黄色毛片A片| 国产中文字幕在线| 亚洲一区中文字幕一区| 噜噜瑟| 色激情五月天| 97天天综合| 91在线美女| 色哟哟AⅤ| 欧美页片| 国产第25页在线观看| 亚洲女人毛茸茸91| 色五月激情综合网| 亚洲影视综合| 人妻一区久久二区三区色播| 1级午夜影院费免区| 2026国产精品视频| 精品国产久热在线观看| 美女黄页网站| 成人AV素股で擦久久| 久久亚洲国产成人| 91欧美性| 六月婷婷色综合| 欧美亚洲情色| 亚洲美女 晚间男人天堂 | 国产又粗又长视频| 久草久热| 国产一区二区三区视频在线看| 九九九九一级| 亚洲综合五月天| 你懂的在线观看区国产| 欧美亚州色的图| 9l视频自拍9l九色成人| A级片一区| 青青草无码视频| 日韩AV片| 上海一级黄片| 精品78| 色天欧美| 97亚洲综合电影| 91社操逼| 91成人18| 久久r精品| 欧美在线|亚洲| 夜夜嗨绯色| 亚洲天堂自拍| 精品二区三四区五电影 | 欧美劲爆第一页| 刺激精品视频| 六月婷婷综合| 日本免费专区| 久久久97| 九九九九一区| 亚洲天堂情色| 九九久久九九久久| 夜夜影视四色| 91久久九九精品国产综合| 在线 欧美 亚洲| 国产60区。| 99啪啪| 久久av一级av少妇av高潮| 91欧美网| 少妇熟女视频一区二区三区| 亚洲男人的天堂AV| 久热免费视频| 欧美成va视频网站| 十八禁av无码免费网站APP| 一本久道久久综合狠狠爱一密臀精 | 97在线观看免费视频| 久久人人爽av亚洲精品天堂桃色| 亚洲情色一区综合| 99只有精品| 久久‘黄片视频| 国产女同视频在线播放| 91色色网站| 啊啊啊啊好疼| 淫纸中9区| 亚洲大色鬼| 男人的天堂日本东京热| 97青娱乐超碰久久| 日韩av熟女一区二区三区成人| 激情五月综合网| 丰满的三级少妇欧美久久久| 国产 日韩 欧美 中文 另类,国产 欧美 另类 制服 变态,高清 日韩 欧美 中文,高 | 视频国产成人精品日本亚洲18| 精品国产99999| 青青操日韩| 97精品视频在线播放| 欧美色图人妻| 亚洲综合图片在线| 午夜综合在线| 东方亚洲在线操逼天堂| 日本熟妇精品九九| 亚洲999综合| 日韩欧美中文字| 我中文字幕6区| 欧美精品三区| 中文日韩欧美熟| 欧美手机在线综合| 九九九国产精品| 欧美日韩不卡传媒| 精品无码少妇| 91亚洲在线| 亚洲美女av无码| 私人尤物在线精品不卡| 人人操人人干xxx| 午夜a成v人电影| 曰韩操B| 亚洲欧美一区二区三区一猛片| 色青青久久影视| 日韩啪啪视频| 国产亚洲人妻综合日韩 久久| 啊…啊…操我用力操我| 国产suv精品一区二区四| 91伊人影视综合| 97频视在线| 久久久久久免费电影| 欧美激情内射| 碰碰97| 日韩无码a片| 亚洲成人福利电影免费| 色婷婷电影| 人人澡人人爽人人精品| 免费网色网站| 88在线一区二区三区| 91美女片在线| 欧美十八禁视频| 国产精品久久成人免费| 天天干天天干天天干| 嗯嗯啊啊亚欧精品| 色哟哟精品1精品2| 欧美色图 色综合图| 国产9 9在线 | 亚洲| 丁香六月东京热| 吻戏激情性巴克| 97色涩| 色区97| 波多野结衣一级视频| 啪啪综合网| 青青草大香蕉在线视频| 婷婷九月丁香| 在线有码中文字幕| 婷婷久月| 美女黑人91神马| 精品熟女呻吟久久91| 中文字幕91页| 亚洲另类久操网| 天天天堂影视日韩亚洲91| 久久九色| 亚洲AV永久无码一区仙野| 97 超碰 人人做 人人爱| 久久久青草青青国产亚洲免观精品高清完整版_97久久综合区小说区图片区,国精品 | 丝袜翘臀后入欧美校园亚洲自拍另类小说一区中文字幕少妇诱惑 | 麻豆久久久一区二区| 天天舔天天日天天射| 欧在线一二区| 亚州AV无码国产精品| 在线 亚洲 网爆 自拍| 亚洲欧美国产精品久久久久久久| 欧洲亚洲国产综合在线| 久久男人的天堂国产| 欧美色综合图片| 91被操| 91亚洲黑人| 亚洲情色 自拍| www.91逼逼.com| 亚洲精品天天影视综合网 | 日韩 女同 综合| 亚洲欧洲无码bt精品合集| 99热网站| 五月天精品| 日本高清_区二区三区| 亚洲视频,小说| 97爱爱| 天天摸,夜夜摸| 97超碰公开| 欧美日韩国产色图在线| 亚洲成人色情五月天丁香花| 精品人妻免费观看| 九色 人妻 大香蕉| 美欧色综合| 久9热| 9美女超碰在线免费观看| 91人妻做a观看视频| 人人超碰在线观看黄| 亚洲日韩在线a不卡99精品| 熟女丝袜视频| 91操熟女| 欧美亚洲第1页| 粉嫩av平台| 婷婷五月天久久精品视频一区二区三区 | 欧美性高潮| 婷婷五月天色色| 高颜值美女口爆高潮浪叫| 精品一区二区亚洲国产| 91麻豆天美国产欧美高潮| 日韩不卡毛片Av免费高清| 青青草久草AV| 小情侣高清国产在线视频| 操逼无毒无码免费视频| 成人性爱全视频观看| 日韩丨制服丨中文|在线| 国产激情av女片自拍| 国产青青综合伊人| 五月婷婷六月丁香| 久久久久久九| 91色鬼| 久操凹凸视频| 天天搞欧美| 三级三级三级日本99| 日韩欧美性爱电影在线观看| 蜜臀人妻少妇久久在线观看| 精品久久久久久中文字幕三区| 黑人精品久久97| 精品久久久亚洲AV成人网站| 亚洲综合20p| 麻豆AV一区二区| 小视频玖玖| 综合亚州欧美| 性色avv| 久久人人爽爽人人爽人人片αV| 插入粉嫩少妇视频| 五月丁香色综合| 亚洲欧洲成人在线电影| 久久久久密臀视频| 久久精品性| 国产亚洲色婷婷久久99精品91 - 百度| 久久99手机免费视频| 中文一区在线视频| 青青草中文字幕| 嗯嗯啊好大| 中文字幕成人| 内射老妇BBWX0C0CK| 中文字幕免费看| 天天日日舔舔| 亚洲国产一级精品毛一级精品看免费视频| 欧美色97| 久久一二三四五六七八九区区| 中文自拍欧美影视| 亚洲欧美日韩制服另类| 久久久久久久久久久久久女过产乱-少妇高潮一区二区三区喷水-成人AV | 国产精品自在线发布| 999999精品| 欧洲亚洲天堂精品| 久悠悠av| 天天躁日日躁AAA片李宗瑞| 日本精品五区| 久久久少妇诱惑精品视频| 伊人久久青青草| 国产精品免费视频人成| 国产午夜精品理论片一二三区区| 亚洲骚男同com| 欲色影视综合吧| 97色色视频| 温婉少妇玩3p| 黄色免费网| 啪啪自拍九九综合| 亚洲涩图欧美| 中文在线久久字幕| 天天综合网国产| 欧美顶级黄片AAAAA在线免费看| 国产精品白丝在线播放| 青娱乐91| 欧美97网| 国产吹潮女在线观看| 欧美香蕉视xxx| 久久香蕉国产传媒一区剧情天美| 蜜臀操逼黄色视频操的好爽| 翔田千里无码中出中文字幕| 韩国三级色呦呦| 少妇天堂| 欧美情色亚洲| 久久精品无码熟妇一区二区三区视频导航| 久久精品免视看国产成人﹣蜜臀av一区. 久久精品免视看国产成人,蜜臀av一区 | 日本高清电影欧美色图| 青草av在线| 九九无码视频| 免费av大片| 国产美女mm131爽爽爽爽| 俄罗斯一区二区视频在线观看| 国产色图乱伦| 亚洲国产成人精品女人久久久| 欧美日韩小说| 高颜值美女口爆高潮浪叫| 老鸭窝日丰县女人| 中文字幕免费看| 欧美丝袜美女电影一二三四区| 天天天天做夜夜夜夜做| 日韩av色图综合| 成人色女网| 91精品人妻一区二区-全集完整版免费正片国语-B02AV | 又粗又长又爽在线观看| 无码外流操逼视频| 91精品亚洲内射孕妇| 久久久久久久免费A片国产成a人亚洲精∨品无码| 蜜臀久久99精品久久久久电影| 中文字幕无码不卡啪啪| 日韩av电影网站| 久久久精品九| AV天堂因数| 骚女天天综合网| 日日爱99| 少妇熟女1区2区3区| 国产亚洲精品精AV.| 国产精品亚洲无码| 国产精品一区二区校花| 国产精品久久久久久久毛片1| 18禁的网站在线| 日韩中文字幕视频在线观看| 中文激情网| 97资源超碰| 中文97国产| 人妻熟女一区二区| 97伦综合| 伊人天天久久动态图| 无码操逼视频一下| 亚洲av影音先锋| 超碰AV在线| 国产四虎在线| 91美女视频在线免费观看| 亚洲交换| 亚洲欧洲激情卡通另类文学四射小说网站| 女性喷水高潮在线观看| 午夜超爽| 色色婷婷丁香| 蜜奶av| 天天碰久久入| 手机看片91人妻| 太久视频| 日韩乱码Av| 亚洲精品一卡二卡三卡福利视频网站| 色哟哟av| 欧美人与动性人交a| 夜夜影视四色| 亚洲91极品| 丝袜翘臀后入欧美校园亚洲自拍另类小说一区中文字幕少妇诱惑 | 女生久久网| 亚洲欧洲综合av在线| 啊视频在线| 青青草视频久久久久| 国产熟码AV| 一区二区三区美女超清| 激情接吻视频久久久久久| 久久精品视-一级做a爰片性色毛片16美国-中国女与老外在线精品 | 91亚洲情色| 国产精品久久久久无码A√| 大香蕉五月天婷婷| 熟妇艹鸡八| 超碰色图| 成人乱码一区二区三少妇| 欧美在线官网| 色噜噜人妻av中文字幕| 日韩激情毛片一级久久久| 欧美色五月| 锕锕好爽 死我在线观看| 综合网亚洲| 巨爆乳肉感一区二区三区竹菊影视| 天堂国产AV| 久热热| 人人插人人搞人人操| 神马久久网| 日韩欧无码一区二区三区免费不卡| 中文字幕精品区先锋资源| 麻豆AV96熟妇人妻| 上特色A在线| 激情四射五月天| 亚洲十八禁止| 国产在线视频二区| 久久99国产综合精品女同| 欧美视频一区二区三区| 日韩精品中文字幕一| 国产传媒1234区| 亚洲一区二区AV| 蜜桃无码AV一区二区| 国产精品亚洲日韩骚欢乐谷最新地址发布页huanieguty性屋娱乐妖精视频 | 91亚洲图片| 十八禁啪啦拍视频无遮挡| 另类视频在线| 黄色大片免费在线| 97se综合网| 亚洲无码视频免费在线观看网址!| 六月激情网| 精品日日人妻| 色综合色综合网| 夜夜春夜夜操| 操逼网站视频漫画国产| 久久精品国产精品| 精品人妻一区| 精品中文日韩字幕视频| 中文字幕国产| 午夜毛片高清免费不卡| 三级AV入口| 九九九九九用不成了| 天天干干天天干干| 国产性爱欧美性爱在线| 中文字幕一区二区在线日韩精品| 西西美女视频网| 精品人妻1区| 97精品国产精品免费观看| 手机在线A片| 狠狠操狠狠燥| 成人丁香五月| 亚洲天天更新| 欧美综合97www| 亚洲天堂五月天国产| 色色综合97| 国产熟女乱论| 日韩91网| 国产一区二区三区中文字幕| 久操视频在线观看| 久久熟妇五十路一区| 日韩综合无码色欲vv| 综合激情一一91| 性色av婷婷久久一区二区点复制| 99re欧美| 亚洲 se图 欧美电影| 激情国产乱伦Av| 亚洲在线A| 久久精品人体| 精品无码久久久久久国产浪潮| 日产成人久久| 五月婷婷深深爱| 你懂的在线观看区国产| www被窝色com| 色婷婷视频| 久久久久亚洲熟妇熟女| 加勒比伊人影院| 欧美成人精品A片免费一区99| 97爱爱| 青青草日韩免费观看高清在线| 国产超碰人人操| 国产偷拍网站| 成年人免费观看网站| 啊啊啊好爽快点啊啊啊嗯嗯| 中文字幕第9页萱萱影音先锋| 中文字幕视频免费| 色婷婷小说| 中文字幕一区二区三区字幕| 日韩少妇丰满亚洲| 大奶啊啊好爽| 一本精品日本在线视频精品 | 亚洲精品乱码线路中文字幕| 国产精品久久久久999| 人妻少妇精品一区二区三区| 国产美女裸体秘 永久无遮挡| 亚洲性刺激| 久久久久久久久久久久久久久乱码| 日韩av一级黄片| 偷拍综合网| 91蜜桃传媒精品久久久一区二区| 国产人妻精品久久久一区二区三区 | 欧美很很操视频| 三男一女不戴套的A片| 精品女同一区| 一区二区三区日韩欧美| 久久久国产三级黄色片| 懂色中文一区二区三区| 色悠悠伊人网五月天| 伊人国产视频| 极品色社| 操逼网站地址| 琪琪精品免费一区二区三区| 色香欲影| 久草成人影片| 欧美亚洲高清不卡| 欧美不卡在线美女| 人人考人人摸人人干| 操逼免费视频无码国产| 农村女一级毛卡片| 90后后入| 免费97视频| 亚洲美女色图| 亚洲日韩av一区二区三区百合| 中文字幕黄色片| 国产成人免费观看在线视频| 被操高清无码视频| 96精品久久| 欧美丝袜91| 超碰97综合网| 久久久久久久9最新免费视频观看| 欧美色图中文字幕| 色av中文字| 能看的AV| 欧美日韩成人| 99久久综合| 六月色色| 91色婷婷综合久久中文字幕二区| 久久激情亚洲精品无码?V| 欧美的性爱网站免费| 中文字幕第9页萱萱影音先锋| 日本一区二区不卡| 天天噜| 中文字幕99999| 亚洲天堂男人的天堂| 欧美se综合| 综合色欧美| 久操免费观看| 日韩情色一区二区| 欧美啪啪色吧在线| 久 久无码人妻AV| 国产精品国产精品国产| 美女久久久久久久久久久| 男人天堂东京热| 中文视频在线观看| 天天看天天日天天操| 精品视频一区二区| 在线午夜成人无码视频| 超碰在线人人射| 国产亚洲精品一区二区三区| 波多野结衣之双飞调教在线播放| CCYY草草影院地址入口| 久久久国产av美女私房| 国产精品黄色三级av| 人人操人人操草草| 岛国成人av在线播放网址| 九九热超碰|