websocket 即时聊天

欢迎使用Markdown编辑器写博客

本Markdown编辑器使用[StackEdit][6]修改而来,用它写博客,将会带来全新的体验哦:

原理

TCP连接
采用TCP协议,在真正的读写操作之前,server与client之间必须建立一个连接。读写完成后,双方不再需要这个连接时,就可以释放这个连接。

TCP短连接
流程:client向server发起TCP连接请求,client向server发送消息,server回应client,然后一次读写就完成了。这时候client会发起close操作,连接关闭。
短连接的优点是:

  • 管理起来比较简单,连接都是有用的连接,不需要额外的控制手段

  • HTTP1.0就是基于此实现的,他是是无状态的。浏览器和服务器每一次HTTP操作,就建立一次连接,但任务结束就中断连接。

TCP长连接:
流程:client向server发起连接,server接受client连接。双方建立连接,Client与server完成一次读写之后,它们之间的连接并不会主动关闭,用心跳保活。后续的读写操作会继续使用这个连接。

WebSocket就是基于TCP连接建立通讯的。
一旦WebSocket连接建立后,后续数据都以帧序列的形式传输。在客户端或Server端中断连接前,不需要客户端和服务端重新发起连接请求。

WebSocket建立连接

websocket与http同样建立于tcp传输协议之上,通过tcp传输层进行数据传输。
我们要使用websocket协议进行通信则首先要建立起websocket连接,这个连接的建立依赖于http。

发起握手:
每个WebSocket连接都始于一个HTTP请求。
具体来说,WebSocket协议在第一次握手连接时,通过HTTP协议在传送数据,但是比普通HTTP请求相比多了一些字段。
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13

服务器响应:
根据特殊的请求头进行了特殊响应,首先101返回码表明本次连接的通信协议经过了转换并成功握手成功建立起了通信。
connection字段和upgrade字段则表明本次通信协议进行了升级转换,转换的是websocket协议。
服务器响应头如下
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat

建立了websocket连接后,只要客户端和服务器端任意一端不主动断开连接前,通信行为都是在一个持久连接上发起,后续数据与请求都通过帧序列的形式进行传输。

这里写图片描述

代码块

代码块语法遵循标准markdown代码,例如:


**前端jsp**
<script type="text/javascript">

    //当前用户编号  --- 唯一
    var userno = $("#btn-clearing").attr("userId");
    //当前用户名称
    var userName = $("#btn-clearing").attr("userName");
    var websocket = null;


    if ('WebSocket' in window) {
       websocket = new WebSocket("ws://localhost:8080/community.web.merchant/websocket/"+$("#btn-clearing").attr("userId"));
    }else {
        alert('当前浏览器 Not support websocket')
    }   

    //连接发生错误的回调方法
    websocket.onerror = function () {
        setMessageInnerHTML("WebSocket连接发生错误");
    };


    //连接成功建立的回调方法
    websocket.onopen = function () {
        setMessageInnerHTML("WebSocket连接成功");
    }


    //接收到消息的回调方法
    websocket.onmessage = function (event) {
        setMessageInnerHTML(event.data);
    }


    //连接关闭的回调方法
    websocket.onclose = function () {
        setMessageInnerHTML("WebSocket连接关闭");
    }


    //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
    window.onbeforeunload = function () {
        closeWebSocket();
    }


    //将消息显示在网页上
    function setMessageInnerHTML(sendMessage) {
        //将数据装换成json
        var json = JSON.parse(sendMessage);

        var useNo = json.sourse;

        var msg = "<p style='color:red;'>"+json.createtime + "   "+ json.messageType +" : " + json.msgContent + "</p>";

        $("#show-message").append(msg);
    }

    //关闭WebSocket连接
    function closeWebSocket() {
        websocket.close();
    }

    /*键盘监听  ctrl+enter 发送消息  */
    function keydowm(event){
        var e = event || window.event || arguments.callee.caller.arguments[0];
         if(e.ctrlKey && e.keyCode==13){ // enter 键 
             //要做的事情 
                send();
         }
    }

    //发送消息
    function send() {
        var message = document.getElementById('text-message').value;//要发送的消息内容
        var now=getNowFormatDate();//获取当前时间

        var msg = "<p>"+now+"   我 :"+message+"</p>";
        $("#show-message").append(msg);

        //接收人编号        
        var ToSendUserno= "shop"+$("#btn-clearing").attr("shopId"); 
        message=message+"|"+ToSendUserno+"|"+userName;//将要发送的信息和内容拼起来,以便于服务端知道消息要发给谁
        websocket.send(message);

        //清空输入框
        document.getElementById('text-message').value = "";//要发送的消息内容
    }
    //获取当前时间
    function getNowFormatDate() {
        var date = new Date();
        var seperator1 = "-";
        var seperator2 = ":";
        var month = date.getMonth() + 1;
        var strDate = date.getDate();
        if (month >= 1 && month <= 9) {
            month = "0" + month;
        }
        if (strDate >= 0 && strDate <= 9) {
            strDate = "0" + strDate;
        }
        var currentdate = date.getFullYear() + seperator1 + month + seperator1 + strDate
                + " " + date.getHours() + seperator2 + date.getMinutes()
                + seperator2 + date.getSeconds();
        return currentdate;
    } 

    function cliText(text){
       var te =  $("#text-message").val();
       $("#text-message").val(te + text);
    }

    //移动聊天弹框
    var posX;
    var posY;
    fwuss = document.getElementById("show-dialogue");
    fwuss.onmousedown=function(e){
        posX = event.x - fwuss.offsetLeft;//获得横坐标x
        posY = event.y - fwuss.offsetTop;//获得纵坐标y
        document.onmousemove = mousemove;//调用函数,只要一直按着按钮就能一直调用
    }
    document.onmouseup = function()
    {
        document.onmousemove = null;//鼠标举起,停止
    }
    function mousemove(ev)
    {
        if(ev==null) ev = window.event;//IE
        fwuss.style.left = (ev.clientX - posX) + "px";
        fwuss.style.top = (ev.clientY - posY) + "px";
    }

    </script>
    后端封装实体类  Messagepojo

public class Messagepojo implements Serializable {

    private static final long serialVersionUID = -6451812593150428369L;

    private String sourse;// 信息来源
    private String messageType;// 消息类型
    private String msgContent;// 消息内容
    private String target;// 发送目的地
    private String infoSourceIP;// 信息来源ip
    private String createtime;// 消息保存时间
    private String otherContent;// 其他信息

    public Messagepojo() {
        super();
    }

    public Messagepojo(String sourse, String messageType, String msgContent, String target, String infoSourceIP,
            String createtime, String otherContent) {
        super();
        this.sourse = sourse;
        this.messageType = messageType;
        this.msgContent = msgContent;
        this.target = target;
        this.infoSourceIP = infoSourceIP;
        this.createtime = createtime;
        this.otherContent = otherContent;
    }

    public String getSourse() {
        return sourse;
    }

    public void setSourse(String sourse) {
        this.sourse = sourse;
    }

    public String getMessageType() {
        return messageType;
    }

    public void setMessageType(String messageType) {
        this.messageType = messageType;
    }

    public String getMsgContent() {
        return msgContent;
    }

    public void setMsgContent(String msgContent) {
        this.msgContent = msgContent;
    }

    public String getTarget() {
        return target;
    }

    public void setTarget(String target) {
        this.target = target;
    }

    public String getInfoSourceIP() {
        return infoSourceIP;
    }

    public void setInfoSourceIP(String infoSourceIP) {
        this.infoSourceIP = infoSourceIP;
    }

    public String getCreatetime() {
        return createtime;
    }

    public void setCreatetime(String createtime) {
        this.createtime = createtime;
    }

    public String getOtherContent() {
        return otherContent;
    }

    public void setOtherContent(String otherContent) {
        this.otherContent = otherContent;
    }

    public static long getSerialversionuid() {
        return serialVersionUID;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("Messagepojo [sourse=");
        builder.append(sourse);
        builder.append(", messageType=");
        builder.append(messageType);
        builder.append(", msgContent=");
        builder.append(msgContent);
        builder.append(", target=");
        builder.append(target);
        builder.append(", infoSourceIP=");
        builder.append(infoSourceIP);
        builder.append(", createtime=");
        builder.append(createtime);
        builder.append(", otherContent=");
        builder.append(otherContent);
        builder.append("]");
        return builder.toString();
    }



public class ServerEncoder implements Encoder.Text<Messagepojo> {

    @Override
    public void destroy() {
        // TODO Auto-generated method stub

    }

    @Override
    public void init(EndpointConfig arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public String encode(Messagepojo messagepojo) throws EncodeException {
        try {
            return Java2Json(messagepojo);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return null;
        }

    }

    public String Java2Json(Messagepojo obj) {
        JSONObject object = (JSONObject) JSONObject.toJSON(obj);
        return object.toString();
    }

}

package org.great.websocket;

import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;

import javax.websocket.EncodeException;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint(value = "/websocket/{userno}", encoders = { ServerEncoder.class })
public class WebSocket {

    // 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
    private static int onlineCount = 0;
    // concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识
    private static ConcurrentHashMap<String, WebSocket> webSocketSet = new ConcurrentHashMap<String, WebSocket>();
    // 与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session WebSocketsession;
    // 当前发消息的人员编号
    private String userno = "";

    /**
     * 连接建立成功调用的方法
     *
     * @param session
     *            可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
     */
    @OnOpen
    public void onOpen(@PathParam(value = "userno") String param, Session WebSocketsession, EndpointConfig config) {
        System.out.println(param);
        userno = param;// 接收到发送消息的人员编号
        this.WebSocketsession = WebSocketsession;
        webSocketSet.put(param, this);// 加入mapaddOnlineCount(); // 在线数加1
        System.out.println("有新连接加入!当前在线人数为" + getOnlineCount());
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        if (!userno.equals("")) {
            webSocketSet.remove(userno); // 从set中删除
            subOnlineCount(); // 在线数减1
            System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
        }
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message
     *            客户端发送过来的消息
     * @param session
     *            可选的参数
     */
    @SuppressWarnings("unused")
    // @OnMessage
    public void onMessage(String message, Session session) {
        System.out.println("来自客户端的消息:" + message);
        // session.get
        // 群发消息

    }

    /**
     * 给指定的人发送消息
     * 
     * @param message
     * @throws EncodeException
     */
    @OnMessage
    public void sendToUser(String message) throws EncodeException {
        String sendUserno = message.split("[|]")[1];
        String sendMessage = message.split("[|]")[0];
        String sendUserName = message.split("[|]")[2];
        String now = getNowTime();
        try {
            if (webSocketSet.get(sendUserno) != null) {

                Messagepojo messagepojo = new Messagepojo(userno, sendUserName, "\n" + sendMessage, "", "", now, "");

                // webSocketSet.get(sendUserno).sendMessage(now + "用户" + userno + "发来消息:" + "
                // <br/> " + sendMessage,
                // userno);
                webSocketSet.get(sendUserno).sendObject(messagepojo);
            } else {
                System.out.println("当前用户不在线");

                Messagepojo messagepojo = new Messagepojo(userno, "系统消息", "\n" + "客服不在线...", "", "", now, "");

                // webSocketSet.get(sendUserno).sendMessage(now + "用户" + userno + "发来消息:" + "
                // <br/> " + sendMessage,
                // userno);
                webSocketSet.get(userno).sendObject(messagepojo);

            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取当前时间
     *
     * @return
     */
    private String getNowTime() {
        Date date = new Date();
        DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String time = format.format(date);
        return time;
    }

    /**
     * 发生错误时调用
     *
     * @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, String userno) throws IOException {
        this.WebSocketsession.getBasicRemote().sendText(message);
        this.WebSocketsession.getBasicRemote().sendText(userno);
        // this.session.getAsyncRemote().sendText(message);
    }

    /**
     * 这个方法与上面几个方法不一样。没有用注解,是根据自己需要添加的方法。
     *
     * @param message
     * @throws IOException
     * @throws EncodeException
     */
    public void sendObject(Object obj) throws IOException, EncodeException {
        this.WebSocketsession.getBasicRemote().sendObject(obj);
        // this.session.getAsyncRemote().sendText(message);
    }

    public static synchronized int getOnlineCount() {
        return onlineCount;
    }


    public static synchronized void addOnlineCount() {
        WebSocket.onlineCount++;
    }


    public static synchronized void subOnlineCount() {
        WebSocket.onlineCount--;
    }

}


演示

这里写图片描述

这里写图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值