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="width: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/

 

posted @ 2018-08-10 16:43  可可西里(lemon)  阅读(822)  评论(0编辑  收藏  举报