websocket練習

一.介紹

  • 服務器端和瀏覽器端建立通道,在http協議基礎上實現的.
  • 之后的通訊都是通過這個通道進行的,無需在發起請求和響應(這一點與http協議是有區別的,http協議是請求響應模型,每一次與服務器的通訊都是一次和響應)
  • 使用最新的websocket JSR356開發規范, tomcat7是支持該規范的,在tomcat7中的http://localhost:8081/examples/websocket/ 有demo展示
    socket-1.jpg

在tomcat7的安裝目錄usr/local/tomcat/lib下會存在websocket的jar包

websocket-api.jar
tomcat7-websocket.jar

其中tomcat7-websocket.jar是對websocket-api.jar接口的一個實現
注意:tomcat版本必須是7以上

二.webSockAPI (ServerApplicationConfig)

  • 項目啟動時會自動啟動,類似與ContextListener.
    是webSocket的核心配置類。

三.ServerApplicationConfig 有兩個方法

getEndPointConfigs 獲取所有以接口方式配置的webSocket類。
getAnnotatedEndpointClasses 掃描src下所有類@ServerEndPoint注解的類。
提示: EndPoint 就指的是 一個webSocket的一個服務端程序

四.實現一個webSocket應用程序,我們要學會幾個基本操作

  1. 開啟連接
  2. 客戶端給服務器端發送數據
  3. 服務器端接收數據
  4. 服務器端給客戶端發送數據
  5. 客戶端接收數據
  6. @ServerEndPoint("/hello")
    在webSocket的服務程序類上面加上 注解。
    表示的連接路徑是: ws://localhost:8080/helloworld/hello
  7. 監聽三類基本事件: onopen, onmessage,onclose
    onmessage 是發送數據時的響應事件。(響應客戶端ws.send()方法發送的數據)
    onopen是打開連接時的響應事件。
    onclose是關閉連接時的響應事件。(客戶端關閉連接時觸發)

五.編寫demo

兩個webSocket服務:簡單輸出和聊天室
簡單輸出訪問頁面:http://localhost:8081/webSocket1/index.jsp
聊天室訪問頁面:http://localhost:8081/webSocket1/login.jsp
以下具體描述聊天室功能,簡單輸出功能請查看我的代碼庫

  • 項目加載三個包
    基礎接口包:websocket-api.jar
    tomcat7實現基礎包:tomcat7-websocket.jar
    谷歌公司的json和java對象轉換包:gson-2.2.4.jar

  • 目錄結構如下:


    webSocket.jpg
  • SocketConfig.java內容

該文件可以參考tomcat的
/usr/local/tomcat/webapps/examples/WEB-INF/classes/websocket/ExamplesConfig.java

package com.lxf.config;
/**
 * ServerApplicationConfig接口的實現類
 * 在web容器啟動的加載并執行,
 */

import java.util.Set;

import javax.websocket.Endpoint;
import javax.websocket.server.ServerApplicationConfig;
import javax.websocket.server.ServerEndpoint;
import javax.websocket.server.ServerEndpointConfig;
//ServerApplicationConfig接口的實現類會在web容器啟動的時候自動執行
public class SocketConfig  implements ServerApplicationConfig{
    /**
     * @param scaned 代表是由web服務器啟動的時候,掃碼本項目中帶有@ServerEndpoint注解的類,
     *  將這些類實例放到該接合中,并傳入到本方法中(也稱依賴注入,被動式編程)
     */
    @Override
    public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> scaned) {
        // TODO Auto-generated method stub
        System.out.println("websocket config---------------掃描到有websocket的endPoint服務個數:"+scaned.size());
        //在這里我們可以做一些過濾操作和一些自己的業務邏輯
        
        //最后給服務器返回,服務器端就會注冊這些socket server,工前臺訪問
        return scaned;
    }

    @Override
    public Set<ServerEndpointConfig> getEndpointConfigs(Set<Class<? extends Endpoint>> arg0) {
        // TODO Auto-generated method stub
        return null;
    }

}
  • Message.java內容
package com.lxf.dataVo;
/**
 * 用來接收用戶頁面發來的數據的結構
 */
import java.util.Date;
import java.util.List;

public class Message {

    private  String  alert;   //
    //用戶名
    private  List<String>  names;
    //發送的信息
    private  String  sendMsg;
    //信息來源
    private String  from;
    //時間
    private String  date;
    
    
    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }

    public String getSendMsg() {
        return sendMsg;
    }

    public void setSendMsg(String sendMsg) {
        this.sendMsg = sendMsg;
    }

    public String getFrom() {
        return from;
    }

    public void setFrom(String from) {
        this.from = from;
    }

    public String getAlert() {
        return alert;
    }

    public void setAlert(String alert) {
        this.alert = alert;
    }

    public List<String> getNames() {
        return names;
    }

    public void setNames(List<String> names) {
        this.names = names;
    }

    public Message() {
        super();
        // TODO Auto-generated constructor stub
    }    
}
  • LoginServlet中doPost()內容
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        doGet(request, response);
        request.setCharacterEncoding("utf-8");
        //獲取用戶名
        String username= request.getParameter("username");
        //將用戶名設置為session屬性    
        request.getSession().setAttribute("username", username);
        //重定向的聊天jsp頁面    
        response.sendRedirect("chat.jsp");
    }

以上完整代碼請查看

  • 聊天室ChatSocket內容

該文件可以參考tomcat的
/usr/local/tomcat/webapps/examples/WEB-INF/classes/websocket/chat/ChatAnnotation.java

package com.lxf.socket;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

import com.google.gson.Gson;
import com.lxf.dataVo.Message;


/**
 * websocket 實現聊天室功能
 * @author lxf
 *
 */
@ServerEndpoint("/chatSocket")
public class ChatSocket {
    //存儲每個管道的ChatSocket對象的set集合
    private  static  Set<ChatSocket>  sockets=new HashSet<ChatSocket>();
    /**存儲以<用戶名,ChatSocket對象>的map,注意:這里必須使用static修飾,因為
        在每個客戶端連接過來的時候tomcat都會創建一個ChatSocket實例,當我們使用廣播給多個用戶發消息
        或者在TestServlet中自己實例化ChatSocket的時候,給指定用戶發消息,我們會根據sMap(key)里面的session發送
        如果不指定為static,sMap會為每個對象局部私有,作用域只在本對象中
        使用static修飾,也就意味著所有對象共享sMap
     */
    private  static  Map<String, ChatSocket>  sMap=new HashMap<String, ChatSocket>(); 
    //用來存儲所有登錄人的用戶名
    private  static  List<String>   names=new ArrayList<String>();
    //websocket會話
    private  Session  session;
    //當前用戶名
    public String username;
    //Gson用來處理json和java對象轉換的谷歌開源類庫
    public Gson  gson=new Gson();
    
    /**
     * 當客戶端建立連接的時候觸發
     * @param session
     */
    @OnOpen
    public  void open(Session  session){
            this.session=session;
            sockets.add(this);
            System.out.println("連接成功:sessionId="+session.getId());
            String  queryString = session.getQueryString();
            System.out.println(queryString);
            this.username = queryString.substring(queryString.indexOf("=")+1);
            names.add(this.username);
            sMap.put(username, this);
     
            Message   message=new Message();
            message.setAlert(this.username+"進入聊天室!!當前聊天室人數"+sockets.size());
            message.setNames(names);
            
            broadcast(sockets, gson.toJson(message) );    
    }
    public Map<String, ChatSocket> getSMap()
    {
        return this.sMap;
    }
    
    /**
     * 行客戶端發送消息 ,當客戶端觸發ws.send()方法時觸發
     * @param session
     * @param msg
     */
    @OnMessage
    public  void receive(Session  session,String msg ) throws IOException{
        //this.session.getBasicRemote().sendText(msg+"人數:"+sockets.size());
        
        Message  message=new Message();
        message.setSendMsg(msg);
        message.setFrom(this.username);
        message.setDate(new Date().toLocaleString());
        //指定一個用戶發消息
        sendSingleMsg(this.username, message); 
        //給所有用戶廣播發消息
        //broadcast(sockets, gson.toJson(message));
    }
    
    /**
     * 客戶端關閉瀏覽器時觸發
     * @param session
     */
    @OnClose
    public  void close(Session session){
        sockets.remove(this);
        names.remove(this.username);
        
        Message   message=new Message();
        message.setAlert(this.username+"退出聊天室!!");
        message.setNames(names);
        
        broadcast(sockets, gson.toJson(message));
    }
    
    /**
     * 給指定客戶端發消息
     * @param ss
     * @param msg
     * @throws IOException 
     */
    public void sendSingleMsg(String uname, Message message) throws IOException
    {
        if(sMap.get(uname)!=null)
        {
            
            ChatSocket chatSocket = (ChatSocket)sMap.get(uname);
            System.out.println(chatSocket.session.getBasicRemote());
            //chatSocket.session.getBasicRemote().sendText("hello 111");
            chatSocket.session.getBasicRemote().sendText(gson.toJson(message));
        }else{
            System.out.println("獲取對應用戶信息丟失: " + username);
        }
        
    }
    
    /**
     * 給所有登錄用戶廣播
     * @param ss
     * @param msg
     */
    public void broadcast(Set<ChatSocket>  ss ,String msg ){
        
        for (Iterator iterator = ss.iterator(); iterator.hasNext();) {
            ChatSocket chatSocket = (ChatSocket) iterator.next();
            try {
                System.out.println(chatSocket.session.getBasicRemote());
                chatSocket.session.getBasicRemote().sendText(msg);
                
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
  • 用戶登錄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="jquery-1.4.4.min.js"></script>
</head>
<body>
    
    <form  name="ff"  action="LoginServlet" method="post" >
        用戶名:<input name="username"  /><br/>
        <input type="submit"  />
    </form>

</body>
</html>
  • 聊天室chat.jsp內容

該文件可以參考tomcat的
/usr/local/tomcat/webapps/examples/websocket/chat.xhtml

<%@ 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="jquery-1.4.4.min.js"></script>
<script type="text/javascript">
    var  ws;
    var url="ws://localhost:8081/webSocket1/chatSocket?username=${sessionScope.username}";
    
    window.onload=connect;
    function connect(){
         if ('WebSocket' in window) {
             ws = new WebSocket(url);
         } else if ('MozWebSocket' in window) {
             ws = new MozWebSocket(url);
         } else {
             alert('WebSocket is not supported by this browser.');
             return;
         }
         ws.onmessage=function(event){
            eval("var result="+event.data);
            
            if(result.alert!=undefined){
                $("#content").append(result.alert+"<br/>");
            }
            
            if(result.names!=undefined){
                $("#userList").html("");
                $(result.names).each(function(){
                    $("#userList").append(this+"<br/>");
                });
            }
            
            if(result.from!=undefined){
                $("#content").append(result.from+" "+result.date+
                        " 說:<br/>"+result.sendMsg+"<br/>");
            }           
         };
    }
    
    function  send(){
        var value= $("#msg").val();
        ws.send(value);
    }
</script>
</head>
<body>

    <h3>歡迎 ${sessionScope.username } 使用本系統!!</h3>

    <div  id="content"  style="
        border: 1px solid black; width: 400px; height: 300px;
        float: left;
    "  ></div>
    <div  id="userList"  style="
        border: 1px solid black; width: 100px; height: 300px;
        float:left;
    "  ></div>

    <div  style="clear: both;" >
        <input id="msg"  /><button  onclick="send();"  >send</button>
    </div>
</body>
</html>
  • 以上完成后,啟動tomcat,啟動的過程中會在控制臺中看到:
websocket config---------------掃描到有websocket的endPoint服務個數:2

以上是SocketConfig.java的getAnnotatedEndpointClasses方法執行的,因為我的項目中有簡單輸出和聊天室兩個webSocket服務,所以websocket的endPoint服務個數:2

  • 訪問用戶登錄頁面
http://localhost:8081/webSocket1/login.jsp
  • 登錄后跳轉到
http://localhost:8081/webSocket1/chat.jsp

進行聊天

  • 此時可以多開幾個瀏覽器測試

六.總結

  • 配置好服務端后每個客戶端瀏覽器都可以 Chat.socket = new WebSocket(host);這種方式與服務端生成管道,進行通訊;
  • tomcat服務器啟動的時候會掃碼所有帶注解@ServerEndpoint("/chatSocket")的類,并將其發在ServerApplicationConfig的實現類中的scaned的set入參中
    @Override
    public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> scaned) {
        // TODO Auto-generated method stub
        System.out.println("websocket config---------------掃描到有websocket的endPoint服務個數:"+scaned.size());
        //在這里我們可以做一些過濾操作和一些自己的業務邏輯
        
        //最后給服務器返回,服務器端就會注冊這些socket server,工前臺訪問
        return scaned;
    }
  • 以上這個類負責過濾一些必要的信息,然后必須返回 return scaned,tomcat啟動返回的scaned集合中的webSocket服務(也就相當于在web容器中有對應的webSocket實例),供客戶端連接;
  • 注意:webSocket是多個實例的,每個通道一個實例;
  • 所以我們如果想在servlet中或者是java類中自己指定用戶發消息,必須將每個通道實例放在webSocket類中有static修飾的Map中,集合key=用戶標識,value=該通道的實例,如下:
private  static  Map<String, ChatSocket>  sMap=new HashMap<String, ChatSocket>(); 
  • 如果想在servlet中自行給指定用戶發送消息可以進行如下操作:(注意:指定的用戶必須提前通過客戶端瀏覽器發起連接)TestServlet.java
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        //response.getWriter().append("Served at: ").append(request.getContextPath());
        ChatSocket chat = new ChatSocket();
        Map<String, ChatSocket> sMap = chat.getSMap();
        Message  message=new Message();
        message.setSendMsg("我是自己的servlet給liangxifeng發送的信息,別人是收不到!");
        message.setFrom("servlet");
        message.setDate(new Date().toLocaleString());
        sMap.get("liangxifeng").sendSingleMsg("liangxifeng", message);
        response.getWriter().append("Served at: ").append(request.getContextPath());
    }

以上完整代碼請查看

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容