[置顶] 基于websocket的简易聊天室的实现

时间:2022-03-16 12:45:08

用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、项目演示 : 用两个浏览器模拟多个用户通信


[置顶]        基于websocket的简易聊天室的实现

[置顶]        基于websocket的简易聊天室的实现 

       1.1 创建项目,在这里我用maven和springMVC搭建的,不用也一样源码会全部给出,下方附上项目项目源码下载

[置顶]        基于websocket的简易聊天室的实现

       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样式在下面项目源码中给出

    项目源码:http://download.csdn.net/detail/aaa5438438/9589077

   转载请注明出处:http://blog.csdn.net/aaa5438438