netty搭建websocket
- 1、背景
- 2、websocket
- 3、netty
- 3.1 socket
- 3.2 Java IO模型
- 3.3 netty
- 3.3.1 概念:
- 3.3.2 三大特點:
- 3.3.3 主從Reactor架構圖
- 3.3.4 應用場景
- 4、springboot環境下使用netty搭建websocket
- 4.1 系統設計架構圖
- 4.2 架構中存在的六大經典問題
- 4.3 引入pom依賴和yml配置
- 4.4 Websocket 初始化器
- 4.5 websocket 通道初始化器
- 4.6 websocket 入站處理器
- 4.7 channel中任務佇列的執行緒任務
- 4.8 websocket啟動程式
- 4.9 問題六解決方案
- 4.10 前端代碼
- 4.11 wesocket在nginx中配置
- 4.12 效果圖
- 5、總結
1、背景
我們在實際的作業開發中,必然會遇到一些需要網頁與服務器端保持連接(起碼看上去是保持連接)的需求,比如類似微信網頁版的聊天類應用,比如需要頻繁更新頁面資料(實時資料例如天氣,電流電壓,pm2.5等等這樣類似的資料)的監控系統頁面或股票看盤頁面,比如服務器讀取MySQL或者redis或者第三方的資料主動推送給瀏覽器客戶端等等業務場景,我們通常采用如下幾種技術:
(1)短輪詢:前端老師利用ajax定期向服務器發起http請求,無論資料是否更新立馬回傳資料,這樣存在的缺點就是,一方面如果后端資料木有更新,那么這一次http請求就是無用的,另一方面高并發情況下,短鏈接的頻繁創建銷毀,以及客戶端數量過大造成過多無用的http請求,都會對服務器和帶寬造成壓力,短輪詢只適用于客戶端連接少,并發量不高的場景;
(2)長輪詢:利用comet不斷向服務器發起請求,服務器將請求暫時掛起,直到有新的資料的時候才回傳,相對短輪詢減少了請求次數得到了一定的優化,但是在高并發的場景下依然不適用;
(3)SSE:服務端推送(Server Send Event),在客戶端發起一次請求后會保持該連接,服務器端基于該連接持續向客戶端發送資料,從HTML5開始加入,
(4)Websocket:這是也是一種保持長連接的技術,并且是雙向的,從HTML5開始加入,并非完全基于HTTP,適合于頻繁和較大流量的雙向通訊場景,是服務器推送訊息功能的最佳實踐,而實作websocket的最佳方式,就是netty,
網上的很多netty搭建websocket的博文都不夠全面,有很多問題都木有解決方式,我通過實際作業中的經驗,把常遇到的問題總結了方法,下文會說到!
2、websocket
什么是websocekt呢?
websocket是一種在單個TCP連接上進行全雙工通信的協議,也就是一種保持長連接的技術,并且是雙向的,
websocket協議本身是構建在http協議之上的升級協議,客戶端首先向服務器端去建立連接,這個連接本身就是http協議只是在頭資訊中包含了一些websocket協議的相關資訊,一旦http連接建立之后,服務器端讀到這些websocket協議的相關資訊就將此協議升級成websocket協議,WebSocket使得客戶端和服務器之間的資料交換變得更加簡單,允許服務端主動向客戶端推送資料,在WebSocket API中,瀏覽器和服務器只需要完成一次握手,兩者之間就直接可以創建持久性的連接,并進行雙向資料傳輸,
簡單理解,就是一種通訊協議,重點是websocket的實作方式–netty,
3、netty
什么是netty呢?
我們在大學里學習Java基礎的時候,想必都學過socket網路編程吧!我們在這里一起復習一下!
3.1 socket
(1)網路編程本質就是說兩個設備之間資訊的發送與接收,通過操作相應API調度計算機硬體資源,并且利用管道(網線)進行資料互動的程序,相關技術點像ISO七層模型,TCP三次握手/四次揮手等網路編程的基礎不再贅述,
(2)而socket是對TCP/IP協議的封裝,Socket本身并不是協議,而是一個呼叫介面(API),通過Socket發起系統呼叫作業系統內核,我們才能使用TCP/IP協議,
(3)我們經常說的I/O,在計算機中指Input/Output,即輸入輸出,實質上IO分為兩種,一種是磁盤IO,磁盤上的資料讀取到用戶空間,那么這次資料轉移操作其實就是一次I/O操作,也就是一次檔案I/O,一種是網路IO,當一個客戶端和服務端之間相互通信,互動我們稱之為網路io(網路通訊)
3.2 Java IO模型
有BIO(同步阻塞IO)、NIO(同步非阻塞IO)、AIO(異步IO),netty就是一個NIO的高性能的框架,相關netty的技術點,關注小編,后續會出相關技術堆疊的細節,
(1)BIO:同步阻塞IO模型,適用于連接數目比較少且固定的架構,這種方式對服務器資源要求比較高,并發局限于應用中,服務器實作模式為一個連接一個執行緒,即客戶端有連 接請求時服務器端就需要啟動一個執行緒進行處理,如果這個連接不做任何事情會造 成不必要的執行緒開銷,可以通過執行緒池機制改善(實作多個客戶連接服務器),

(2)NIO:同步非阻塞IO模型,適用于連接資料多且連接比較短的架構,如聊天服務器,彈幕系統,服務器間通訊等;服務器實作模式為一個執行緒處理多個連接(一個請求一個執行緒),包含Selector 、Channel 、Buffer三大組件,

①selector選擇器:用一個執行緒處理多個客戶端的連接,就會使用到selector選擇器,selector用于監聽多個通道上是否有事件發生(比如連接請求,資料到達等),如果有事件發生,便獲取事件然后針對于每個事件進行相應的處理,因此可以使用單個執行緒就可以監聽多個客戶端通道,
②channel通道:Channel管道和Java IO中的Stream(流)是差不多一個等級的,只不過Stream是單向的,譬如:InputStream, OutputStream.而Channel是雙向的,同時進行讀寫資料,而流只能讀或者寫,可以實作異步讀寫資料,可以從緩沖區讀資料,也可以寫資料到緩沖區,
③buffer緩沖區:Buffer本質上是一個可以讀寫的記憶體塊,可以理解成容器物件,底層是有一個陣列,通過buffer實作非阻塞機制,該物件提供了一組方法,可以輕松的使用記憶體塊,緩沖區物件內置了一些機制,能夠跟蹤和記錄緩沖區的狀態變化情況,Channel提供了從檔案,網路讀取資料的通道,但是讀取和寫入的資料必須經過buffer,
(3)AIO:異步IO模型,使用于連接數目多且連接比較長(重操作)的架構,比如相冊服務器,充分 呼叫OS參與并發操作,編程比較復雜,在Linux底層用epoll(一種輪詢模型),aio多包了一層封裝,aio的api更好用,Windows上的aio是自己實作的,不是輪詢模型是事件模型,完成埠實作的,要比linxu上的aio效率高,

3.3 netty
3.3.1 概念:
netty是一個開源異步的事件驅動的網路應用程式框架,用于快速開發可維護的高性能協議服務器和客戶端,
3.3.2 三大特點:
①高并發:Netty 是一款基于 NIO(Nonblocking IO,非阻塞IO)開發異步事件驅動的高性能網路通信框架,nio使用了select模型(多路復用器技術),從而使得系統在單執行緒的情況下可以同時處理多個客戶端請求,Netty使用了Reactor模型,Reactor模型有三種多執行緒模型,netty是在主從 Reactor 多執行緒模型上做了一定的改進,Netty有兩個執行緒組,一個作為bossGroup執行緒組,負責客戶端接收,一個workerGroup執行緒組負責作業執行緒的作業(與客戶端的IO操作和任務操作等等),Netty 的所有 IO 操作都是異步非阻塞的,通過 Future-Listener 機制,用戶可以方便的主動獲取或者通過通知機制獲得 IO 操作結果,他的并發性能得到了很大提高,
②傳輸快:Netty 的傳輸依賴于零拷貝特性,實作了更高效率的傳輸,零拷貝要求內核(kernel)直接將資料從磁盤檔案拷貝到Socket緩沖區(套接字),而無須通過應用程式,零拷貝減少不必要的記憶體拷貝,不僅提高了應用程式的性能,而且減少了內核態和用戶態背景關系切換,
③封裝好:Netty 封裝了 NIO 操作的很多細節,提供了易于使用呼叫介面,如果你用過或者見過nio代碼,你會感覺netty真的yyds!!!
3.3.3 主從Reactor架構圖

說明:①Reactor回應式編程(事件驅動模型):一般有一個主回圈和一個任務佇列,所有事件只管往佇列里塞,主回圈則從佇列里取出并處理,如果不依賴于多路復用處理多個任務就會需要多執行緒(與連接數對等) ,但是依賴于多路復用,這個回圈就可以在單執行緒的情況下處理多個連接,無論是哪個連接發生了什么事件,都會被主回圈從佇列取出并處理(可能用回呼函式處理等) ,也就是說程式的走向由事件驅動.
②mainReactor:主Reactor負責 單執行緒就可以接受所有客戶端連接
③subReactor:子Reactor負責 多執行緒處理客戶端的讀寫IO事件
④ThreadPool:執行緒池負責 處理業務耗時的操作
3.3.4 應用場景
①現在物聯網的應用無處不在,大量的專案都牽涉到應用傳感器和服務器端的資料通信,Netty作為基礎通信組件進行網路編程,
②現在互聯網系統講究的都是高并發、分布式、微服務,各類訊息滿天飛,Netty在這類架構里面的應用可謂是如魚得水,如果你對當前的各種應用服務器不爽,那么完全可以基于Netty來實作自己的HTTP服務器,FTP服務器,UDP服務器,RPC服務器,WebSocket服務器,Redis的Proxy服務器,MySQL的Proxy服務器等等,
現在非常多的開源軟體都是基于netty開發的,例如阿里分布式服務框架 Dubbo 的 RPC 框架,淘寶的訊息中間件 RocketMQ;
③游戲行業:無論是手游服務端還是大型的網路游戲,Java 語言得到了越來越廣泛的應用,Netty 作為高性能的基礎通信組件,它本身提供了 TCP/UDP 和 HTTP 協議堆疊,地圖服務器之間可以方便的通過 Netty 進行高性能的通信,
④大資料:開源集群運算框架 Spark;分布式計算框架 Storm;
4、springboot環境下使用netty搭建websocket
4.1 系統設計架構圖

4.2 架構中存在的六大經典問題
第一個問題:客戶端和服務端單獨通信,怎么實作?
第二個問題:單機中websocekt主動向所有客戶端推送訊息如何實作?在集群中如何實作?
第三個問題:單機如何統計同時在線的客戶數量?websocket集群如何統計在線的客戶數量呢?
第四個問題:由于客戶端和websocket服務器集群中的某個節點建立長連接是隨機的,如何解決服務端向某個或某些部分客戶端推送訊息?
第五個問題:websocket服務端周期性向客戶端推送訊息,單機或集群中如何實作?
第六個問題:websocket集群中一個客戶端向其他客戶端主動發送訊息,如何實作?
福利來啦!!以上所有問題,已在代碼中全部解決并實踐!!!
4.3 引入pom依賴和yml配置
(1)pom依賴
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.36.Final</version>
</dependency>
</dependencies>
(2)yml配置
websocket:
port: 7000 #埠
url: /msg #訪問url
(3) 客戶端和服務端互動的訊息體
package com.wander.netty.websocket.yeelight;
import lombok.Data;
import java.io.Serializable;
/**
* @Author WDYin
* @Date 2021/8/13
* @Description
**/
@Data
public class MessageRequest implements Serializable {
private Long unionId;
private Integer current = 1;
private Integer size = 10;
}
4.4 Websocket 初始化器
package com.wander.netty.websocket.yeelight;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* @Author WDYin
* @Date 2021/6/10
* @Description websocket初始化器
**/
@Slf4j
@Component
public class WebsocketInitialization {
@Resource
private WebsocketChannelInitializer websocketChannelInitializer;
@Value("${websocket.port}")
private Integer port;
@Async
public void init() throws InterruptedException {
//bossGroup連接執行緒組,主要負責接受客戶端連接,一般一個執行緒足矣
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
//workerGroup作業執行緒組,主要負責網路IO讀寫
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//啟動輔助類
ServerBootstrap serverBootstrap = new ServerBootstrap();
//bootstrap系結兩個執行緒組
serverBootstrap.group(bossGroup, workerGroup);
//設定通道為NioChannel
serverBootstrap.channel(NioServerSocketChannel.class);
//可以對入站\出站事件進行日志記錄,從而方便我們進行問題排查,
serverBootstrap.handler(new LoggingHandler(LogLevel.INFO));
//設定自定義的通道初始化器,用于入站操作
serverBootstrap.childHandler(websocketChannelInitializer);
//啟動服務器,本質是Java程式發起系統呼叫,然后內核底層起了一個處于監聽狀態的服務,生成一個檔案描述符FD
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
//異步
channelFuture.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
4.5 websocket 通道初始化器
package com.wander.netty.websocket.yeelight;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @Author WDYin
* @Date 2021/6/10
* @Description websocket通道初始化器
**/
@Component
public class WebsocketChannelInitializer extends ChannelInitializer<SocketChannel> {
@Autowired
private WebSocketHandler webSocketHandler;
@Value("${websocket.url}")
private String websocketUrl;
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//獲取pipeline通道
ChannelPipeline pipeline = socketChannel.pipeline();
//因為基于http協議,使用http的編碼和解碼器
pipeline.addLast(new HttpServerCodec());
//是以塊方式寫,添加ChunkedWriteHandler處理器
pipeline.addLast(new ChunkedWriteHandler());
/*
說明
1. http資料在傳輸程序中是分段, HttpObjectAggregator ,就是可以將多個段聚合
2. 這就就是為什么,當瀏覽器發送大量資料時,就會發出多次http請求
*/
pipeline.addLast(new HttpObjectAggregator(8192));
/* 說明
1. 對應websocket ,它的資料是以 幀(frame) 形式傳遞
2. 可以看到WebSocketFrame 下面有六個子類
3. 瀏覽器請求時 ws://localhost:7000/msg 表示請求的uri
4. WebSocketServerProtocolHandler 核心功能是將 http協議升級為 ws協議 , 保持長連接
5. 是通過一個 狀態碼 101
*/
pipeline.addLast(new WebSocketServerProtocolHandler(websocketUrl));
//自定義的handler ,處理業務邏輯
pipeline.addLast(webSocketHandler);
}
}
4.6 websocket 入站處理器
package com.wander.netty.websocket.yeelight;
import com.alibaba.fastjson.JSON;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.Future;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
* @Author WDYin
* @Date 2021/6/10
* @Description websocket處理器
**/
@Slf4j
@Component
@ChannelHandler.Sharable//保證處理器,在整個生命周期中就是以單例的形式存在,方便統計客戶端的在線數量
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
//通道map,存盤channel,用于群發訊息,以及統計客戶端的在線數量,解決問題問題三,如果是集群環境使用redis的hash資料型別存盤即可
private static Map<String, Channel> channelMap = new ConcurrentHashMap<>();
//任務map,存盤future,用于停止佇列任務
private static Map<String, Future> futureMap = new ConcurrentHashMap<>();
//存盤channel的id和用戶主鍵的映射,客戶端保證用戶主鍵傳入的是唯一值,解決問題四,如果是集群中需要換成redis的hash資料型別存盤即可
private static Map<String, Long> clientMap = new ConcurrentHashMap<>();
/**
* 客戶端發送給服務端的訊息
*
* @param ctx
* @param msg
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
try {
//接受客戶端發送的訊息
MessageRequest messageRequest = JSON.parseObject(msg.text(), MessageRequest.class);
//每個channel都有id,asLongText是全域channel唯一id
String key = ctx.channel().id().asLongText();
//存盤channel的id和用戶的主鍵
clientMap.put(key, messageRequest.getUnionId());
log.info("接受客戶端的訊息......" + ctx.channel().remoteAddress() + "-引數[" + messageRequest.getUnionId() + "]");
if (!channelMap.containsKey(key)) {
//使用channel中的任務佇列,做周期回圈推送客戶端訊息,解決問題二和問題五
Future future = ctx.channel().eventLoop().scheduleAtFixedRate(new WebsocketRunnable(ctx, messageRequest), 0, 10, TimeUnit.SECONDS);
//存盤客戶端和服務的通信的Chanel
channelMap.put(key, ctx.channel());
//存盤每個channel中的future,保證每個channel中有一個定時任務在執行
futureMap.put(key, future);
} else {
//每次客戶端和服務的主動通信,和服務端周期向客戶端推送訊息互不影響 解決問題一
ctx.channel().writeAndFlush(new TextWebSocketFrame(Thread.currentThread().getName() + "服務器時間" + LocalDateTime.now() + "wdy"));
}
} catch (Exception e) {
log.error("websocket服務器推送訊息發生錯誤:", e);
}
}
/**
* 客戶端連接時候的操作
*
* @param ctx
* @throws Exception
*/
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
log.info("一個客戶端連接......" + ctx.channel().remoteAddress() + Thread.currentThread().getName());
}
/**
* 客戶端掉線時的操作
*
* @param ctx
* @throws Exception
*/
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
String key = ctx.channel().id().asLongText();
//移除通信過的channel
channelMap.remove(key);
//移除和用戶系結的channel
clientMap.remove(key);
//關閉掉線客戶端的future
Optional.ofNullable(futureMap.get(key)).ifPresent(future -> {
future.cancel(true);
futureMap.remove(key);
});
log.info("一個客戶端移除......" + ctx.channel().remoteAddress());
ctx.close(); //關閉連接
}
/**
* 發生例外時執行的操作
*
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
String key = ctx.channel().id().asLongText();
//移除通信過的channel
channelMap.remove(key);
//移除和用戶系結的channel
clientMap.remove(key);
//移除定時任務
Optional.ofNullable(futureMap.get(key)).ifPresent(future -> {
future.cancel(true);
futureMap.remove(key);
});
//關閉長連接
ctx.close();
log.info("例外發生 " + cause.getMessage());
}
public static Map<String, Channel> getChannelMap() {
return channelMap;
}
public static Map<String, Future> getFutureMap() {
return futureMap;
}
public static Map<String, Long> getClientMap() {
return clientMap;
}
}
4.7 channel中任務佇列的執行緒任務
package com.wander.netty.websocket.yeelight;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
/**
* @Author WDYin
* @Date 2021/8/10
* @Description websocket程式
**/
@Slf4j
@Component
public class WebsocketApplication {
@Resource
private WebsocketInitialization websocketInitialization;
@PostConstruct
public void start() {
try {
log.info(Thread.currentThread().getName() + ":websocket啟動中......");
websocketInitialization.init();
log.info(Thread.currentThread().getName() + ":websocket啟動成功!!!");
} catch (Exception e) {
log.error("websocket發生錯誤:",e);
}
}
}
4.8 websocket啟動程式
package com.wander.netty.websocket.yeelight;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
/**
* @Author WDYin
* @Date 2021/8/10
* @Description websocket程式
**/
@Slf4j
@Component
public class WebsocketApplication {
@Resource
private WebsocketInitialization websocketInitialization;
@PostConstruct
public void start() {
try {
log.info(Thread.currentThread().getName() + ":websocket啟動中......");
websocketInitialization.init();
log.info(Thread.currentThread().getName() + ":websocket啟動成功!!!");
} catch (Exception e) {
log.error("websocket發生錯誤:",e);
}
}
}
4.9 問題六解決方案
package com.wander.netty.websocket.wdy;
import com.wander.netty.websocket.yeelight.WebSocketHandler;
import io.netty.channel.Channel;
import io.netty.channel.ChannelId;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
/**
* @Author WDYin
* @Date 2021/9/12
* @Description
**/
@RequestMapping("index")
@Controller
public class WebsocketController {
/**
*
* @param id 用戶主鍵
* @param idList 要把訊息發送給其他用戶的主鍵
*/
@RequestMapping("hello1")
private void hello(Long id, List<Long> idList){
//獲取所有連接的客戶端,如果是集群環境使用redis的hash資料型別存盤即可
Map<String, Channel> channelMap = WebSocketHandler.getChannelMap();
//獲取與用戶主鍵系結的channel,如果是集群環境使用redis的hash資料型別存盤即可
Map<String, Long> clientMap = WebSocketHandler.getClientMap();
//解決問題六,websocket集群中一個客戶端向其他客戶端主動發送訊息,如何實作?
clientMap.forEach((k,v)->{
if (idList.contains(v)){
Channel channel = channelMap.get(k);
channel.eventLoop().execute(() -> channel.writeAndFlush(new TextWebSocketFrame(Thread.currentThread().getName()+"服務器時間" + LocalDateTime.now() + "wdy")));
}
});
}
}
4.10 前端代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
var socket;
//判斷當前瀏覽器是否支持websocket
if(window.WebSocket) {
socket = new WebSocket("ws://localhost:7000/msg");
//相當于channelReado, ev 收到服務器端回送的訊息
socket.onmessage = function (ev) {
var rt = document.getElementById("responseText");
rt.value = rt.value + "\n" + ev.data;
}
//相當于連接開啟(感知到連接開啟)
socket.onopen = function (ev) {
var rt = document.getElementById("responseText");
rt.value = "連接開啟了.."
}
//相當于連接關閉(感知到連接關閉)
socket.onclose = function (ev) {
var rt = document.getElementById("responseText");
rt.value = rt.value + "\n" + "連接關閉了.."
}
} else {
alert("當前瀏覽器不支持websocket")
}
//發送訊息到服務器
function send(websocketMessage) {
if(!window.socket) { //先判斷socket是否創建好
return;
}
if(socket.readyState == WebSocket.OPEN) {
//通過socket 發送訊息
socket.send(websocketMessage)
} else {
alert("連接沒有開啟");
}
}
</script>
<form onsubmit="return false">
<textarea name="websocketMessage" style="height: 300px; width: 300px"></textarea>
<input type="button" value="發生訊息" onclick="send(this.form.websocketMessage.value)">
<textarea id="responseText" style="height: 300px; width: 300px"></textarea>
<input type="button" value="清空內容" onclick="document.getElementById('responseText').value=''">
</form>
</body>
</html>
4.11 wesocket在nginx中配置
nginx.conf中的配置
#第一步:
upstream websocket-router {
server 192.168.1.31:7000 max_fails=10 weight=1 fail_timeout=5s;
keepalive 1000;
}
#第二步:
server {
listen 80; #監聽80埠
server_name websocket.wdy.com; #域名配置
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
client_max_body_size 100M;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade; #支持wss
proxy_set_header Connection "upgrade"; #支持wssi
proxy_pass http://websocket-router; #代理路由
root html;
index index.html index.htm;
}
}
4.12 效果圖
springboot啟動后,用瀏覽器打開前端頁面:

5、總結
本文主要講述了網路編程相關的IO模型以及NIO框架netty 如何搭建websocket,小伙伴有遇到說明問題,可評論留言,我會及時回復,關注小編,后續會在博客中講解netty詳細的技術堆疊!
至此,一個高性能,高并發的websocket集群搭建完畢!可以找領導漲薪啦!!
對設計模式不是很理解的童靴可以看小編另一篇文章
2.5萬字詳解Java 23種設計模式的簡介和創建型模式(簡單工廠、工廠方法、抽象工廠、單例-多執行緒安全詳解、建造者、原型)的詳細解讀、UML類圖、及代碼演示
代碼中如何干掉太多的if else即if else的多種替代方案以提高代碼質量通過公司代碼審查
感興趣的小伙伴可以看看小編其他文章
微服務 分布式 集群 負載均衡詳述
MySQL查詢陳述句執行順序以及各關鍵字的詳解
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/299659.html
標籤:java
