基于websocket的简易聊天室的实现
来源:互联网 发布:淘宝积分记录怎么删除 编辑:程序博客网 时间:2024/04/30 15:45
用java实现基于websocket的简易聊天室
WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。一开始的握手需要借助HTTP请求完成。
———— 引自 百度百科
简介:传统的使用http协议的web应用在实时通信方面存在很大的局限性,由于服务器只能等客户端浏览器主动发出request才能响应一个response,使客户端总要向服务器发出请求才能得到想要的消息(也可以用长连接让客户端hold住这个请求等类似的方式,但是这些方法都会占用比较多的服务器资源),随着html5到来的websocket使web应用的能更方便的实现实时通信。websocket和http协议一样也是基于tcp/ip协议的应用层的协议,它只需在第一次和服务器端连接是借用http协议与服务器端握手,然后就从http协议切换成websocket协议(我们可以把这个过程看做是http升级成了webstocket),关于握手可以参照这篇博文:http://blog.csdn.net/edwingu/article/details/44040961,本文不详细地介绍WebSocket规范,主要介绍下WebSocket在Java Web中的实现。
websocket有如此强大的功能,作为在web领域数一数二的java当然也提供了实现方法,JavaEE 7中出了JSR-356:Java API for WebSocket规范。不少Web容器,如Tomcat,Nginx,Jetty等都支持WebSocket。Tomcat从7.0.27开始支持WebSocket,从7.0.47开始支持JSR-356,下面我们所要写的简易聊天室也是需要部署在Tomcat7.0.47以上的版本才能运行。
1、项目演示 : 用两个浏览器模拟多个用户通信
1.1 创建项目,在这里我用maven和springMVC搭建的,不用也一样源码会全部给出,下方附上项目项目源码下载
1.2 后台代码 User.java,这个没什么讲的
package com.chat.pojo;/** * 用户类 * @author chenxin * */public class User {private Integer id;private String name;private String password;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}@Overridepublic String toString() {return "User [id=" + id + ", name=" + name + ", password=" + password+ "]";}}
1.3 UserController.java ,控制层
package com.chat.controller;import java.util.Collection;import java.util.List;import java.util.concurrent.CopyOnWriteArrayList;import javax.servlet.http.HttpServletRequest;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.ResponseBody;import com.chat.pojo.User;/** * 用户登录 * 主要是学习websocket,数据库和权限判断就不写了 * @author chenxin * */@Controller@RequestMapping("/user")public class UserController {//分配user的id,需设计为线程安全的private static int count=1;//在线用户列表,需设计成线程安全的private static List<User> userList = new CopyOnWriteArrayList<User>();/** * 跳转到登陆页面 * @return */@RequestMapping("/tologin")public String toregister(){return "login";}/** * 登陆 * @param user * @param request * @return */@RequestMapping("/login")public String login(User user,HttpServletRequest request){//生成iduser.setId(count);//id增长UserController.increase();//把用户存入session域中request.getSession().setAttribute("user", user);//把登陆用户传入用户列表中userList.add(user);return "index";}/** * 得到在线人数及用户名 * @param request * @return */@RequestMapping("/getAll")public @ResponseBody Collection<User> getAllUser(HttpServletRequest request){return UserController.userList;}/** * 下线,当页面关闭时前台页面通过ajax调用该handler * @return */@RequestMapping("/downLine")public void downLine(HttpServletRequest request){//得到session中的userUser user = (User)request.getSession().getAttribute("user");//遍历用户列表,删除自己for(User item:userList){if(user.getId()==item.getId())userList.remove(item);}}//用来id增长private static synchronized void increase(){ UserController.count++;}}
1.4 WebsocketChat.java 整个小项目的核心代码,websocket连接有该类接受建立
package com.ssm.websocket;import java.io.IOException;import java.text.SimpleDateFormat;import java.util.Date;import java.util.concurrent.CopyOnWriteArraySet;import javax.servlet.http.HttpSession;import javax.websocket.*;import javax.websocket.server.ServerEndpoint;import com.chat.pojo.User;/** * @ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端, * 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端 * 由于此类不是servlet类,要通过另一种方法才能的到HttpSession * configurator 属性 通过GetHttpSessionConfigurator类得到httpsession */@ServerEndpoint(value="/websocket" ,<span style="color:#FF0000;">configurator=GetHttpSessionConfigurator.class</span>)//得到httpSessionpublic class WebSocketChat { //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。 private static int onlineCount = 0; //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识 private static CopyOnWriteArraySet<WebSocketChat> webSocketSet = new CopyOnWriteArraySet<WebSocketChat>(); //与某个客户端的连接会话,需要通过它来给客户端发送数据 private Session session; //当前会话的httpession private HttpSession httpSession; /** * 连接建立成功调用的方法 * @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据 * @param config 有 */ @OnOpen public void onOpen(Session session,EndpointConfig config){ //得到httpSession this.httpSession = (HttpSession) config.getUserProperties() .get(HttpSession.class.getName()); this.session = session; webSocketSet.add(this); //加入set中 addOnlineCount(); //在线数加1 System.out.println("有新连接加入!当前在线人数为" + getOnlineCount()); } /** * 连接关闭调用的方法 */ @OnClose public void onClose(){ webSocketSet.remove(this); //从set中删除 subOnlineCount(); //在线数减1 System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount()); } /** * 收到客户端消息后调用的方法 * @param message 客户端发送过来的消息 * @param session 可选的参数 */ @OnMessage public void onMessage(String message, Session session) { //获得消息发来时间 SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String time = sd.format(new Date()); System.out.println("来自客户端的消息:" + message); //替换表情 message = replaceImage(message); //得到当前用户名 User user = (User)this.httpSession.getAttribute("user"); String name = user.getName(); //群发消息 for(WebSocketChat item: webSocketSet){ try { item.sendMessage(name+" "+time+"</br>"+message); } catch (IOException e) { e.printStackTrace(); continue; } } } /** * 发生错误时调用 * @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) throws IOException{ this.session.getBasicRemote().sendText(message); //this.session.getAsyncRemote().sendText(message); } public void sendMessage(String message,String userid) throws IOException{ } //对count的加减获取同步 public static synchronized int getOnlineCount() { return onlineCount; } public static synchronized void addOnlineCount() { WebSocketChat.onlineCount++; } public static synchronized void subOnlineCount() { WebSocketChat.onlineCount--; } //替换表情 private String replaceImage(String message){ for(int i=1;i<11;i++){ if(message.contains("<:"+i+":>")){ message = message.replace("<:"+i+":>", "<img " + "src='/chat/Images/" + i + ".gif' id='" + i + "' />"); } } return message; }}
1.5 GetHttpSessionConfigurator.java
package com.ssm.websocket;import javax.servlet.http.HttpSession;import javax.websocket.HandshakeResponse;import javax.websocket.server.HandshakeRequest;import javax.websocket.server.ServerEndpointConfig;import javax.websocket.server.ServerEndpointConfig.Configurator;/* * 获取HttpSession的配置类 * 只要在websocket类上@ServerEndpoint注解中加入configurator=GetHttpSessionConfigurator.class * 再在 @OnOpen注解的方法里加入参EndpointConfig config * 通过config得到httpSession */public class GetHttpSessionConfigurator extends Configurator { @Override public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) { // TODO Auto-generated method stub HttpSession httpSession=(HttpSession) request.getHttpSession(); sec.getUserProperties().put(HttpSession.class.getName(),httpSession); } }2. 前台页面及js
2.1 login.jsp
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%><html><head> <title>用户登录</title> <link rel="stylesheet" href="${pageContext.request.contextPath}/Css/login.css" /> <script src="${pageContext.request.contextPath}/Js/jquery-1.8.2.min.js"></script> <script src="${pageContext.request.contextPath}/Js/login.js"></script></head><body> <div id="login"> <div id="login-t">用户登录</div> <form action="${pageContext.request.contextPath}/user/login" method="post"> <div id="login-main"> <div id="login-con"> <div class="login-item"> 用户:<input id="txtName" name="name" type="text" /> </div> <div class="login-item"> 密码:<input id="txtPwd" name="password" type="password" /> </div> <div id="login-btn"> <input id="btnLogin" type="submit" value="登录" /> <input id="btnCancel" type="reset" value="取消" /> </div> </div> </div> </form>快速登陆,无需注册 </div> <div id="login-msg"></div></body></html>
2.2 index.jsp ,发起websocket请求放在了chat.js文件中,页面上的js代码是为了方便写路径,显示在线人数和用户下线的ajax方法
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%><html><head> <title>聊天室</title> <link rel="stylesheet" href="${pageContext.request.contextPath}/Css/chat.css" /> <script src="${pageContext.request.contextPath}/Js/jquery-1.8.2.min.js"></script> <script src="${pageContext.request.contextPath}/Js/chat.js"></script> <script type="text/javascript"> //获取当前用户 function getUser(){ $("#chat-user-con ul").html(""); $.post("${pageContext.request.contextPath}/user/getAll",{}, function(data){ var temp; for(temp=0;temp<data.length;temp++){ $("#chat-user-con ul").append("<li>"+data[temp].name+"</li>"); } },"json"); } //下线 function downLine(){ $.post("${pageContext.request.contextPath}/user/downLine",{}, function(){}); }</script></head><body><span id="message"></span> <div id="chat"> <div id="chat-top"> <div id="chat-dialog"> <div id="chat-dialog-t">聊天室</div> <div id="chat-dialog-con"> <ul> </ul> </div> </div> <div id="chat-user"> <div id="chat-user-t">当前在线用户</div> <div id="chat-user-con"> <ul> </ul> </div> </div> </div> <div id="chat-bottom"> <div id="chat-input"> <div id="chat-input-expr"> <!--<img src="Images/1.gif" id="1" /><img src="Images/2.gif" id="2" /><img src="Images/3.gif" id="3" /><img src="Images/4.gif" id="4" /><img src="Images/5.gif" id="5" /><img src="Images/6.gif" id="6" /><img src="Images/7.gif" id="7" /><img src="Images/8.gif" id="8" /><img src="Images/9.gif" id="9" /><img src="Images/10.gif" id="10" />--> </div> <div id="chat-input-edit"> <div id="input-field"> <textarea id="txtInput"></textarea> </div> <div id="input-btn"> <input id="btnSend" type="button" value="发送" /> </div> </div> <div id="chat-input-tip">发送内容不能为空</div> </div> </div> </div> <div id="chat-msg"></div></body></html>2.3 chat.js 里面是前台发送websocket请求的核心代码
$(function () { var websocket = null; //判断当前浏览器是否支持WebSocket if ('WebSocket' in window) { websocket = new WebSocket("ws://localhost:8080/chat/websocket"); } else { alert('当前浏览器版本过低不支持websocket!!!') } //连接发生错误的回调方法 websocket.onerror = function () { setMessageInnerHTML("WebSocket连接发生错误"); } //连接成功建立的回调方法 websocket.onopen = function () { setMessageInnerHTML("WebSocket连接成功"); } //接收到消息的回调方法 websocket.onmessage = function (event) { showMessageInnerHTML(event.data); } //连接关闭的回调方法 websocket.onclose = function () { setMessageInnerHTML("WebSocket连接关闭"); } //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。 //同时退出聊天室(下线) window.onbeforeunload = function () { closeWebSocket(); downLine(); } //将是否连接websocket消息显示在网页上 function setMessageInnerHTML(innerHTML) { $("#message").append(innerHTML+"</br>"); } //将消息显示在页面上 function showMessageInnerHTML(innerHTML){// document.getElementById('chat-dialog-con').innerHTML += innerHTML + '<br/>'; $("#chat-dialog-con ul").append("<li>"+innerHTML+"</li>"); } //关闭WebSocket连接 function closeWebSocket() { websocket.close(); } //发送消息 function send() { var message = $("#txtInput").val(); websocket.send(message); } //添加表情 for (var i = 1; i <= 10; i++) { $("#chat-input-expr").html($("#chat-input-expr").html() + "<img src='/chat/Images/" + i + ".gif' id='" + i + "' />"); //html和text方法不一样,前者可添加html标签和纯文本,后者只可添加纯文本。 } //向消息中添加表情 $("#chat-input-expr img").click(function () { $("#txtInput").val($("#txtInput").val() + "<:" + $(this).attr("ID") + ":>"); }); //6.发送消息判断 $("#btnSend").click(function () { var sendMsg = $("#txtInput"); if (sendMsg.val() != "") { send(); sendMsg.val("") } else { alert("发送内容不能为空!"); sendMsg.focus(); return false; } }); //得到在线用户 getUser(); setInterval("getUser()",3000);});以上为实现聊天室功能的代码,还有一些css样式在下面项目源码中给出
项目源码:http://download.csdn.net/detail/aaa5438438/9589077
转载请注明出处:http://blog.csdn.net/aaa5438438
- 基于websocket的简易聊天室的实现
- 基于WebSocket的简易聊天室
- 基于spring4 websocket的简易聊天室
- 基于websocket和swoole的简易聊天室
- php基于websocket实现的在线聊天室
- WebSocket聊天室的实现
- Golang实现基于Websocket协议的H5聊天室
- Golang实现基于Websocket协议的H5聊天室(上)
- websocket实现简单的聊天室
- 实现一个无刷新的基于ajax的简易聊天室
- 实现一个无刷新的基于ajax的简易聊天室
- 实现一个无刷新的基于ajax的简易聊天室
- 实现一个无刷新的基于ajax的简易聊天室
- 实现一个无刷新的基于ajax的简易聊天室
- 实现一个无刷新的基于ajax的简易聊天室
- 实现一个无刷新的基于ajax的简易聊天室
- 实现一个无刷新的基于ajax的简易聊天室
- C 基于UDP实现一个简易的聊天室
- binary search二分检索(查找)
- [Kubernetes]Docker的网络模型
- 乐观锁与悲观锁
- C++ typedef用法小结
- 网络流24题——软件补丁问题(spfa+位运算)
- 基于websocket的简易聊天室的实现
- mysql 数据库引擎
- 关于adb在linux下的常用命令
- Menu: 只能通过隐藏和显示控制,而不能物理增减项目
- 使用GitHub
- 谷歌浏览器调试快捷键
- 软件测试行业悲观走冷,“让天下没有难做的工程效能”是否一支强心剂
- Promise of iOS
- 懒加载jquery.lazyload.js