本文是基于websocket写的一个简单的聊天室的例子,可以实现简单的群聊和私聊。是基于websocket的注解方式编写的。(有一个小的缺陷,如果用户名是中文,会乱码,不知如何处理,如有人知道,请告知一下。在页面获取到的不会乱码,但是传递到websocket中,在@OnOpen注解标注的方法中获取就会乱码。用户名是在weboscket的url中以rest风格的参数传递过去的。)
一、效果如下
当用户登入(或登出)聊天室时,聊天界面显示一个欢迎的提示信息,同时刷新右边的在线用户列表。
1.当不选中右边的在线用户列表时,发送的时群聊信息,所有人都可以看到,即 图片上化红线的部分。
2.当选中右边的在线用户时,自己发送的消息在右边显示,接受到消息的用户消息在左侧显示。即从上方画矩形的区域可以看出,私聊的消息只有自己和私聊的那个人可以看见。
二、开发环境:
window tomcat7.0.63 jdk1.8 Chrome浏览器
因为websocket是html5的一个技术,有些浏览器并不支持,而且jdk貌似也要在1.7或1.7以上,tomcat的低版本是不支持websocket的。
三、开发步骤:
1.服务器端:
1.1先编写一个简单的登录servlet,完成登录的过程
1.2编写一个类实现ServerApplicationConfig接口,并实现getAnnotatedEndpointClasses(...)方法,该方法是基于注解的。
1.3 编写一个普通的java类,使用注解@ServerEndpoint标记,标明该类是一个websocket的服务类(该类是一个多例的)
1.3.1 @ServerEndpoint("/chat/{username}") 标明可以端链接服务器的地址是:ws://localhost:端口号/项目名/chat/.... 使用rest风格的方式,后面的username即为用户名,需要在@OnOpen方法中获取到
1.3.2@OnOpen 标注方法(表示客户端和服务器端第一次建立连接时触发)
1.获取到用户名
2.保存session,此处的session为websocket的session ,使用此session可以向客户端发送消息
3.先客户端发送一条欢迎消息,同时将所有的在线用户发送给客户端,将消息的类型也要发给客户端
4.因为上方说过,该类是一个多实例的,因此需要将用户和该用户的对应的session存入到一个静态的map中。
1.3.2@OnMessage注解标注方法(表示客户端发送消息过来时触发)
1.获取客户端发送过来的消息,并转换成一个map (客户端发送的消息为一个msg和toUser) --> 私聊时,toUser中会有值,群聊时没有。
2.封装客户端发送过来的消息,此时不需要传递在线用户列表(因为没有新加入的用户和离开的用户),也需要给定消息的类型
1.3.3@OnClose注解标注方法(表示客户端关闭了websocket连接)
1.将当前用户的移除
2.向客户端发送一条离开消息,需要传递在线用户列表和消息的类型
1.4广播消息
在该方法中需要判断,
当前是群聊还是私聊,如果是群聊需要将消息发送给所有的人。
当前是私聊聊,只发送给特定的人。
2.客户端:(websocket的url是ws://开头的,如果是安全的则是wss://开头)
2.1编写一个简单的登录界面
2.2显示当前用户,以及和服务器端建立连接
2.2.1从request中获取到用户名,显示到页面中
2.2.2创建websocke对象,此处需要判断浏览器是否支持websocket
2.2.3创建websocket对象后,监听websocket的onopen,onmessage,onerror,onclose事件
2.3.3.1此处说一个onmessage方法,次方法当服务器发送数据过来时触发,改方法中有一个参数,假如叫r,r.data 即后台返回的数据
2.3.3.2获取到后台返回的数据,并将它转换成json对象(因为我在服务器端是以json的数据返回的),进行处理
-> 获取后台返回的数据的消息的类型
->欢迎信息或离开信息 。1.需要显示信息.2.需要刷新在线用户列表
->如果是聊天信息。 1.简单的封装一下,显示到界面上。
2.3.4给发送按钮绑定事件,
1.获取发送的数据,->获取右边选择的在线用户,如果没有就是空的->将消息发送到服务器端。如果是私聊,将自己发送的消息,放到右边显示。
四、代码实现:(需要注意一下我url的组装方式)
客户端:(登录的界面代码就不贴出了,只贴聊天界面)
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isELIgnored="false"%> <!DOCTYPE> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <style type="text/css"> *{box-sizing:border-box; -moz-box-sizing:border-box; /* Firefox */ -webkit-box-sizing:border-box} .container{width: 400px;height: 300px;border: 1px solid lightblue;margin: 0 auto;} .container .main{width: 70%;height:80%;float: left;border-right: 1px solid lightblue;overflow: scroll;} .container .main .commonmsg{text-align: center;color: red;background-color: #f9f9f9;height: 50px;line-height: 50px;border-bottom: 1px solid lightblue;} .container .main .smsg{text-align: right;padding: 5px;} .container .onlineUsers{float: left;width: 29.8%;} .container .msg{border-top: 1px solid lightblue;height: 20%;width: 100%;} table[t_table]{width: 100%;border-collapse: collapse;} table[t_table] thead{background-color: #eee;} table[t_table] thead tr{background-color: #eee;} table[t_table] thead tr th{padding: 2px;border: 1px solid #ccc;} table[t_table] tbody tr td{border: 1px solid #ccc;padding: 2px;} table[t_table] tbody tr{color:black;} table[t_table] tbody tr:nth-child(odd){background-color:#fff;} table[t_table] tbody tr:nth-child(even){background-color: #f9f9f9;} table[t_table] tbody tr:hover{cursor: pointer;background-color: rgba(0,0,0,.05);color:red;} </style> <title>Insert title here</title> <script type="text/javascript" src="${pageContext.request.contextPath }/js/jquery-2.1.1.js"></script> </head> <body> <div class="container"> <h1 style="text-align: center;">当前用户:${username }</h1> <div class="main"> </div> <div class="onlineUsers"> <table t_table > <thead> <tr> <th colspan="2" >在线用户列表</th> </tr> </thead> <tbody id="onLineUsersTbody" style="overflow: scroll;"> </tbody> </table> </div> <div style="clear: both;"></div> <div class="msg"> <div contenteditable="true" style="width: 80%;float: left;"> <textarea style="width: 100%;height: 100%;" id="sendMsg"></textarea> </div> <div style="float: left;height: 100%;width: 20%;"> <input type="button" value="发送" style="display: block;width: 100%;height: 100%;" id="send"/> </div> <div style="clear: both;" id="send"></div> </div> </div> <script type="text/javascript"> $(function(){ var username = '${requestScope.username}', ws = null, wsUrl = "ws://localhost:8080/study-websocket/chat/"+ username; console.info(username); var Chat = { openConnection : function(){ if ('WebSocket' in window) { ws = new WebSocket(wsUrl); } else if ('MozWebSocket' in window) { ws = new MozWebSocket(wsUrl); } else { alert('您的浏览器不支持websocket.'); return; } console.info("创建websocket对象成功."); ws.onopen = function(){ console.info("websocket 连接打开."); } ws.onmessage = function(r){ console.info("后台返回的数据:"); console.info(r.data); Chat.handleMsg(JSON.parse(r.data)); } ws.onerror = function(e){ console.warn(e); console.warn("websocket出现异常."); } ws.onclose = function(e){ console.info("websocket连接关闭."); } }, handleMsg : function(data){ var type = data.msgTypeEnum; switch(type){ case "WELCOME": case "LEAVE" : Chat.handleWelcomeMsg(data); break; case "CHAT" : Chat.handChatMsg(data); break; default : console.info("后台返回未知的消息类型."); break; } }, handChatMsg : function(data){ console.warn(data); $('<div />').addClass("chatmsg").html(data.msg.date+" -- " + data.msg.fromUser + "<br / >" + data.msg.msg).appendTo(".main"); }, handleWelcomeMsg : function(data){ // 1.处理在线用户 var users = data.users; var trs = ""; users.forEach(function(user,i){ trs += "<tr>".concat("<td>").concat("<input type='checkbox' value='"+user+"' />").concat("</td>") .concat("<td>").concat(user).concat("</td>") .concat("</tr>"); }); $('#onLineUsersTbody').html(trs); // 2.处理消息 $('<div />').addClass("commonmsg").html(data.msg).appendTo(".main"); }, sendMsg : function(){ if(ws){ $('#send').off('click').on('click',function(){ var msg = $('#sendMsg').val(); var toUser = []; $('#onLineUsersTbody').find(":checked").each(function(i,ele){ toUser.push($(ele).val()); }); if(msg){ var jsonMsg = { msg : msg, toUser : toUser.join(",") }; ws.send(JSON.stringify(jsonMsg)); $('#sendMsg').val(''); Chat.addPageMsg(toUser,msg); } }); }else{ alert('连接服务器的websocket通道已经关闭.') } }, addPageMsg : function(toUser,msg){ if(toUser.length){ $('<div />').addClass("smsg").html(username + ":" + msg).appendTo(".main"); } } }; Chat.openConnection(); Chat.sendMsg(); }); </script> </body> </html>
服务器端:
1.实现了ServerApplicationConfig的类
/** * 此类在服务器启动时,自动运行 * @author huan */ public class ChatConfig implements ServerApplicationConfig { private Logger log = Logger.getLogger(ChatConfig.class); @Override /** * @param classes 中的类是拥有@ServerEndpoint注解标注的类 */ public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> classes) { for (Class<?> clazz : classes) { log.info("加载websocket服务类:" + clazz.getName()); } return classes; } @Override public Set<ServerEndpointConfig> getEndpointConfigs(Set<Class<? extends Endpoint>> arg0) { return null; } }2.@ServerEndpoint注解标注的类
package com.huan.study.websocket.chat.endpoint; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.websocket.CloseReason; 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; import org.apache.log4j.Logger; import com.huan.study.websocket.chat.data.Msg; import com.huan.study.websocket.chat.data.ResponseMsg; import com.huan.study.websocket.chat.enums.MsgTypeEnum; import com.huan.study.websocket.chat.util.JsonUtil; /** * 该类表示websocket服务端,此类不需要别的配置 * * @author huan * */ @ServerEndpoint("/chat/{username}") public class ChatEndpoint { private static Logger log = Logger.getLogger(ChatEndpoint.class); /** 保存的是用户名和该用户对应的session */ private static Map<String, ChatEndpoint> userSessionMap = new ConcurrentHashMap<String, ChatEndpoint>(); private Session session; private String username; @OnOpen public void onOpen(Session session, @PathParam("username") String username) { log.info("【" + username + "】进入聊天室."); this.session = session; this.username = username; userSessionMap.put(username, this); Msg msg = new Msg(); msg.setMsg(String.format("欢迎【%s】进入聊天室", username)); msg.setMsgTypeEnum(MsgTypeEnum.WELCOME); msg.setUsers(userSessionMap.keySet()); broadcast(JsonUtil.toJson(msg)); } @OnMessage public void onTextMessage(Session session, String msg) { log.info(String.format("客户端发送消息:%s", msg)); Map<String, Object> msgMap = JsonUtil.toMap(msg); String toUser = (String) msgMap.get("toUser"); Msg _msg = new Msg(); _msg.setMsg(new ResponseMsg(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()), username, msgMap.get("msg"))); _msg.setMsgTypeEnum(MsgTypeEnum.CHAT); broadcast(JsonUtil.toJson(_msg), toUser); } @OnClose public void onClose(CloseReason closeReason) { log.info("关闭: " + closeReason.getCloseCode()); log.info("关闭: " + closeReason.getReasonPhrase()); userSessionMap.remove(this.username); Msg msg = new Msg(); msg.setMsg(String.format("欢迎【%s】离开聊天室", username)); msg.setMsgTypeEnum(MsgTypeEnum.LEAVE); msg.setUsers(userSessionMap.keySet()); broadcast(JsonUtil.toJson(msg)); } @OnError public void OnError(Throwable t) { t.printStackTrace(); } private static void broadcast(String msg, String toUser) { String[] arr = null; if (null != toUser && !"".equals(toUser)) { log.info("当前是单聊."); arr = toUser.split(","); } else { log.info("当前是群聊."); } for (Map.Entry<String, ChatEndpoint> entry : userSessionMap.entrySet()) { String username = entry.getKey(); if (null != arr) { if (!Arrays.asList(arr).contains(username)) { continue; } } ChatEndpoint endpoint = entry.getValue(); synchronized (endpoint) { try { log.info(String.format("返回到客户端的消息:%s", msg)); endpoint.session.getBasicRemote().sendText(msg); } catch (IOException e) { e.printStackTrace(); log.info("【" + username + "】离开了聊天室."); userSessionMap.remove(username); try { endpoint.session.close(); } catch (IOException e1) { e1.printStackTrace(); log.info(String.format("关闭用户【%s】的session失败", username)); } Msg _msg = new Msg(); _msg.setMsg(String.format("【%s】 离开了聊天室.", username)); _msg.setMsgTypeEnum(MsgTypeEnum.LEAVE); _msg.setUsers(userSessionMap.keySet()); _msg.getUsers().remove(username); broadcast(JsonUtil.toJson(_msg)); } } } } /** 广播消息 */ private static void broadcast(String msg) { broadcast(msg, null); } }主要的代码就是以上的部分:(下面是几个用到的类)
Msg.java类,该类是返回给客户端的消息
/*** 消息的类型*/ private MsgTypeEnum msgTypeEnum; /*** 返回给客户端的消息*/ private Object msg; /*** 当前的在线用户 */ private Set<String> users;ResponseMsg.java类是私聊时或群聊时返回给客户端的消息类
private String date; private String toUser; private String fromUser; private Object msg;MsgTypeEnum.java 是一个枚举类,用于设定消息的类型(客户端根据消息的类型,以不同的方式处理消息)
WELCOME("进入聊天室"), LEAVE("离开聊天室"),CHAT("聊天类型的消息");JsonUtil.java是一个json的序列化和反序列化类
public static String toJson(Object obj) { return new Gson().toJson(obj); } public static Map<String, Object> toMap(Object obj) { return new Gson().fromJson(obj.toString(), new TypeToken<Map<String, Object>>() {}.getType()); }
相关推荐
在本项目中,我们利用WebSocket实现了一个简单的聊天室和单聊功能,使得用户可以实时地发送和接收消息。 首先,我们需要理解WebSocket的基本原理。WebSocket协议基于TCP,通过HTTP/1.1的Upgrade头来建立连接,一旦...
WebSocket是一种在客户端...总之,基于WebSocket的聊天室源码是一个结合了Java后端、前端JavaScript、HTML和CSS的综合项目,它展示了WebSocket如何实现实时通信,并为开发者提供了一个学习和实践WebSocket技术的实例。
本项目是一个基于WebSocket的网页聊天室,具备私聊和多对多聊天功能,适合初学者理解和实践网络通信的实时性。 在WebSocket技术中,服务器和客户端之间的通信始于一个HTTP升级请求,客户端通过发送一个"Upgrade...
## 开发软件:WebStorm Nodejs + WebSocket + Vue 网络聊天室 一个简单的聊天室demo,实现的功能有: 私聊 群聊 消息已读和未读 未读消息显示和提醒 聊天文字颜色区别 创建房间 用户下线提示
ChatAnnotation 类是服务器端的核心类,使用 Java EE 7 的 WebSocket API 实现聊天室的业务逻辑。ChatAnnotation 类使用注解的方式,提供了对 WebSocket 连接的管理和数据传输的支持。 五、聊天室实现细节 聊天室...
HTML5基于websocket的仿微信聊天,即时聊天IM前后端实现,简易聊天室源代码分享HTML5基于websocket的仿微信聊天,即时聊天IM前后端实现,简易聊天室源代码分享HTML5基于websocket的仿微信聊天,即时聊天IM前后端实现...
例如,创建一个简单的WebSocket服务器端点可能如下: ```java @ServerEndpoint("/chat") public class ChatServer { @OnOpen public void onOpen(Session session) { // 连接打开时的操作 } @OnMessage ...
在本文中,我们将探讨如何构建一个基于Vue.js和WebSocket的多人在线聊天室。Vue.js是一个流行的前端JavaScript框架,用于构建用户界面,而WebSocket则是一种在客户端和服务器之间提供全双工、低延迟通信的协议。结合...
本项目基于Spring平台,整合websocket协议,实现一个简易web聊天室的功能。主要特性如下: 1.包含聊天室登录、退出的功能。登录时,浏览器自动向服务器发起websocket连接,退出时自动切断。 2.登录后,用户可查看到...
在这个“基于WebSocket的web聊天室”项目中,我们将会探讨如何使用Java来实现一个实时的、交互式的聊天应用程序。 首先,我们需要理解WebSocket的工作原理。WebSocket协议基于TCP,它在HTTP握手之后建立连接,并...
以上就是使用Spring Boot集成WebSocket实现一个简单聊天室的基本步骤。当然,实际应用可能需要考虑更多细节,比如用户认证、错误处理、消息持久化等。这个示例旨在为你提供一个起点,帮助你理解如何在Spring Boot...
需要修改conn/conn.php的配置文件,还需要安装node.js环境,...基于websocket的即时聊天室网站源码 环境: php5.5 mysql node.js 教程视频下载: 链接:https://pan.baidu.com/s/1fjZw2L01-o74EYlw-OaT1A 提取码:3h2p
基于websocket的聊天室。可同时支持多人在线聊天。
在这个基于Java、HTML和Tomcat实现的匿名聊天室项目中,WebSocket起到了核心作用。 首先,我们要理解WebSocket的工作原理。WebSocket协议在TCP/IP层面上提供了一个新的独立的套接字类型(ws/wss),通过握手过程与...
在本“基于websocket和jetty8的聊天室demo”中,我们将探讨如何使用Jetty 8这个轻量级的Java Web服务器和Servlet容器,结合HTML5的WebSocket API来实现一个简单的聊天室功能。 首先,让我们了解Jetty 8。Jetty是一...
在Java中实现基于WebSocket的聊天室,我们需要掌握以下几个核心知识点: 1. **WebSocket协议**:WebSocket是HTML5定义的一种在单个TCP连接上进行全双工通信的协议。它提供了比HTTP更高效的数据传输方式,因为HTTP...
3. **聊天室逻辑**:在lualib目录中的Lua库,我们将实现聊天室的核心功能,如创建、加入和离开聊天室,以及发送和接收消息。这包括维护聊天室的会话状态,处理新用户的加入,以及广播消息到所有聊天室成员。 4. **...