基于websocket的简易聊天室的实现

来源:互联网 发布:淘宝积分记录怎么删除 编辑:程序博客网 时间:2024/04/30 15:45

用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;}@Overridepublic 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){//生成iduser.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中的userUser 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>)//得到httpSessionpublic 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


1 0
原创粉丝点击