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: 华文琥珀;"> 在线用户:</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(" "+userNames[i])); } } else if(sign=="userMessage"){ //用户发送信息处理 var p = $("<p/>").html("  "+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/
本文来自博客园,作者:可可西里(lemon),转载请注明原文链接:https://www.cnblogs.com/ldl326308/p/9456005.html