1. 程式人生 > >基於websocket的簡易聊天室的實現

基於websocket的簡易聊天室的實現

用java實現基於websocket的簡易聊天室

   WebSocket protocol 是HTML5一種新的協議。它實現了瀏覽器與伺服器全雙工通訊(full-duplex)。一開始的握手需要藉助HTTP請求完成。

                                                                                                                                                                                                                                                  ————  引自 百度百科

    簡介:傳統的使用http協議的web應用在實時通訊方面存在很大的侷限性,由於伺服器只能等客戶端瀏覽器主動發出request才能響應一個response,使客戶端總要向伺服器發出請求才能得到想要的訊息(也可以用長連線讓客戶端hold住這個請求等類似的方式,但是這些方法都會佔用比較多的伺服器資源),隨著html5到來的websocket使web應用的能更方便的實現實時通訊。websocket和http協議一樣也是基於tcp/ip協議的應用層的協議,它只需在第一次和伺服器端連線是借用http協議與伺服器端握手,然後就從http協議切換成websocket協議(我們可以把這個過程看做是http升級成了webstocket),關於握手可以參照這篇博文:

http://blog.csdn.net/edwingu/article/details/44040961,本文不詳細地介紹WebSocket規範,主要介紹下WebSocket在Java Web中的實現。

     websocket有如此強大的功能,作為在web領域數一數二的java當然也提供了實現方法,JavaEE 7中出了JSR-356:Java API for WebSocket規範。不少Web容器,如Tomcat,Nginx,Jetty等都支援WebSocket。Tomcat從7.0.27開始支援WebSocket,從7.0.47開始支援JSR-356,下面我們所要寫的簡易聊天室也是需要部署在Tomcat7.0.47以上的版本

才能執行。

       1、專案演示 : 用兩個瀏覽器模擬多個使用者通訊


 

       1.1 建立專案,在這裡我用maven和springMVC搭建的,不用也一樣原始碼會全部給出,下方附上專案專案原始碼下載


       1.2   後臺程式碼     User.java,這個沒什麼講的

package com.chat.pojo;
/**
 * 使用者類
 * @author chenxin
 *
 */
public class User {
	private Integer id;
	private String name;
	private String password;
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;

	}
	@Override

	public String toString() {
		return "User [id=" + id + ", name=" + name + ", password=" + password
				+ "]";
	}
}

    1.3  UserController.java ,控制層
package com.chat.controller;

import java.util.Collection;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.chat.pojo.User;

/**
 * 使用者登入
 * 主要是學習websocket,資料庫和許可權判斷就不寫了
 * @author chenxin
 *
 */
@Controller
@RequestMapping("/user")
public class UserController {
	
	//分配user的id,需設計為執行緒安全的
	private static int count=1;
	
	//線上使用者列表,需設計成執行緒安全的
	private static List<User> userList = new  CopyOnWriteArrayList<User>();
	
	
	/**
	 * 跳轉到登陸頁面
	 * @return
	 */
	@RequestMapping("/tologin")
	public String toregister(){
		return "login";
	}	
	
	/**
	 * 登陸
	 * @param user
	 * @param request
	 * @return
	 */
	@RequestMapping("/login")
	public String login(User user,HttpServletRequest request){
		//生成id
		user.setId(count);
		//id增長
		UserController.increase();
		//把使用者存入session域中
		request.getSession().setAttribute("user", user);
		//把登陸使用者傳入使用者列表中
		userList.add(user);
		return "index";
	}
	
	/**
	 * 得到線上人數及使用者名稱
	 * @param request
	 * @return
	 */
	@RequestMapping("/getAll")
	public @ResponseBody Collection<User> getAllUser(HttpServletRequest request){
		return UserController.userList;
	}
	
	/**
	 * 下線,當頁面關閉時前臺頁面通過ajax呼叫該handler
	 * @return
	 */
	@RequestMapping("/downLine")
	public void downLine(HttpServletRequest request){
		//得到session中的user
		User user = (User)request.getSession().getAttribute("user");
		//遍歷使用者列表,刪除自己
		for(User item:userList){
			if(user.getId()==item.getId())
				userList.remove(item);
		}
	}
	//用來id增長
	private static synchronized void  increase(){
		 UserController.count++;
	}
}

    1.4  WebsocketChat.java  整個小專案的核心程式碼,websocket連線有該類接受建立
package com.ssm.websocket;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.servlet.http.HttpSession;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;

import com.chat.pojo.User;

/**
 * @ServerEndpoint 註解是一個類層次的註解,它的功能主要是將目前的類定義成一個websocket伺服器端,
 * 註解的值將被用於監聽使用者連線的終端訪問URL地址,客戶端可以通過這個URL來連線到WebSocket伺服器端
 * 由於此類不是servlet類,要通過另一種方法才能的到HttpSession
 * configurator 屬性 通過GetHttpSessionConfigurator類得到httpsession
 */

@ServerEndpoint(value="/websocket" ,<span style="color:#FF0000;">configurator=GetHttpSessionConfigurator.class</span>)//得到httpSession
public class WebSocketChat {
    //靜態變數,用來記錄當前線上連線數。應該把它設計成執行緒安全的。
    private static int onlineCount = 0;

    //concurrent包的執行緒安全Set,用來存放每個客戶端對應的MyWebSocket物件。若要實現服務端與單一客戶端通訊的話,可以使用Map來存放,其中Key可以為使用者標識
    private static CopyOnWriteArraySet<WebSocketChat> webSocketSet = new CopyOnWriteArraySet<WebSocketChat>();

    //與某個客戶端的連線會話,需要通過它來給客戶端傳送資料
    private Session session;
     
    //當前會話的httpession
    private HttpSession httpSession;
   
     /**
     * 連線建立成功呼叫的方法
     * @param session  可選的引數。session為與某個客戶端的連線會話,需要通過它來給客戶端傳送資料
     * @param config   有
     */
    @OnOpen
    public void onOpen(Session session,EndpointConfig config){
    	//得到httpSession
    	this.httpSession = (HttpSession) config.getUserProperties()
                .get(HttpSession.class.getName());
    	this.session = session;
        webSocketSet.add(this);     //加入set中
        addOnlineCount();           //線上數加1
        System.out.println("有新連線加入!當前線上人數為" + getOnlineCount());
    }

    /**
     * 連線關閉呼叫的方法
     */
    
    @OnClose
    public void onClose(){
        webSocketSet.remove(this);  //從set中刪除
        subOnlineCount();           //線上數減1
        System.out.println("有一連線關閉!當前線上人數為" + getOnlineCount());
    }

    /**
     * 收到客戶端訊息後呼叫的方法
     * @param message 客戶端傳送過來的訊息
     * @param session 可選的引數
     */
    @OnMessage
    public void onMessage(String message, Session session) {
    	//獲得訊息發來時間
    	SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String time = sd.format(new Date());
        
    	System.out.println("來自客戶端的訊息:" + message);
    	//替換表情
     message = replaceImage(message);
     //得到當前使用者名稱 
     User user = (User)this.httpSession.getAttribute("user"); 
     String name = user.getName(); 
     //群發訊息 
     for(WebSocketChat item: webSocketSet){
         try { 
              item.sendMessage(name+" "+time+"</br>"+message);
           } catch (IOException e) {
                e.printStackTrace(); continue;
             } 
       } 
    }
   /** * 發生錯誤時呼叫 
    * @param session
   * @param error 
   * @OnError 
   */
   public void onError(Session session, Throwable error){ 
    System.out.println("發生錯誤"); 
    error.printStackTrace();
   } 
   /** * 這個方法與上面幾個方法不一樣。沒有用註解,是根據自己需要新增的方法。
   * @param message
   * @throws IOException 
   */ 
   public void sendMessage(String message) throws IOException{ 
     this.session.getBasicRemote().sendText(message); 
     //this.session.getAsyncRemote().sendText(message); 
   } 
   public void sendMessage(String message,String userid) throws IOException{ } 
     //對count的加減獲取同步 
     public static synchronized int getOnlineCount() {
       return onlineCount; 
     }
     public static synchronized void addOnlineCount() { 
       WebSocketChat.onlineCount++;
     } 
     public static synchronized void subOnlineCount() {
       WebSocketChat.onlineCount--;
     } 
     //替換表情
     private String replaceImage(String message){
       for(int i=1;i<11;i++){ 
       if(message.contains("<:"+i+":>")){ 
         message = message.replace("<:"+i+":>", "<img " + "src='/chat/Images/" + i + ".gif' id='" + i + "' />");
        } 
       } 
        return message;
       }
}



    1.5  GetHttpSessionConfigurator.java
package com.ssm.websocket;

import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
import javax.websocket.server.ServerEndpointConfig.Configurator;
/*
 * 獲取HttpSession的配置類
 * 只要在websocket類上@ServerEndpoint註解中加入configurator=GetHttpSessionConfigurator.class
 * 再在 @OnOpen註解的方法里加入參EndpointConfig config
 * 通過config得到httpSession
 */

public class GetHttpSessionConfigurator extends Configurator {

    @Override
    public void modifyHandshake(ServerEndpointConfig sec,
            HandshakeRequest request, HandshakeResponse response) {
        // TODO Auto-generated method stub
        HttpSession httpSession=(HttpSession) request.getHttpSession();
        sec.getUserProperties().put(HttpSession.class.getName(),httpSession);
    }
    
}

    2. 前臺頁面及js

      2.1 login.jsp

<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>

<html>
<head>
    <title>使用者登入</title>
    <link rel="stylesheet" href="${pageContext.request.contextPath}/Css/login.css" />
    <script src="${pageContext.request.contextPath}/Js/jquery-1.8.2.min.js"></script>
    <script src="${pageContext.request.contextPath}/Js/login.js"></script>
</head>
<body>
    <div id="login">
        <div id="login-t">使用者登入</div>
       <form action="${pageContext.request.contextPath}/user/login" method="post">
        <div id="login-main">
            <div id="login-con">
                <div class="login-item">
                    使用者:<input id="txtName"  name="name" type="text" />
                </div>
                <div class="login-item">
                    密碼:<input id="txtPwd" name="password" type="password" />
                </div>
                <div id="login-btn">
                    <input id="btnLogin" type="submit" value="登入" /> 
                    <input id="btnCancel" type="reset" value="取消" />
                </div>
            </div>         
        </div>
        </form>
		快速登陸,無需註冊
    </div>
    <div id="login-msg"></div>
</body>
</html>

    2.2 index.jsp ,發起websocket請求放在了chat.js檔案中,頁面上的js程式碼是為了方便寫路徑,顯示線上人數和使用者下線的ajax方法
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<html>
<head>
    <title>聊天室</title>
    <link rel="stylesheet" href="${pageContext.request.contextPath}/Css/chat.css" />
    <script src="${pageContext.request.contextPath}/Js/jquery-1.8.2.min.js"></script>
    <script src="${pageContext.request.contextPath}/Js/chat.js"></script>    
	<script type="text/javascript">
		 //獲取當前使用者
	    function getUser(){
	    $("#chat-user-con ul").html("");
	    	$.post("${pageContext.request.contextPath}/user/getAll",{},
	    		function(data){
	    		var temp;
	    		for(temp=0;temp<data.length;temp++){
	    			$("#chat-user-con ul").append("<li>"+data[temp].name+"</li>");
	    		}
	    	},"json");
	    }
    
    	//下線
    	function downLine(){
    		$.post("${pageContext.request.contextPath}/user/downLine",{},
    		function(){});
    	}
	</script>
</head>
<body>
	<span id="message"></span>
    <div id="chat">
        <div id="chat-top">
            <div id="chat-dialog">
                <div id="chat-dialog-t">聊天室</div>
                <div id="chat-dialog-con">
                    <ul>

                    </ul>
                </div>
            </div>
            <div id="chat-user">
                <div id="chat-user-t">當前線上使用者</div>
                <div id="chat-user-con">
                    <ul>

                    </ul>
                </div>
            </div>
        </div>
        <div id="chat-bottom">
            <div id="chat-input">
                <div id="chat-input-expr">
                    <!--<img src="Images/1.gif" id="1" /><img src="Images/2.gif" id="2" /><img src="Images/3.gif" id="3" /><img src="Images/4.gif" id="4" /><img src="Images/5.gif" id="5" /><img src="Images/6.gif" id="6" /><img src="Images/7.gif" id="7" /><img src="Images/8.gif" id="8" /><img src="Images/9.gif" id="9" /><img src="Images/10.gif" id="10" />-->
                </div>
                <div id="chat-input-edit">
                    <div id="input-field">
                        <textarea id="txtInput"></textarea>
                    </div>
                    <div id="input-btn">
                        <input id="btnSend" type="button" value="傳送" />
                    </div>
                </div>
                <div id="chat-input-tip">傳送內容不能為空</div>
            </div>
        </div>
    </div>
    <div id="chat-msg"></div>
</body>
</html>
    2.3  chat.js  裡面是前臺傳送websocket請求的核心程式碼
$(function () { 
var websocket = null;
    //判斷當前瀏覽器是否支援WebSocket
    if ('WebSocket' in window) {
        websocket = new WebSocket("ws://localhost:8080/chat/websocket");
    }
    else {
        alert('當前瀏覽器版本過低不支援websocket!!!')
    }

    //連線發生錯誤的回撥方法
    websocket.onerror = function () {
        setMessageInnerHTML("WebSocket連線發生錯誤");
    }

    //連線成功建立的回撥方法
    websocket.onopen = function () {
        setMessageInnerHTML("WebSocket連線成功");
    }

    //接收到訊息的回撥方法
    websocket.onmessage = function (event) {
    	showMessageInnerHTML(event.data);
    }

    //連線關閉的回撥方法
    websocket.onclose = function () {
        setMessageInnerHTML("WebSocket連線關閉");
    }

    //監聽視窗關閉事件,當視窗關閉時,主動去關閉websocket連線,防止連線還沒斷開就關閉視窗,server端會拋異常。
    //同時退出聊天室(下線)
    window.onbeforeunload = function () {
        closeWebSocket();
        downLine();
    }

    //將是否連線websocket訊息顯示在網頁上
    function setMessageInnerHTML(innerHTML) {
        $("#message").append(innerHTML+"</br>");
    }
    //將訊息顯示在頁面上
    function showMessageInnerHTML(innerHTML){
//    	document.getElementById('chat-dialog-con').innerHTML += innerHTML + '<br/>';
    	$("#chat-dialog-con ul").append("<li>"+innerHTML+"</li>");
    }

    //關閉WebSocket連線
    function closeWebSocket() {
        websocket.close();
    }

    //傳送訊息
    function send() {
        var message = $("#txtInput").val();
        websocket.send(message);
    }


    //新增表情
    for (var i = 1; i <= 10; i++) {
        $("#chat-input-expr").html($("#chat-input-expr").html() + "<img src='/chat/Images/" + i + ".gif' id='" + i + "' />");
        //html和text方法不一樣,前者可新增html標籤和純文字,後者只可新增純文字。
    }

    //向訊息中新增表情
    $("#chat-input-expr img").click(function () {
        $("#txtInput").val($("#txtInput").val() + "<:" + $(this).attr("ID") + ":>");
    });

    //6.傳送訊息判斷
    $("#btnSend").click(function () {
        var sendMsg = $("#txtInput");
        if (sendMsg.val() != "") {
            send();
            sendMsg.val("")
        }
        else {
            alert("傳送內容不能為空!");
            sendMsg.focus();
            return false;
        }
    });
    
    //得到線上使用者
    getUser();
    setInterval("getUser()",3000);
});
    以上為實現聊天室功能的程式碼,還有一些css樣式在下面專案原始碼中給出