1、先構建基本的netty框架
再下面的代碼中我構建了一個最基本的netty實現websocket的框架,其他個性化部分再自行添加。
@Slf4j
public class TeacherServer {public void teacherStart(int port) throws InterruptedException {NioEventLoopGroup boss = new NioEventLoopGroup();NioEventLoopGroup worker = new NioEventLoopGroup(2);try{ServerBootstrap serverBootstrap = new ServerBootstrap().group(boss, worker).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel nsc) throws Exception {//http的編解碼器nsc.pipeline().addLast(new HttpServerCodec());//將多個快組成一個完整的http請求nsc.pipeline().addLast(new HttpObjectAggregator(65536));nsc.pipeline().addLast(new WebSocketServerProtocolHandler("/teacher", null, true, 65536 * 10,false,true));}});ChannelFuture cf = serverBootstrap.bind(port).sync();log.info("教師服務已開啟");cf.channel().closeFuture().sync();} finally {boss.shutdownGracefully().sync(); // 釋放線程池資源worker.shutdownGracefully().sync();}}//初始化public void init(int port){//異步啟動new Thread(() -> {try {teacherStart(port);} catch (InterruptedException e) {e.printStackTrace();}}).start();}
}
要實現websocket并且實現url傳參,我們需要關注WebSocketServerProtocolHandler處理的一個參數:checkStartsWith
這個參數我們需要把他設置成ture,為什么呢,讓我們來看一下源碼isWebSockerPath(),這方法是判斷具體的url是否和我設置的路徑相匹配:
可以看出如果checkStartWith設為false的話,則必須url和websocketPath相等,否則會返回false。如果設置為true的話則只需要websocketPath是具體的url的前綴就行。當最后返回false時,連接就無法建立。
我們看源碼這是一個處理類WebSocketServerProtocolHandshakeHandler,這是WebSocketServerProtocolHandler這個處理類再創建的時候給加pipelien()里的的,放在其之前,專門用來處理握手的處理器。可以看到如果返回false,則不會接下來進行握手操作,而是直接將消息返回給下一個處理器。如果這樣的話我們可以認為連接已經失敗。
所以我們如果要通過url傳參的話再構建WebSocketServerProtocolHandler對象時要將chaekStartWith設為true。
2、獲取url中的傳參
? ? ? ? 2.1 再沒建立連接前獲取url
? ? ? ? 因為websocket發起建立連接用的時http協議并攜帶升級協議的請求,后面服務端進行升級,將其升為websocket,那么我們可以再還未升級前,也就是再WebSocketServerProtocolHandler處理器前再新增一個處理器,讀取第一次發起的http請求。再其中獲取url,等獲取到初始化完后將這個處理器從pipelien中移除。
FullRequest是一個Java類,它表示一個完整的HTTP請求,包含請求方法、路徑、頭部和內容。它是Netty框架中的一個組件,用于處理網絡通信。
public class TeacherContineHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext channelHandlerContext, Object msg) throws Exception {if (msg instanceof FullHttpRequest){FullHttpRequest request = (FullHttpRequest) msg;String uri = request.uri();/**再這里編寫自己的操作*/// 在本channel上移除這個handler消息處理,即只處理一次,鑒權通過與否channelHandlerContext.pipeline().remove(TeacherContineHandler.class);}super.channelRead(channelHandlerContext, msg);}
}
ps:主要放置順序:?
2.2 建立后通過自定義事件HandshakeComplete獲取url?
其實netty已經為我們想好了,我也是看源碼才發現的,再連接建立完成后WebSocketServerProtocolHandshakeHandler會響應一個事件,再這個事件里我們可以獲取到我們想要的請求路徑和請求頭,我們通過這個方法不光可以再url上傳參,還能通過請求頭傳參。
?其中我們通過一個 那個futre對象,就是握手方法返回的,該對象是一個異步的操作結果,可以在完成時觸發回調函數。當其完成時我們通過這個對象判斷是否成功握手。如果握手成功,那么就調用localHandshakePromise的trySuccess方法,表示握手成功,并調用ctx.fireUserEventTriggered方法,觸發兩個用戶自定義的事件,分別是:
- ServerHandshakeStateEvent.HANDSHAKE_COMPLETE,表示握手完成的狀態事件。
- HandshakeComplete,表示握手完成的具體信息,包括req.uri()、req.headers()和handshaker.selectedSubprotocol(),分別表示WebSocket的URI地址、HttpRequest的頭信息和選擇的子協議。
那我們就可以通過監聽這個用戶自定義事件來獲取請求體了。具體做法就是我們需要實現userEventTriggered()方法這個方法就是用來再有用戶自定義事件發生時被調用的,
具體實現如下:
public class TeacherWebSocketHandler extends ChannelInboundHandlerAdapter {@Overridepublic void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete){WebSocketServerProtocolHandler.HandshakeComplete handshakeComplete = (WebSocketServerProtocolHandler.HandshakeComplete) evt;String s = handshakeComplete.requestUri();HttpHeaders entries = handshakeComplete.requestHeaders();/*** 實現自己的初始化操作*/}super.userEventTriggered(ctx, evt);}
}
3、項目啟動
我們的啟動代碼就寫在main方法當中,再這個方法進行初始化,記得傳入你想監聽的端口,如果你想監聽多個端口,可以安這樣的步驟之間重復再寫一遍就行,因為是異步啟動的。