WebSocket实现即时聊天室

5 篇文章 0 订阅
1 篇文章 0 订阅

原理

很多网站为了实现即时聊天,使用的是轮询方式(在特定的时间间隔,由浏览器向服务器端发出 Http request,然后由服务器返回最新的数据)实现。这种传统的 Http request的方式有个明显的缺点,浏览器需要不断的向服务器发出请求,然而HTTP request 的header是非常长的,里面包含的有用数据可能只是一个很小的值,这样会占用很多的带宽。
比较新的方式是Comet—用了Ajax,但是还是要发出请求。
但是在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。这样做的两大好处是
1. Header
互相沟通的Header是很小的-大概只有 2 Bytes
2. Server Push
服务器的推送,服务器不再被动的接收到浏览器的request之后才返回数据,而是在有新数据时就主动推送给浏览器。

开发基于WebSocket协议的聊天室

开发环境

MyEclipse2014、JDK1.7.45 64位、Tomcat8

WebSocketConfig

package com.sx2.websocket;

import java.util.Set;

import javax.websocket.Endpoint;
import javax.websocket.server.ServerApplicationConfig;
import javax.websocket.server.ServerEndpointConfig;
/**
 * 
 * @author Jin
 *  在Tomcat启动的时候会加载此类(因为实现了ServerApplicationConfig接口)
 */
public class WebSocketConfig implements ServerApplicationConfig{

    @Override
    public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> scan) {
        /*
         * 这里会自动扫描带有@ServerEndpoint注解的类
         * 返回它们的类名集合,这里可以做一些过滤,比如过滤测试用的带有@ServerEndpoint注解的类
         * 最后一定要返回过滤后的set集合,相当于注册服务
         * 最后一定要返回过滤后的set集合,相当于注册服务
         * 最后一定要返回过滤后的set集合,相当于注册服务
         */
        return scan;
    }

    @Override
    public Set<ServerEndpointConfig> getEndpointConfigs(
            Set<Class<? extends Endpoint>> arg0) {
        return null;
    }

}

chatSocket.java


import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
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.alibaba.fastjson.JSONObject;
import com.sx2.pojo.WebSocketResult;

/**
 * 
 * @author Jin
 * 这里带有@ServerEndpoint注解,会自动被扫描到
 */
@ServerEndpoint("/chatSocket")
public class ChatSocket {
    //所有的实例的集合
    private  static  Set<ChatSocket>  sockets=new HashSet<ChatSocket>();
    //保存每个username对应的用户头像,这里只是这个项目中需要,如果实际项目中不需要,此变量无用
    private static Map<String, String> photos = new HashMap<String, String>();
    //保存每个用户的用户名
    private  static  Set<String>   names=new HashSet<String>();
    //每个用户进入聊天室,都会开启一个session,要注意的是这里的session是javax.websocket.Session
    private  Session  session;
    //当有用户进入聊天室时,保存用户名
    private String username;
    //当有用户进入聊天室时,保存用户头像,不是必须的
    private String photo;

    /**
     * 每当有用户进入聊天室时,都会执行一次此函数
     * @param session
     */
    @OnOpen
    public  void open(Session  session){
            this.session=session;
            sockets.add(this);
            //这里的逻辑是得到请求路径后的参数
            //比如:ws://localhost:8080/SX2/chatSocket?username=jin&photo=/image/user/b3.png",那么queryString就是username=jin&photo=/image/user/b3.png
            //username为用户的用户名,是必须的。photo为用户的头像,看实际需要并不是必须的
            String  queryString = session.getQueryString();
            //对得到queryString进行切割,得到用户名和头像
            String[] infos = queryString.split("&");
            this.username = infos[0].substring(infos[0].indexOf("=") + 1);
            this.photo = infos[1].substring(infos[1].indexOf("=") + 1);
            //WebSocket返回值的包装类
            WebSocketResult message = new WebSocketResult();
            String encodeUsername = "";
            try {
                if(this.username.matches("[A-Za-z]")){   //如果用户的用户名是纯英文,比如Tom,Jerry,不用编码
                    encodeUsername = this.username;
                }else{                                   //如果用户的用户名不是纯英文的,则需要编码
                    encodeUsername = URLDecoder.decode(this.username,"UTF-8");
                }
                this.username = encodeUsername;
                //将经过处理的用户名,添加到用户名集合中
                names.add( encodeUsername);
                //将用户名集合放到返回值中
                message.setNames(names);

                photos.put(encodeUsername, photo);
                message.setPhotos(photos);
                //将欢迎信息放到返回值中
                message.setWelCome(encodeUsername+"进入聊天室!!");
                //将返回值的包装类转为json字符串,向所有人打印欢迎信息。
                broadcast(sockets, JSONObject.toJSONString(message));
            } catch (UnsupportedEncodingException e1) {
                // TODO Auto-generated catch block
                message.setWelCome("服务器异常!");
                broadcast(sockets, JSONObject.toJSONString(message));
            }


    }
    @SuppressWarnings("deprecation")
    /**
     * 当有用户发送消息时,会执行此函数
     * @param session  为此用户开启的session
     * @param msg 用户发送的消息文本
     */
    @OnMessage
    public  void receive(Session  session,String msg ){

        WebSocketResult  message=new WebSocketResult();
        //将消息文本放到返回值中
        message.setSendMsg(msg);
        //将来自哪个用户放到返回值中
        message.setFrom(this.username);
        //将发送日期放到返回值中
        message.setDate(new Date().toLocaleString());
        //用户头像
        message.setPhotos(photos);
        //向所有人打印发送的消息
        broadcast(sockets, JSONObject.toJSONString(message));
    }

    /**
     * 
     * @param session 为此用户开启的session
     */
    @OnClose
    public  void close(Session session){
        //从实例集合中移除此实例
        sockets.remove(this);
        //从用户名集合中移除此用户名
        names.remove(this.username);
        //从头像集合中移除此用户的头像
        photos.remove(this.username);
        WebSocketResult   message=new WebSocketResult();
        //将欢送消息放到返回值中
        message.setWelCome(this.username+"退出聊天室!!");
        //重新设置用户名集合
        message.setNames(names);
        //重新设置头像集合
        message.setPhotos(photos);
        //向所有人打印欢送消息
        broadcast(sockets, JSONObject.toJSONString(message));
    }
    /**
     * 
     * @param ss 所有实例集合
     * @param msg 要向浏览器端发送的json字符串
     */
    public void broadcast(Set<ChatSocket>  ss ,String msg ){

        for (Iterator<ChatSocket> iterator = ss.iterator(); iterator.hasNext();) {
            ChatSocket chatSocket = (ChatSocket) iterator.next();
            try {
                //使用session的getBasicRemote方法向浏览器端发送json字符串
                chatSocket.session.getBasicRemote().sendText(msg);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

WebSocketResult.java

import java.util.Map;
import java.util.Set;
/**
 * 
 * @author Jin
 * WebSocket返回值的包装类
 */
public class WebSocketResult {


    private String sendMsg;

    private String date;

    private String from;

    private String welCome;

    private Set<String> names;

    private Map<String, String> photos;

    public String getWelCome() {
        return welCome;
    }

    public void setWelCome(String welCome) {
        this.welCome = welCome;
    }

    public String getSendMsg() {
        return sendMsg;
    }

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

    public String getDate() {
        return date;
    }

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

    public String getFrom() {
        return from;
    }

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

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

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

    public Map<String, String> getPhotos() {
        return photos;
    }

    public void setPhotos(Map<String, String> photos) {
        this.photos = photos;
    }
}

客户端代码

<?xml version="1.0" encoding="UTF-8"?>

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
    <title>Apache Tomcat WebSocket Examples: Chat</title>
    <style type="text/css">
        input#chat {
            width: 410px
        }

        #console-container {
            width: 400px;
        }

        #console {
            border: 1px solid #CCCCCC;
            border-right-color: #999999;
            border-bottom-color: #999999;
            height: 170px;
            overflow-y: scroll;
            padding: 5px;
            width: 100%;
        }
        #userlist {
            border: 1px solid #CCCCCC;
            border-right-color: #999999;
            border-bottom-color: #999999;
            height: 170px;
            overflow-y: scroll;
            padding: 5px;
            width: 100%;
        }
        p {
            padding: 0;
            margin: 0;
        }
   </style>
   <script type="application/javascript">
       //这里的target就是websocket服务的路径
       //对应于@ServerEndpoint("/chatSocket")
       var target = "ws://localhost:8080/SX2/chatSocket?username="+ username +"&photo=<%=path%>/image/user/b3.png";
        window.onload = function() {
            //进入聊天页面,就打开socket通道;
            if ('WebSocket' in window) {
                ws = new WebSocket(target);
            } else if ('MozWebSocket' in window) {
                ws = new MozWebSocket(target);
            } else {
                alert("WebSocket is not supported by this browser!");
                return;
            }
            //当服务端向浏览器发送消息时会执行此函数
            ws.onmessage = function(event) {
                eval("var msg=" + event.data);
                if(undefined != msg.welCome){
                    $('#console').append("msg.welCome");
                }
                if(undefined != msg.sendMsg){
                $('#console').append(msg.sendMsg);
                }
                if(undefined != msg.names){
                    $('#userlist').html("");
                    $(msg.names).each(function(){
                    $('#userlist').append(this);
                    });
                }
            };
        };
        //发送消息
        function sendMsg() {
            var msg = $('#chat').val();
            if(msg == "" || msg == null)
                return;
            ws.send(msg);
            $('#chat').val("");
        }
        document.getElementById('chat').onkeydown = function(event) {
            if (event.keyCode == 13) {
                sendMsg();
            }
        };
   </script>
   </head>
<body>
<div>
    <p>
        <input type="text" placeholder="type and press enter to chat" id="chat" />
    </p>
    <div id="console-container">
        <div id="console"/>
    </div>
    <div id="console-container">
        <div id="userlist"/>
    </div>
</div>
</body>
</html>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值