Spring4.3+Webscket 實現聊天、消息推送詳解之具體實現(三)

前面可能廢話太多了,下面進入正題,講解spring具體的實現方式。javascript

先來講一下幾個核心的步驟吧: 1.構建websocket服務端業務處理程序 2.構建websocket服務端自身配置程序 3.註冊websocket服務端、配置、攔截過濾器等 3.客戶端發送握手請求並創建鏈接``` 這裏輸入代碼html

4.客戶端發送消息給服務端
5.服務端處理客戶端發送來的消息
6.服務端發送消息給客戶端(指定或者羣發)
7.客戶端展示。


所需jar包如圖:
https://static.oschina.net/uploads/img/201609/12105945_t83F.png 

第一步:構建websocket
spring實現方式中,有一個核心的接口WebSocketHandler,此接口中定義了websokcet服務端核心的幾個方法,如用戶成功鏈接服務端方法afterConnectionEstablished、處理客戶端發送消息的方法handleTextMessage等等。查看源碼發現其有一個重要的實現類:.AbstractWebSocketHandler該類是一個抽象類,其有兩個實現類分別爲:BinaryWebSocketHandler, TextWebSocketHandler。BinaryWebSocketHandler此類中定義了特殊的方法handleBinaryMessage(WebSocketSession session, BinaryMessage message),看其源碼中,BinaryMessage 構造函數裏面,能夠傳入一個字節形的數組,經過集成該類可用次方法用於附件上傳等功能。

通常信息傳遞,可經過繼承TextWebSocketHandler類來實現。下面先貼出代碼:


websockethandler處理程序。

package cn.com.mt.websocket;前端

import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Map;java

import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler;jquery

import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject;web

import cn.com.mt.model.Common_Constant; import cn.com.mt.model.UserInfo; /**redis

  • 該類爲websocket服務端處理程序,你能夠把他廣義上理解爲這就是websocket的服務端處理程序
  • @author 寒冰
  • QQ羣號: 67746867

*/ public class ContextWebSocketHandler extends TextWebSocketHandler{spring

/*
 * 用於存放在線用戶集合,key用用戶的id,真實生產環境中,未避免大數據出現的性能、內存、速度等一系列問題,採起內存數據庫如redis存儲
 */
private static final Map<String,WebSocketSession> users;
static {
	users = new HashMap<String,WebSocketSession>();
}


/**
 * 當客戶端有消息發送過來時,會進入此方法進行處理
 * session:消息來源者
 * message:封裝了發送過來的消息信息
 * 說明:再此處說明一下,消息格式最好以下:{to:消息接收者ID/Username惟一標識/服務端標識...,content:消息內容},方便業務操做
 */
@Override
protected void handleTextMessage(WebSocketSession session,
		TextMessage message) throws Exception {
	super.handleTextMessage(session, message);
	//若是想取得原始session中封存的用戶身份等信息,可經過:session.getAttributes().get(名稱)方法取得 
	UserInfo user = (UserInfo)session.getAttributes().get(Common_Constant.USER_INFO);
	
	//經過message.getPayload()方法獲取客戶端發送過來的有效信息
	String content = message.getPayload();
	JSONObject obj = JSON.parseObject(content);
	//取得標識令牌
	String token = obj.getString("to");
	if("server".equals(token)){//發送過來的信息爲服務器接收的系統信息,則處理相關係統級業務邏輯,此處只作原樣信息返回
		TextMessage returnMessage = new TextMessage("{'token':'socket_info','fromUser':'server','content':'信息已經收到,內容爲:"+obj.getString("content")+"'}");
		session.sendMessage(returnMessage);
	}else if("all".equals(token)){//發送過來的信息爲廣播信息,則調用所有在線人員接收信息方法
		TextMessage returnMessage = new TextMessage("{'token':'broadcast','fromUser':'"+user.getUsername()+"','content':'"+obj.getString("content")+"'}");
		sendMessageToUsers(returnMessage);
	}else{//發給我的接收
		TextMessage returnMessage = new TextMessage("{'token':'info','fromUser':'"+user.getUsername()+"','toUser':'"+obj.getString("toUser")+"','content':'"+obj.getString("content")+"'}");
		sendMessageToUser(obj.getString("to"),returnMessage);
	}
	
}

/**
 * 用戶成功鏈接成功後會調用該方法
 * 說明:次方法中,咱們在實際生產環境中可能用到的場景如:
 * 1.用戶上線以後,接收自身的離線消息。
 * 2.刷新全局在線用戶列表
 */
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
		
		UserInfo user = (UserInfo)session.getAttributes().get(Common_Constant.USER_INFO);
		if(null != user){
			users.put(user.getUsername(), session);//放入在線用戶集合中
		}
		//發送給當前登陸用戶在線用戶列表信息
		sendMessageToUsers(new TextMessage("{'token':'refreshlines','list':"+getlineUsers()+"}"));
	
    }


/**
 * 給某個用戶發送消息
 *
 * @param userName
 * @param message
 */
public void sendMessageToUser(String username, TextMessage message) {
	
	WebSocketSession user = users.get(username);
	/*
	 * 斷定接收信息的一方是否存在而且在線狀態,若是在線就發送
	 * 說明:在這個地方,真實的應用裏面,處理邏輯應該是先在當前在線用戶列表裏面,先取指定的用戶是否在線,若是不在線,則再去數據庫中查找該用戶是否存在,若是存在,則該消息
	 * 將加入離線消息存儲邏輯處理,待該用戶上線時,服務器推送離線消息給該用戶。
	 */
	if(null != user && user.isOpen()){
		 try {
			user.sendMessage(message);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
    
}


/**
 * 給全部在線用戶發送消息
 *
 * @param message
 */
public void sendMessageToUsers(TextMessage message) {
	
	for (Map.Entry<String,WebSocketSession> entry : users.entrySet()) {  
		  
		 try {
             if (entry.getValue().isOpen()) {
            	 entry.getValue().sendMessage(message);
             }
         } catch (IOException e) {
             e.printStackTrace();
         }
	  
	}  
    
}

/**
 * 返回在線用戶列表,json字符串格式
 * @return
 */
public String getlineUsers(){
	return JSON.toJSONString(users.keySet());
}


@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
    if(session.isOpen()){
        session.close();
    }
   System.out.println("傳輸處理錯誤......");
    users.remove(session);
}

@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
	System.out.println("websocket connection closed......");
    users.remove(session);
}

@Override
public boolean supportsPartialMessages() {
    return false;
}

}數據庫

握手攔截器經過繼承HttpSessionHandshakeInterceptor實現,以下代碼:

package cn.com.mt.websocket;json

import java.util.Map;

import javax.servlet.http.HttpSession;

import org.springframework.http.server.ServerHttpRequest;   import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.web.socket.WebSocketHandler;   import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

import cn.com.mt.model.Common_Constant; import cn.com.mt.model.UserInfo;   /**  * HandShakeInterceptor是websocket握手攔截器,用於攔截websocket初始化鏈接的請求   * @author 寒冰  * QQ羣號: 67746867  *  */ public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor{  

    /**      * 握手以前調用該方法      */     @Override     public boolean beforeHandshake(ServerHttpRequest request,             ServerHttpResponse response, WebSocketHandler wsHandler,             Map<String, Object> attributes) throws Exception {         System.out.println("握手開始");         if (request instanceof ServletServerHttpRequest) {             ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;             HttpSession session = servletRequest.getServletRequest().getSession(false);             if (session != null) {                 //握手之初,封裝一下當前用戶的信息,爲的是在後面的消息處理邏輯中,取出session中封裝的用戶信息                 UserInfo user = (UserInfo) session.getAttribute(Common_Constant.USER_INFO);                 if (user==null) {                      attributes.put(Common_Constant.USER_INFO,null);                 }else{                     attributes.put(Common_Constant.USER_INFO,user);                 }                                               }         }         return super.beforeHandshake(request, response, wsHandler, attributes);     }

    @Override     public void afterHandshake(ServerHttpRequest request,             ServerHttpResponse response, WebSocketHandler wsHandler,             Exception ex) {         System.out.println("握手完成");         super.afterHandshake(request, response, wsHandler, ex);     }

   }  

經過xml註冊websocket相關信息以下:
spring-websocket.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"     xmlns:websocket="http://www.springframework.org/schema/websocket"     xmlns:context="http://www.springframework.org/schema/context"      xmlns:mvc="http://www.springframework.org/schema/mvc"     xsi:schemaLocation="         http://www.springframework.org/schema/beans         http://www.springframework.org/schema/beans/spring-beans.xsd         http://www.springframework.org/schema/websocket         http://www.springframework.org/schema/websocket/spring-websocket.xsd         http://www.springframework.org/schema/context         http://www.springframework.org/schema/context/spring-context.xsd         http://www.springframework.org/schema/mvc          http://www.springframework.org/schema/mvc/spring-mvc.xsd">              <!-- 聲明服務端處理程序 -->     <bean id="contextwebsocketHandler" class="cn.com.mt.websocket.ContextWebSocketHandler"/>            <!-- 註冊服務端處理程序路徑及相關握手攔截器 -->      websocket:handlers         <websocket:mapping path="/chandler" handler="contextwebsocketHandler"/>        websocket:handshake-interceptors            <bean class="cn.com.mt.websocket.HandshakeInterceptor"></bean>        </websocket:handshake-interceptors>     </websocket:handlers>          <!-- 註冊服務端處理程序路徑及相關握手攔截器  開啓sockjs方式,開啓以後,前端必須用sockjs方式發起請求鏈接-->     websocket:handlers         <websocket:mapping path="/jhandler" handler="contextwebsocketHandler"/>        websocket:handshake-interceptors            <bean class="cn.com.mt.websocket.HandshakeInterceptor"></bean>        </websocket:handshake-interceptors>        websocket:sockjs/     </websocket:handlers>

    <!-- 配置websocket消息的最大緩衝區長度 -->     <bean class="org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean">         <property name="maxTextMessageBufferSize" value="8192"/>         <property name="maxBinaryMessageBufferSize" value="8192"/>     </bean>   

    </beans>

上述的代碼中,用到了兩個本身寫的類,一個是實體類UserInfo,一個是用於定義常量的類,以下:
UserInfo:

package cn.com.mt.model; /**  * 用戶信息實體  *@author 寒冰  *QQ羣號: 67746867  *  */ public class UserInfo {

    private int id;          private String username;     

    public int getId() {         return id;     }

    public void setId(int id) {         this.id = id;     }

    public String getUsername() {         return username;     }

    public void setUsername(String username) {         this.username = username;     }

          }

Common_Constant:

package cn.com.mt.model; /**  * 系通通一常量定義實體  * @author 寒冰  * QQ羣號: 67746867  *  */ public class Common_Constant {

    public static final String USER_INFO = "USER_INFO";//用戶身份信息           }

同時爲了記錄登錄用戶信息,寫了一個action,LoginAction,代碼以下:

package cn.com.mt.action;

import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping;

import cn.com.mt.model.Common_Constant; import cn.com.mt.model.UserInfo;

/**  * 登錄action  *@author 寒冰  *QQ羣號: 67746867  *  */ @Controller public class LoginAction {

    /**      * 登錄驗證      * 說明:此處僅爲闡明websocket在線用戶業務,不作用戶身份信息認證聲明。      * 特別說明:如作集羣部署,如需解決session同步問題,需作session共享。      * @param userinfo      * @param request      * @return      */     @RequestMapping("/login.action")     public String login(UserInfo userinfo,String index,HttpServletRequest request){         request.getSession().setAttribute(Common_Constant.USER_INFO, userinfo);                  return "0".equals(index)?"index0":"index1";     } }

爲了可以websocketSession與httpsession能掛上關聯,需額外添加請求攔截器RequestListener,讓每一次的請求都帶着session信息,以下實現:

package cn.com.mt.listener;

import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; import javax.servlet.http.HttpServletRequest; /**  * 用戶請求/響應監聽器  *   * 此監聽器的主要做用是讓用戶每次向websocket發出的請求,都帶上session信息  * @author 寒冰  * QQ羣號: 67746867  *  */ public class RequestListener implements ServletRequestListener {

    @Override     public void requestInitialized(ServletRequestEvent request) {         // TODO Auto-generated method stub          ((HttpServletRequest) request.getServletRequest()).getSession();     }          @Override     public void requestDestroyed(ServletRequestEvent arg0) {         // TODO Auto-generated method stub              }

}

最後貼出web.xml 配置文件:

<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">               <display-name>yao_yan</display-name>   <servlet>         <servlet-name>spring</servlet-name>         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>         <init-param>             <param-name>contextConfigLocation</param-name>             <param-value>classpath*:spring-*.xml</param-value>         </init-param>         <load-on-startup>1</load-on-startup>         <async-supported>true</async-supported>     </servlet>

    <!-- websocket URL標識 -->     <servlet-mapping>         <servlet-name>spring</servlet-name>         <url-pattern>/socket/</url-pattern>     </servlet-mapping>          <!-- action URL標識 -->     <servlet-mapping>         <servlet-name>spring</servlet-name>         <url-pattern>.action</url-pattern>     </servlet-mapping>                    <!-- 註冊請求監聽器 -->     <listener>          <listener-class>cn.com.mt.listener.RequestListener</listener-class>     </listener> </web-app>

前端登錄login.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"     pageEncoding="UTF-8"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> <script type="text/javascript" src="js/jquery-1.10.2.min.js"></script> <script>  $(function(){            $("#login").click(function(){          var username = $.trim($("#username").val());          if(""==username){              alert("enter username please.....");              return ;          }else{              f1.submit();          }      });  }); </script> </head> <body> <form action="login.action" method="post" name="f1"> username:<input type="text" name="username" id="username"> <input type="radio" name="index" value="0" checked="checked">普通方式 <input type="radio" name="index" value="1">sockjs方式 <input type="button" id="login" value="login"> </form>

</body> </html> ```

普通方式jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>

<script>

    var socket_url = "ws://192.168.1.107:8888/yao_yan/socket/chandler";
    
    var  websocket ;

   $(function(){
       
       //鏈接websocket服務器
       $("#connect").click(function(){
           
           websocket = new WebSocket(socket_url); 
           websocket.onopen = function (evt) { onOpen(evt) }; 
           websocket.onclose = function (evt) { onClose(evt) }; 
           websocket.onmessage = function (evt) { onMessage(evt) }; 
           websocket.onerror = function (evt) { onError(evt) }; 
           
       });
       
       //註冊發送消息按鈕事件
       $("#send").click(function(){
           var who = $("input[name='who']:checked").val();//獲取消息接收方類別
           var content = $.trim($("#content").val());//獲取消息內容
           var msg = "{";
           
           if(""==content){
               alert("輸入消息內容!");
               return;
           }
           if("server"==who){
               msg+="'to':'server',";
               $("#msg").after("<p>你對服務器說:"+content+"</p>");
           }else if("all"==who){
               
               msg+="'to':'all',";
               $("#msg").after("<p>你對你們說:"+content+"</p>");
           }else if("one"==who){
               
               var to_user = $.trim($("#userlist").val());
               if(""==to_user){
                  alert("選擇接收消息方!");
                  return ;
               }
               msg+="'to':'"+to_user+"',";
               
               $("#msg").after("<p>你對"+to_user+"說:"+content+"</p>");
           }
           msg += "'content':'"+content+"'}";
           
           sendd(msg);
           
       });
       
       
      
   });
   
    //鏈接成功websocket服務器時執行
       function onOpen(evt) { 
           $("#msg").text("<p>成功鏈接到websocket服務器</p>");
           $("#send").removeAttr("disabled");//發送信息按鈕可用
           $("#connect").attr("disabled","disabled");//鏈接服務器按鈕不可用
    } 
    
    //鏈接關閉時執行
    function onClose(evt) { 
       console.log("鏈接關閉。。。。"); 
    } 
    
    //服務器有推送消息過來時執行
    function onMessage(evt) {
        
       var obj = eval('(' + evt.data + ')');
       if(obj.token=="refreshlines"){//斷定token爲刷新在線用戶列表
           refresh_line_users(obj.list);
       }else if(obj.token=="broadcast"){//廣播消息
           $("#msg").after("<p>"+obj.fromUser+" 對你們說:"+obj.content+"</p>");
       }else if(obj.token=="info"){//私信
           $("#msg").after("<p>"+obj.fromUser+" 對你說:"+obj.content+"</p>");
       }else if(obj.token=="socket_info"){//系統提醒消息
           $("#msg").after("<p>"+obj.fromUser+" 響應消息:"+obj.content+"</p>");
       }
    } 
    
    //有錯誤信息時執行
    function onError(evt) { 
       console.log('Error occured: ' + evt.data); 
    }
    
    //向服務器發送消息,此方式實際生產中考慮消息加密傳遞
    function sendd(msg){
        websocket.send(msg);
        
    }
    
    //刷新在線用戶列表
    function refresh_line_users(data){
        
        $("#userlist").empty();
        for(var i=0;i<data.length;i++){
            $("#userlist").append("<option value='"+$.trim(data[i])+"'>"+$.trim(data[i])+"</option>");
        }
        $("#userlist").append("<option value='all'>所有</option>");
        
    }
</script>
</head>
<body>
<h1>普通請求方式</h1>

        響應提示區域:<div id="msg" style="color:red"></div>
        <hr>
        
        
        <br>
        消息內容:
        <input type="text" id="content" name="content">
        <br><br>
         在線列表:
         <select id="userlist">
         </select>
        <br><br>
        我要發消息給:<input type="radio" id="server" name="who" value="server" checked="checked">服務器識別消息
        <input type="radio" id="one" name="who" value="one">我的
        <input type="radio" id="all" name="who" value="all">廣播
        <br><br>
        <input type="button" id="connect" value="鏈接websocket服務器">
        <input type="button" id="send" value="發送" disabled="disabled">
        
</body>
</html>

sockjs方式:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
<script src="js/sockjs-1.1.1.min.js"></script>  
<script>

    var socket_url = "http://192.168.1.107:8888/yao_yan/socket/jhandler";
    
    var  sock ;

   $(function(){
       
       //鏈接websocket服務器
       $("#connect").click(function(){
           
           sock = new SockJS(socket_url);
           sock.onopen = function (evt) { onOpen(evt) }; 
           sock.onclose = function (evt) { onClose(evt) }; 
           sock.onmessage = function (evt) { onMessage(evt) }; 
           sock.onerror = function (evt) { onError(evt) }; 
           
       });
       
       //註冊發送消息按鈕事件
       $("#send").click(function(){
           var who = $("input[name='who']:checked").val();//獲取消息接收方類別
           var content = $.trim($("#content").val());//獲取消息內容
           var msg = "{";
           
           if(""==content){
               alert("輸入消息內容!");
               return;
           }
           if("server"==who){
               msg+="'to':'server',";
               $("#msg").after("<p>你對服務器說:"+content+"</p>");
           }else if("all"==who){
               
               msg+="'to':'all',";
               $("#msg").after("<p>你對你們說:"+content+"</p>");
           }else if("one"==who){
               
               var to_user = $.trim($("#userlist").val());
               if(""==to_user){
                  alert("選擇接收消息方!");
                  return ;
               }
               msg+="'to':'"+to_user+"',";
               
               $("#msg").after("<p>你對"+to_user+"說:"+content+"</p>");
           }
           msg += "'content':'"+content+"'}";
           
           sendd(msg);
           
       });
       
       
      
   });
   
    //鏈接成功websocket服務器時執行
       function onOpen(evt) { 
           $("#msg").after("<font color='red'>系統提示:</font><p>你已經成功鏈接到websocket服務器!!!如今能夠聊天了</p>");
           $("#send").removeAttr("disabled");//發送信息按鈕可用
           $("#connect").attr("disabled","disabled");//鏈接服務器按鈕不可用
    } 
    
    //鏈接關閉時執行
    function onClose(evt) { 
       console.log("鏈接關閉。。。。"); 
    } 
    
    //服務器有推送消息過來時執行
    function onMessage(evt) {
        
       var obj = eval('(' + evt.data + ')');
       if(obj.token=="refreshlines"){//斷定token爲刷新在線用戶列表
           refresh_line_users(obj.list);
       }else if(obj.token=="broadcast"){//廣播消息
           $("#msg").after("<p>"+obj.fromUser+" 對你們說:"+obj.content+"</p>");
       }else if(obj.token=="info"){//私信
           $("#msg").after("<p>"+obj.fromUser+" 對你說:"+obj.content+"</p>");
       }else if(obj.token=="socket_info"){//系統提醒消息
           $("#msg").after("<p>"+obj.fromUser+" 響應消息:"+obj.content+"</p>");
       }
    } 
    
    //有錯誤信息時執行
    function onError(evt) { 
       console.log('Error occured: ' + evt.data); 
    }
    
    //向服務器發送消息,此方式實際生產中考慮消息加密傳遞
    function sendd(msg){
        sock.send(msg);
        
    }
    
    //刷新在線用戶列表
    function refresh_line_users(data){
        
        $("#userlist").empty();
        for(var i=0;i<data.length;i++){
            $("#userlist").append("<option value='"+$.trim(data[i])+"'>"+$.trim(data[i])+"</option>");
        }
        $("#userlist").append("<option value='all'>所有</option>");
        
    }
</script>
</head>
<body>
<h1>sockjs請求方式</h1>
        響應提示區域:<div id="msg" style="color:red"></div>
        <hr>
        
        
        <br>
        消息內容:
        <input type="text" id="content" name="content">
        <br><br>
         在線列表:
         <select id="userlist">
         </select>
        <br><br>
        我要發消息給:<input type="radio" id="server" name="who" value="server" checked="checked">服務器識別消息
        <input type="radio" id="one" name="who" value="one">我的
        <input type="radio" id="all" name="who" value="all">廣播
        <br><br>
        <input type="button" id="connect" value="鏈接websocket服務器">
        <input type="button" id="send" value="發送" disabled="disabled">
        
</body>
</html>

下面的章節,會詳細介紹上述代碼。

相關文章
相關標籤/搜索