zoukankan      html  css  js  c++  java
  • Java WebSocket实现简易聊天室

     

    一、Socket简介

    Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求。Socket的英文原义是“孔”或“插座”,作为UNIX的进程通信机制。Socket可以实现应用程序间网络通信。

    Socket可以使用TCP/IP协议或UDP协议。

    TCP/IP协议

    TCP/IP协议是目前应用最为广泛的协议,是构成Internet国际互联网协议的最为基础的协议,由TCP和IP协议组成:
    TCP协议:面向连接的、可靠的、基于字节流的传输层通信协议,负责数据的可靠性传输的问题。

    IP协议:用于报文交换网络的一种面向数据的协议,主要负责给每台网络设备一个网络地址,保证数据传输到正确的目的地。

    UDP协议

    UDP特点:无连接、不可靠、基于报文的传输层协议,优点是发送后不用管,速度比TCP快。

    二、WebSocket简介与消息推送

    B/S架构的系统多使用HTTP协议,HTTP协议的特点:

    1 无状态协议
    2 用于通过 Internet 发送请求消息和响应消息
    3 使用端口接收和发送消息,默认为80端口
    底层通信还是使用Socket完成。

    HTTP协议决定了服务器与客户端之间的连接方式,无法直接实现消息推送(F5已坏),一些变相的解决办法:

    双向通信与消息推送

    轮询:客户端定时向服务器发送Ajax请求,服务器接到请求后马上返回响应信息并关闭连接。  优点:后端程序编写比较容易。  缺点:请求中有大半是无用,浪费带宽和服务器资源。  实例:适于小型应用。

    长轮询:客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。  优点:在无消息的情况下不会频繁的请求,耗费资小。  缺点:服务器hold连接会消耗资源,返回数据顺序无保证,难于管理维护。 Comet异步的ashx, 实例:WebQQ、Hi网页版、Facebook IM。

    长连接:在页面里嵌入一个隐蔵iframe,将这个隐蔵iframe的src属性设为对一个长连接的请求或是采用xhr请求,服务器端就能源源不断地往客户端输入数据。  优点:消息即时到达,不发无用请求;管理起来也相对便。  缺点:服务器维护一个长连接会增加开销。  实例:Gmail聊天

    Flash Socket:在页面中内嵌入一个使用了Socket类的 Flash 程序JavaScript通过调用此Flash程序提供的Socket接口与服务器端的Socket接口进行通信,JavaScript在收到服务器端传送的信息后控制页面的显示。  优点:实现真正的即时通信,而不是伪即时。  缺点:客户端必须安装Flash插件;非HTTP协议,无法自动穿越防火墙。  实例:网络互动游戏。

    Websocket:
    WebSocket是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。依靠这种技术可以实现客户端和服务器端的长连接,双向实时通信。
    特点:
    事件驱动
    异步
    使用ws或者wss协议的客户端socket

    能够实现真正意义上的推送功能

    缺点:

    少部分浏览器不支持,浏览器支持的程度与方式有区别。

    WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。

    三、界面如下:

    四、实现代码

      1、客户端CSS样式(ChatClient.css):

           #divMessage{
                width:750px;
                height:550px;
                margin:5px;
                background-image: url(https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1533792134030&di=14e6b1be8d569d7dcb570ed21bbd218d&imgtype=0&src=http%3A%2F%2Ffb.topitme.com%2Fb%2F28%2F64%2F1129884950a4e6428bo.jpg);
                background-position: -150px 0px;
                float:left;
                color:black;
                font-size:18px;
                font-family:新宋体;
            }
            #divOperation{
                width:400px;
                height:450px;
                float:right;
            }
            .green{
                color:green;
            }
            .red{
                color:red;
            }
    
            img{
                width:100px;
                height:100px;
            }
    
            span{
                font-size:24px;
                font-family:华文琥珀;
            }
    
            #showAllUserName{
                width:150px;
                height:550px;
                background-image: url(https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1533792134030&di=14e6b1be8d569d7dcb570ed21bbd218d&imgtype=0&src=http%3A%2F%2Ffb.topitme.com%2Fb%2F28%2F64%2F1129884950a4e6428bo.jpg);
                float:left;
                font-size:18px;
                font-family:新宋体;
                margin:5px;
    
            }
    
            input[type=button]{
                width:80px;
                height: 30px;
                margin:5px;
            }

       2、客户端界面:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>聊天界面</title>
        <link rel="stylesheet" href="css/ChatClient.css">
    </head>
    <body>
        <div  style="OVERFLOW-Y: auto; OVERFLOW-X:hidden;" id="showAllUserName">
            <p style="color:green;font-family: 华文琥珀;">&emsp;在线用户:</p>
        </div>
        <div style="OVERFLOW-Y: auto; OVERFLOW-X:hidden;" id="divMessage">
            
        </div>
    
        <div id="divOperation">
            <p>
                昵称:<input style="120px;line-height:20px;" type="text" maxlength="5" id="txtUserName" /></p>
                <span id="spanUserName" class="red">未连接</span>
            </p>
            <p>
                消息输入(可输入HTML代码):<textarea id="txtMessage" cols="50" rows="4"></textarea>
            </p>
            <p>
                <input type="button" id="btnConnection" value="连接" /><span id="spanMessage"></span>
                <input type="button" disabled id="btnSend" value="发送"/>
                <input type="button" disabled id="btnClose" value="关闭" />
            </p>
        </div>
        <script  type="text/javascript" src="js/jquery-1.11.3.js"></script>
        <script>
    
            var socket;
            if(typeof(WebSocket)=="undefined"){
                alert("您的浏览器不支持WebSocket");
            }
    
            //连接点击
            $("#btnConnection").click(function(){
                var name = $("#txtUserName").val();
    
                if(name==null || name==""){
                    $("#spanUserName").text("用户名格式错误!").prop("class","red");
                    return;
                }
    
                //实例化WebSocket,指定要连接的服务器地址与端口
                socket = new WebSocket("ws://localhost:8080/ws/"+name);
    
                //打开事件
                socket.onopen = function () {
                    console.log("Socket已打开");
                    $("#spanUserName").text("已连接").prop("class","green");
                    $("#btnConnection").prop("disabled",true);
                    $("#btnSend").prop("disabled",false);
                    $("#btnClose").prop("disabled",false);
                    $("#txtUserName").prop("disabled",true);
                }
    
                //获得消息事件
                socket.onmessage = function (msg) {
    
                    var sign = msg.data.substring(0,msg.data.indexOf("&"));
                    var content = msg.data.substring(msg.data.indexOf("&")+1);
    
    
                    if(sign=="userAllName"){ //所有在线用户信息处理
                        var userNames = content.split(",");
    
                        //除了第一个p标签,其余清空
                        $("#showAllUserName p:gt(0)").remove();
    
                        for(var i=0;i<userNames.length;i++){
                            $("#showAllUserName").append($("<p/>").html("&nbsp;"+userNames[i]));
                        }
    
                    }
                    else if(sign=="userMessage"){ //用户发送信息处理
                        var p = $("<p/>").html("&emsp;&emsp;"+content);
                        $("#divMessage").append(p);
                    }
                }
    
                //关闭事件
                socket.onclose = function(){
                    console.log("socket已关闭");
                }
    
                //发生错误的事件
                socket.onerror = function(){
                    console.log("发生了错误");
                }
            });
    
            //发送消息
            $("#btnSend").click(function(){
                if(document.getElementById("txtMessage").value=="" || document.getElementById("txtMessage").value==null){
                   alert("消息不能为空!");
                    return;
                }
    
                socket.send(document.getElementById("txtMessage").value);
                document.getElementById("txtMessage").value = "";
            });
    
            //关闭事件
            $("#btnClose").click(function(){
                socket.close();
                $("#btnConnection").prop("disabled",false);
                $("#btnClose").prop("disabled",true);
                $("#btnSend").prop("disabled",true);
                $("#spanUserName").text("已断开").prop("class","red");
                $("#txtUserName").prop("disabled",false);
                $("#txtUserName").prop("value","");
                $("#divMessage p").remove();
                $("#showAllUserName p:gt(0)").remove();
            });
    
        </script>
    </body>
    </html>

      3、后台代码(WSServer.java):

    package socket;
    
    import javax.websocket.*;
    import javax.websocket.server.PathParam;
    import javax.websocket.server.ServerEndpoint;
    import java.io.IOException;
    import java.util.*;
    
    @ServerEndpoint("/ws/{user}")
    public class WSServer {
        private String currentUser;
        private static Set<Session> map = new HashSet<>();
        //用户保存
        private static Map<String,String> userName = new HashMap<String, String>();
    
        //连接打开时执行
        @OnOpen
        public void onOpen(@PathParam("user")String user, Session session){
            currentUser = user;
            map.add(session);
            userName.put(session.getId(),user);
            //自定义方法,更新客户端的客户在线信息
            sendOutMessage();
        }
    
    
    
        //收到消息时执行
        @OnMessage
        public void onMessage(String message,Session session) throws IOException {
            //把信息传到已连接的用户客户端
            for(Session sess : map){
                //userMessage& 这段是为了客户端判断信息类型,是用户发送的消息,还是所有在线用户的信息
                sess.getBasicRemote().sendText("userMessage&"+currentUser + "  : " + message);
            }
    
        }
    
        //连接关闭时执行
        @OnClose
        public void onClose(Session session, CloseReason closeReason){
            map.remove(session);//删掉断开连接的用户
            userName.remove(session.getId()); //删掉断开连接的用户信息
            //更新在线的所有用户
            sendOutMessage();
        }
    
        //连接错误时执行
        @OnError
        public void OnError(Throwable t){
            t.printStackTrace();
        }
    
        private static void sendOutMessage(){
            //将所有在线的用户拼接成字符串  userAllName&  这段是信息类型判断
            StringBuffer userAllStr = new StringBuffer("userAllName&");
            String str = "";
            for(String s : userName.keySet()){
                userAllStr.append(str+userName.get(s));
                str=",";
            }
            System.out.println(userAllStr);
            //循环所有客户id,向客户端发送信息
            for(Session session : map){
                try {
                    session.getBasicRemote().sendText(userAllStr.toString());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
        }
    
    }

     初心易得,始终难守。(LC)

     介绍一个博客:http://best.cnblogs.com/

  • 相关阅读:
    在C#中使用SQL存储过程说明
    asp.net后台获取html控件值
    SQL字符串操作汇总[记住这个]select substring(quantityperunit,charindex('',quantityperunit)+1,100) as 结果 from products
    分页控件AspnetPager的用法,巩固用
    摆脱Access在.net中中模糊查询,老错的困扰
    基于黑金开发板的秒表verilog hdl程序
    2808 sci 接收中断
    黑金开发板状态机实现的可用按键控制的流水灯
    基于黑金开发板的键盘边沿检测程序
    可以使用键盘实现加减数的数码管的verilog hdl程序(基于黑金开发板)
  • 原文地址:https://www.cnblogs.com/ldl326308/p/9456005.html
Copyright © 2011-2022 走看看