<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一文搞懂四種 WebSocket 使用方式,建議收藏!!!

          共 29401字,需瀏覽 59分鐘

           ·

          2022-06-10 05:10

          作者:和耳朵

          來源:SegmentFault  思否社區(qū) 


          在上家公司做IM消息系統(tǒng)的時候,一直是使用 WebSocket 作為收發(fā)消息的基礎組件,今天就和大家聊聊在 Java 中,使用 WebSocket 所常見的四種姿勢,如果大家以后或者現(xiàn)在碰到有要使用 WebSoocket 的情況可以做個參考。



          上面的思維導圖已經(jīng)給大家列出了三種使用 WebSocket 的方式,下文會對它們的特點進行一一解讀,不同的方式具有不同的特點,我們先按下不表。

          在這里,我想讓大家思考一下我在思維導圖中列舉的第四種做 WebScoket 支持的方案可能是什么?不知道大家能不能猜對,后文將會給出答案。

          本文代碼:以下倉庫中 spring-websocket 模塊,拉整個倉庫下來后可在 IDEA Maven 工具欄中單獨編譯此模塊。

          • Github
            https://github.com/rookie-ricardo/spring-boot-learning-demo
          • Gitee
            https://gitee.com/he-erduo/spring-boot-learning-demo


          WS簡介


          ?

          在正式開始之前,我覺得有必要簡單介紹一下 WebSocket 協(xié)議,引入任何一個東西之前都有必要知道我們?yōu)槭裁葱枰?br style="box-sizing: border-box;">
          在 Web 開發(fā)領域,我們最常用的協(xié)議是 HTTP,HTTP 協(xié)議和 WS 協(xié)議都是基于 TCP 所做的封裝,但是 HTTP 協(xié)議從一開始便被設計成請求 -> 響應的模式,所以在很長一段時間內 HTTP 都是只能從客戶端發(fā)向服務端,并不具備從服務端主動推送消息的功能,這也導致在瀏覽器端想要做到服務器主動推送的效果只能用一些輪詢和長輪詢的方案來做,但因為它們并不是真正的全雙工,所以在消耗資源多的同時,實時性也沒理想中那么好。

          既然市場有需求,那肯定也會有對應的新技術出現(xiàn),WebSocket 就是這樣的背景下被開發(fā)與制定出來的,并且它作為 HTML5 規(guī)范的一部分,得到了所有主流瀏覽器的支持,同時它還兼容了 HTTP 協(xié)議,默認使用 HTTP 的80端口和443端口,同時使用 HTTP header 進行協(xié)議升級。

          和 HTTP 相比,WS 至少有以下幾個優(yōu)點:

          1. 使用的資源更少:因為它的頭更小。
          2. 實時性更強:服務端可以通過連接主動向客戶端推送消息。
          3. 有狀態(tài):開啟鏈接之后可以不用每次都攜帶狀態(tài)信息。

          除了這幾個優(yōu)點以外,我覺得對于 WS 我們開發(fā)人員起碼還要了解它的握手過程和協(xié)議幀的意義,這就像學習 TCP 的時候需要了解 TCP 頭每個字節(jié)幀對應的意義一樣。

          像握手過程我就不說了,因為它復用了 HTTP 頭只需要在
          維基百科(阮一峰的文章講的也很明白)上面看一下就明白了,像協(xié)議幀的話無非就是:標識符、操作符、數(shù)據(jù)、數(shù)據(jù)長度這些協(xié)議通用幀,基本都沒有深入了解的必要,我認為一般只需要關心 WS 的操作符就可以了。

          WS 的操作符代表了 WS 的消息類型,它的消息類型主要有如下六種:

          1. 文本消息
          2. 二進制消息
          3. 分片消息(分片消息代表此消息是一個某個消息中的一部分,想想大文件分片)
          4. 連接關閉消息
          5. PING 消息
          6. PONG 消息(PING的回復就是PONG)

          那我們既然知道了 WS 主要有以上六種操作,那么一個正常的 WS 框架應當可以很輕松的處理以上這幾種消息,所以接下來就是本文的中心內容,看看以下這幾種 WS 框架能不能很方便的處理這幾種 WS 消息。

          J2EE方式



          先來 J2EE,一般我把 javax 包里面對 JavaWeb 的擴展都叫做 J2EE,這個定義是否完全正確我覺得沒必要深究,只是一種個人習慣,而本章節(jié)所介紹的 J2EE 方式則是指 Tomcat 為 WS 所做的支持,這套代碼的包名前綴叫做:javax.websocket

          這套代碼中定義了一套適用于 WS 開發(fā)的注解和相關支持,我們可以利用它和 Tomcat 進行WS 開發(fā),由于現(xiàn)在更多的都是使用 SpringBoot 的內嵌容器了,所以這次我們就來按照 SpringBoot 內嵌容器的方式來演示。

          首先是引入
          SpringBoot - Web 的依賴,因為這個依賴中引入了內嵌式容器 Tomcat:
                  
          <dependency>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-starter-web</artifactId>
                  </dependency>

          接著就是將一個類定義為 WS 服務器,這一步也很簡單,只需要為這個類加上@ServerEndpoint注解就可以了,在這個注解中比較常用的有三個參數(shù):WS路徑、序列化處理類、反序列化處理類。

          @Retention(RetentionPolicy.RUNTIME)
          @Target({ElementType.TYPE})
          public @interface ServerEndpoint {
              String value();

              String[] subprotocols() default {};

              Class<? extends Decoder>[] decoders() default {};

              Class<? extends Encoder>[] encoders() default {};

              Class<? extends Configurator> configurator() default Configurator.class;
          }

          接下來我們來看具體的一個 WS 服務器類示例:

          @Component
          @ServerEndpoint("/j2ee-ws/{msg}")
          public class WebSocketServer {

              //建立連接成功調用
              @OnOpen
              public void onOpen(Session session, @PathParam(value = "msg") String msg){
                  System.out.println("WebSocketServer 收到連接: " + session.getId() + ", 當前消息:" + msg);
              }

              //收到客戶端信息
              @OnMessage
              public void onMessage(Session session, String message) throws IOException {
                  message = "WebSocketServer 收到連接:" + session.getId() +  ",已收到消息:" + message;
                  System.out.println(message);
                  session.getBasicRemote().sendText(message);
              }

              //連接關閉
              @OnClose
              public void onclose(Session session){
                  System.out.println("連接關閉");
              }

          }

          在以上代碼中,我們著重關心 WS 相關的注解,主要有以下四個:

          1. @ServerEndpoint :這里就像 RequestMapping 一樣,放入一個 WS 服務器監(jiān)聽的 URL。
          2. @OnOpen :這個注解修飾的方法會在 WS 連接開始時執(zhí)行。
          3. @OnClose :這個注解修飾的方法則會在 WS 關閉時執(zhí)行。
          4. @OnMessage :這個注解則是修飾消息接受的方法,并且由于消息有文本和二進制兩種方式,所以此方法參數(shù)上可以使用 String 或者二進制數(shù)組的方式,就像下面這樣:

           @OnMessage
           public void onMessage(Session session, String message) throws IOException {

           }

           @OnMessage
           public void onMessage(Session session, byte[] message) throws IOException {

           }

          除了以上這幾個以外,常用的功能方面還差一個分片消息、Ping 消息 和 Pong 消息,對于這三個功能我并沒有查到相關用法,只在源碼的接口列表中看到了一個 PongMessage 接口,有知道的讀者朋友們有知道的可以在評論區(qū)指出。

          細心的小伙伴們可能發(fā)現(xiàn)了,示例中的 WebSocketServer 類還有一個 
          @Component 注解,這是由于我們使用的是內嵌容器,而內嵌容器需要被 Spring 管理并初始化,所以需要給 WebSocketServer 類加上這么一個注解,所以代碼中還需要有這么一個配置:

          @Configuration
          public class WebSocketConfig {

           @Bean
           public ServerEndpointExporter serverEndpointExporter() {
               return new ServerEndpointExporter();
           }
          }

          Tips:在不使用內嵌容器的時候可以不做以上步驟。

          最后上個簡陋的 WS 效果示例圖,前端方面直接使用 HTML5 的 WebScoket 標準庫,具體可以查看我的倉庫代碼:



          Spring方式



          第二部分來說 Spring 方式,Spring 作為 Java 開發(fā)界的老大哥,幾乎封裝了一切可以封裝的,對于 WS 開發(fā)呢 Spring 也提供了一套相關支持,而且從使用方面我覺得要比 J2EE 的更易用。

          使用它的第一步我們先引入
          SpringBoot - WS 依賴,這個依賴包也會隱式依賴 SpringBoot - Web 包:

               <dependency>
                   <groupId>org.springframework.boot</groupId>
                   <artifactId>spring-boot-starter-websocket</artifactId>
               </dependency>

          第二步就是準備一個用來處理 WS 請求的 Handle了,Spring 為此提供了一個接口—— WebSocketHandler,我們可以通過實現(xiàn)此接口重寫其接口方法的方式自定義邏輯,我們來看一個例子:

          @Component
          public class SpringSocketHandle implements WebSocketHandler {

           @Override
           public void afterConnectionEstablished(WebSocketSession session) throws Exception {
               System.out.println("SpringSocketHandle, 收到新的連接: " + session.getId());
           }

           @Override
           public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
               String msg = "SpringSocketHandle, 連接:" + session.getId() +  ",已收到消息。";
               System.out.println(msg);
               session.sendMessage(new TextMessage(msg));
           }

           @Override
           public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
               System.out.println("WS 連接發(fā)生錯誤");
           }

           @Override
           public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
               System.out.println("WS 關閉連接");
           }

           // 支持分片消息
           @Override
           public boolean supportsPartialMessages() {
               return false;
           }
          }

          上面這個例子很好的展示了 WebSocketHandler 接口中的五個函數(shù),通過名字我們就應該知道它具有什么功能了:


          1. afterConnectionEstablished:連接成功后調用。
          2. handleMessage:處理發(fā)送來的消息。
          3. handleTransportError:WS 連接出錯時調用。
          4. afterConnectionClosed:連接關閉后調用。
          5. supportsPartialMessages:是否支持分片消息。

          以上這幾個方法重點可以來看一下 handleMessage 方法,handleMessage 方法中有一個 WebSocketMessage 參數(shù),這也是一個接口,我們一般不直接使用這個接口而是使用它的實現(xiàn)類,它有以下幾個實現(xiàn)類:

          1. BinaryMessage:二進制消息體
          2. TextMessage:文本消息體
          3. PingMessage:Ping 消息體
          4. PongMessage:Pong 消息體

          但是由于 handleMessage 這個方法參數(shù)是WebSocketMessage,所以我們實際使用中可能需要判斷一下當前來的消息具體是它的哪個子類,比如這樣:
              
             public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
                  if (message instanceof TextMessage) {
                      this.handleTextMessage(session, (TextMessage)message);
                  } else if (message instanceof BinaryMessage) {
                      this.handleBinaryMessage(session, (BinaryMessage)message);
                  }
              }

          但是總這樣寫也不是個事,為了避免這些重復性代碼,Spring 給我們定義了一個 AbstractWebSocketHandler,它已經(jīng)封裝了這些重復勞動,我們可以直接繼承這個類然后重寫我們想要處理的消息類型:
              
                      protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
              }

              protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
              }

              protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
              }

          上面這部分都是對于 Handle 的操作,有了 Handle 之后我們還需要將它綁定在某個 URL 上,或者說監(jiān)聽某個 URL,那么必不可少的需要以下配置:

          @Configuration
          @EnableWebSocket
          public class SpringSocketConfig implements WebSocketConfigurer {

              @Autowired
              private SpringSocketHandle springSocketHandle;

              @Override
              public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
                  registry.addHandler(springSocketHandle, "/spring-ws").setAllowedOrigins("*");
              }
          }

          這里我把我的自定義 Handle 注冊到 "/spring-ws" 上面并設置了一下跨域,在整個配置類上還要打上@EnableWebSocket 注解,用于開啟 WS 監(jiān)聽。

          Spring 的方式也就以上這些內容了,不知道大家是否感覺 Spring 所提供的 WS 封裝要比 J2EE 的更方便也更全面一些,起碼我只要看 WebSocketHandler 接口就能知道所有常用功能的用法,所以對于 WS 開發(fā)來說我是比較推薦 Spring 方式的。

          最后上個簡陋的 WS 效果示例圖,前端方面直接使用 HTML5 的 WebScoket 標準庫,具體可以查看我的倉庫代碼:




          SocoketlO方式



          SocketIO 方式和上面兩種有點不太一樣,因為 SocketIO 誕生初就是為了兼容性作為考量的,前端的讀者們應該對它更熟悉,因為它是一個 JS 庫,我們先來看一下維基百科對它的定義:

          Socket.IO 是一個面向實時 web 應用的 JavaScript 庫。它使得服務器和客戶端之間實時雙向的通信成為可能。他有兩個部分:在瀏覽器中運行的客戶端庫,和一個面向Node.js的服務端庫,兩者有著幾乎一樣的API。

          Socket.IO 主要使用WebSocket協(xié)議。但是如果需要的話,Socket.io可以回退到幾種其它方法,例如Adobe Flash Sockets,JSONP拉取,或是傳統(tǒng)的AJAX拉取,并且在同時提供完全相同的接口。

          所以我覺得使用它更多是因為兼容性,因為 HTML5 之后原生的 WS 應該也夠用了,然而它是一個前端庫,所以 Java 語言這塊并沒有官方支持,好在民間大神已經(jīng)以 Netty 為基礎開發(fā)了能與它對接的 Java 庫:netty-socketio

          不過我要先給大家提個醒,不再建議使用它了,不是因為它很久沒更新了,而是因為它支持的 Socket-Client 版本太老了,截止到 2022-04-29 日,SocketIO 已經(jīng)更新到 4.X 了,但是 NettySocketIO 還只支持 2.X 的 Socket-Client 版本。

          說了這么多,該教大家如何使用它了,第一步還是引入最新的依賴:
                  
                  <dependency>
                      <groupId>com.corundumstudio.socketio</groupId>
                      <artifactId>netty-socketio</artifactId>
                      <version>1.7.19</version>
                  </dependency>

          第二步就是配置一個 WS 服務:

          @Configuration
          public class SocketIoConfig {

              @Bean
              public SocketIOServer socketIOServer() {
                  com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration();

                  config.setHostname("127.0.0.1");
                  config.setPort(8001);
                  config.setContext("/socketio-ws");
                  SocketIOServer server = new SocketIOServer(config);
                  server.start();
                  return server;
              }

              @Bean
              public SpringAnnotationScanner springAnnotationScanner() {
                  return new SpringAnnotationScanner(socketIOServer());
              }
          }

          大家在上文的配置中,可以看到設置了一些 Web 服務器參數(shù),比如:端口號和監(jiān)聽的 path,并將這個服務啟動起來,服務啟動之后日志上會打印這樣一句日志:

          [ntLoopGroup-2-1] c.c.socketio.SocketIOServer : SocketIO server started at port: 8001

          這就代表啟動成功了,接下來就是要對 WS 消息做一些處理了:

          @Component
          public class SocketIoHandle {

              /**
               * 客戶端連上socket服務器時執(zhí)行此事件
               * @param client
               */
              @OnConnect
              public void onConnect(SocketIOClient client) {
                  System.out.println("SocketIoHandle 收到連接:" + client.getSessionId());
              }

              /**
               * 客戶端斷開socket服務器時執(zhí)行此事件
               * @param client
               */
              @OnDisconnect
              public void onDisconnect(SocketIOClient client) {
                  System.out.println("當前鏈接關閉:" + client.getSessionId());
              }

              @OnEvent( value = "onMsg")
              public void onMessage(SocketIOClient client, AckRequest request, Object data) {
                  System.out.println("SocketIoHandle 收到消息:" + data);
                  request.isAckRequested();
                  client.sendEvent("chatMsg""我是 NettySocketIO 后端服務,已收到連接:" + client.getSessionId());
              }
          }

          我相信對于以上代碼,前兩個方法是很好懂的,但是對于第三個方法如果大家沒有接觸過 SocketIO 就比較難理解了,為什么@OnEvent( value = "onMsg")里面這個值是自定義的,這就涉及到 SocketIO 里面發(fā)消息的機制了,通過 SocketIO 發(fā)消息是要發(fā)給某個事件的,所以這里的第三個方法就是監(jiān)聽 發(fā)給onMsg事件的所有消息,監(jiān)聽到之后我又給客戶端發(fā)了一條消息,這次發(fā)給的事件是:chatMsg,客戶端也需要監(jiān)聽此事件才能接收到這條消息。

          最后再上一個簡陋的效果圖:


          由于前端代碼不再是標準的 HTML5 的連接方式,所以我這里簡要貼一下相關代碼,具體更多內容可以看我的代碼倉庫:
              
            function changeSocketStatus() {
                  let element = document.getElementById("socketStatus");
                  if (socketStatus) {
                      element.textContent = "關閉WebSocket";
                      const socketUrl="ws://127.0.0.1:8001";
                      socket = io.connect(socketUrl, {
                          transports: ['websocket'],
                          path: "/socketio-ws"
                      });
                      //打開事件
                      socket.on('connect', () => {
                          console.log("websocket已打開");
                      });
                      //獲得消息事件
                      socket.on('chatMsg', (msg) => {
                          const serverMsg = "收到服務端信息:" + msg;
                          pushContent(serverMsg, 2);
                      });
                      //關閉事件
                      socket.on('disconnect', () => {
                          console.log("websocket已關閉");
                      });
                      //發(fā)生了錯誤事件
                      socket.on('connect_error', () => {
                          console.log("websocket發(fā)生了錯誤");
                      })
                  }
              }


          第四種方式?



          第四種方式其實就是 Netty 了,Netty 作為 Java 界大名鼎鼎的開發(fā)組件,對于常見協(xié)議也全部進行了封裝,所以我們可以直接在 Netty 中去很方便的使用 WebSocket,接下來我們可以看看 Netty 怎么作為 WS 的服務器進行開發(fā)。

          注意:以下內容如果沒有 Netty 基礎可能一臉蒙的進,一臉蒙的出,不過還是建議大家看看,Netty 其實很簡單。

          第一步需要先引入一個 Netty 開發(fā)包,我這里為了方便一般都是 All In:

                  <dependency>
                      <groupId>io.netty</groupId>
                      <artifactId>netty-all</artifactId>
                      <version>4.1.75.Final</version>
                  </dependency>

          第二步的話就需要啟動一個 Netty 容器了,配置很多,但是比較關鍵的也就那幾個:

          public class WebSocketNettServer {
              public static void main(String[] args) {

                  NioEventLoopGroup boss = new NioEventLoopGroup(1);
                  NioEventLoopGroup work = new NioEventLoopGroup();

                  try {
                      ServerBootstrap bootstrap = new ServerBootstrap();
                      bootstrap
                              .group(boss, work)
                              .channel(NioServerSocketChannel.class)
                              //設置保持活動連接狀態(tài)
                              .childOption(ChannelOption.SO_KEEPALIVE, true)
                              .localAddress(8080)
                              .handler(new LoggingHandler(LogLevel.DEBUG))
                              .childHandler(new ChannelInitializer<SocketChannel>() {
                                  @Override
                                  protected void initChannel(SocketChannel ch) throws Exception {
                                      ch.pipeline()
                                              // HTTP 請求解碼和響應編碼
                                              .addLast(new HttpServerCodec())
                                              // HTTP 壓縮支持
                                              .addLast(new HttpContentCompressor())
                                              // HTTP 對象聚合完整對象
                                              .addLast(new HttpObjectAggregator(65536))
                                              // WebSocket支持
                                              .addLast(new WebSocketServerProtocolHandler("/ws"))
                                              .addLast(WsTextInBoundHandle.INSTANCE);
                                  }
                              });

                      //綁定端口號,啟動服務端
                      ChannelFuture channelFuture = bootstrap.bind().sync();
                      System.out.println("WebSocketNettServer啟動成功");

                      //對關閉通道進行監(jiān)聽
                      channelFuture.channel().closeFuture().sync();

                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  } finally {
                      boss.shutdownGracefully().syncUninterruptibly();
                      work.shutdownGracefully().syncUninterruptibly();
                  }

              }
          }

          以上代碼我們主要關心端口號和重寫的 ChannelInitializer 就行了,里面我們定義了五個過濾器(Netty 使用責任鏈模式),前面三個都是 HTTP 請求的常用過濾器(畢竟 WS 握手是使用 HTTP 頭的所以也要配置 HTTP 支持),第四個則是 WS 的支持,它會攔截 /ws 路徑,最關鍵的就是第五個了過濾器它是我們具體的業(yè)務邏輯處理類,效果基本和 Spring 那部門中的 Handle 差不多,我們來看看代碼:

          @ChannelHandler.Sharable
          public class WsTextInBoundHandle extends SimpleChannelInboundHandler<TextWebSocketFrame> {

              private WsTextInBoundHandle() {
                  super();
                  System.out.println("初始化 WsTextInBoundHandle");
              }

              @Override
              public void channelActive(ChannelHandlerContext ctx) throws Exception {
                  System.out.println("WsTextInBoundHandle 收到了連接");
              }

              @Override
              protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {

                  String str = "WsTextInBoundHandle 收到了一條消息, 內容為:" + msg.text();

                  System.out.println(str);

                  System.out.println("-----------WsTextInBoundHandle 處理業(yè)務邏輯-----------");

                  String responseStr = "{\"status\":200, \"content\":\"收到\"}";

                  ctx.channel().writeAndFlush(new TextWebSocketFrame(responseStr));
                  System.out.println("-----------WsTextInBoundHandle 數(shù)據(jù)回復完畢-----------");
              }

              @Override
              public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {

                  System.out.println("WsTextInBoundHandle 消息收到完畢");
                  ctx.flush();
              }

              @Override
              public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                  System.out.println("WsTextInBoundHandle 連接邏輯中發(fā)生了異常");
                  cause.printStackTrace();
                  ctx.close();
              }
          }

          這里面的方法我都不說了,看名字就差不多知道了,主要是看一下這個類的泛型:TextWebSocketFrame,很明顯這是一個 WS 文本消息的類,我們順著它的定義去看發(fā)現(xiàn)它繼承了 WebSocketFrame,接著我們去看它的子類:



          一圖勝千言,我想不用多說大家也都知道具體的類是處理什么消息了把,在上文的示例中我們是一定了一個文本 WS 消息的處理類,如果你想處理其他數(shù)據(jù)類型的消息,可以將泛型中的 TextWebSocketFrame 換成其他 WebSocketFrame 類就可以了。

          至于為什么沒有連接成功后的處理,這個是和 Netty 的相關機制有關,可以在 channelActive 方法中處理,大家有興趣的可以了解一下 Netty。

          最后上個簡陋的 WS 效果示例圖,前端方面直接使用 HTML5 的 WebScoket 標準庫,具體可以查看我的倉庫代碼:



          總結



          洋洋灑灑五千字,有了收獲別忘贊。

          在上文中,我總共介紹了四種在 Java 中使用 WS 的方式,從我個人使用意向來說我感覺應該是這樣的:
          Spring 方式 > Netty 方式 > J2EE 方式 > SocketIO 方式,當然了,如果你的業(yè)務存在瀏覽器兼容性問題,其實只有一種選擇:SocketIO。

          最后,我估計某些讀者會去具體拉代碼看代碼,所以我簡單說一下代碼結構:

          ├─java
          │  └─com
          │      └─example
          │          └─springwebsocket
          │              │  SpringWebsocketApplication.java
          │              │  TestController.java
          │              │
          │              ├─j2ee
          │              │      WebSocketConfig.java
          │              │      WebSocketServer.java
          │              │
          │              ├─socketio
          │              │      SocketIoConfig.java
          │              │      SocketIoHandle.java
          │              │
          │              └─spring
          │                      SpringSocketConfig.java
          │                      SpringSocketHandle.java

          └─resources
              └─templates
                      J2eeIndex.html
                      SocketIoIndex.html
                      SpringIndex.html

          代碼結構如上所示,應用代碼分成了三個文件夾,分別放著三種方式的具體示例代碼,在資源文件夾下的 templates 文件夾也有三個 HTML 文件,就是對應三種示例的 HTML 頁面,里面的鏈接地址和端口我都預設好了,拉下來直接單獨編譯此模塊運行即可。

          我沒有往里面放 Netty 的代碼,是因為感覺 Netty 部分內容很少,文章示例中的代碼直接復制就能用,后面如果寫 Netty 的話會再開一個 Netty 模塊用來放 Netty 相關的代碼。

          好了,今天的內容就到這了,希望對大家有幫助的話可以幫我文章點點贊,GitHub 也點點贊,大家的點贊與評論都是我更新的不懈動力,下期見。



          點擊左下角閱讀原文,到 SegmentFault 思否社區(qū) 和文章作者展開更多互動和交流,掃描下方”二維碼“或在“公眾號后臺回復“ 入群 ”即可加入我們的技術交流群,收獲更多的技術文章~

          - END -


          瀏覽 85
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产精品色色哟 | 高清无码在线免费 | 男人天堂色天堂 | 欧美一级毛片久久99精品蜜桃 | 黄色视频网站在线观看 |