基於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),關於握手可以參照這篇博文:
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樣式在下面專案原始碼中給出