上文中,我们已经基本了解了webscoket的原理以及部分api的实现,接下来我们就使用websocket来实现一个简单的聊天室功能。

1. 需求分析

1.1 登陆聊天室:

1.2 登陆成功后与别人进行聊天

  1. 李四界面:

  2. 张三界面:

  3. 在李四页面的好友列表中,点击张三,与之聊天:

  4. 在张三好友列表页,点击李四,打开对话框,可以接收到李四的消息:

  5. 互相发送:

2. 实现流程:

(下图中蓝色的@onError应该为@onClose)

3. 消息格式:

4. 导入相关jar包

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

5. 代码实现:

5.1 POJO类:

5.1.1 浏览器发送给服务器的webSocket数据:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Message {private String toName;private String message;private String fromName;
}
5.1.2 服务端给客户端发送的websocket数据:
@Data
@NoArgsConstructor
@AllArgsConstructor
//用户间传送的消息
public class ResultMessage {private boolean isSystem;private String fromName;//private String toName;private Object message;
}
5.1.3 用于登陆返回给浏览器的数据
@Data
@AllArgsConstructor
@NoArgsConstructor
//登录时用到的信息
public class Result {private boolean flag;private String message;
}

5.2 消息工具类:

//封装发送的消息内容
public class MessageUtils {public static String getMessage(boolean isSystemMessage,String fromName,Object message){try{ResultMessage resultMessage = new ResultMessage();resultMessage.setSystem(isSystemMessage);resultMessage.setMessage(message);if(fromName != null){resultMessage.setFromName(fromName);}
//            if(toName !=null ){//                resultMessage.setToName(toName);
//            }ObjectMapper mapper = new ObjectMapper();return mapper.writeValueAsString(resultMessage);}catch (JsonProcessingException e){e.printStackTrace();}return null;}
}

5.3 主要前端代码:

var toName;
var username;
//点击好友名称展示相关消息
function showChat(name){toName = name;//现在聊天框$("#content").html("");$("#content").css("visibility","visible");// 显示正在和谁聊天$("#Inchat").html("当前正与"+toName+"聊天");// 从sessionStorage中获取历史信息var chatData = sessionStorage.getItem(toName);if (chatData != null){// 将聊天记录渲染到聊天区$("#content").html(chatData);}
}
$(function () {$.ajax({url:"getUsername",success:function (res) {username = res;},async:false //同步请求,只有上面好了才会接着下面});//建立websocket连接//获取host解决后端获取httpsession的空指针异常var host = window.location.host;var ws = new WebSocket("ws://"+host+"/chat");ws.onopen = function (evt) {$("#username").html("<h3 style=\"text-align: center;\">用户:"+ username +"<span>-在线</span></h3>");}//接受消息ws.onmessage = function (evt) {//获取服务端推送的消息var dataStr = evt.data;//将dataStr转换为json对象var res = JSON.parse(dataStr);//判断是否是系统消息if(res.system){//系统消息//1.好友列表展示//2.系统广播的展示//此处声明的变量是调试时命名的,可以直接合并var names = res.message;var userlistStr = "";var broadcastListStr = "";var temp01 = "<a style=\"text-align: center; display: block;\" οnclick='showChat(\"";var temp03 = "\")'>";var temp04 = "</a></br>";var temp = "";for (var name of names){if (name != username){temp = temp01 + name + temp03 + name + temp04;userlistStr = userlistStr + temp;broadcastListStr += "<p style='text-align: center'>"+ name +"上线了</p>";}}//渲染好友列表和系统广播$("#hyList").html(userlistStr);$("#xtList").html(broadcastListStr);} else {//不是系统消息// 将服务器推送的消息进行展示var str = "<span id='mes_left'>"+ res.message +"</span></br>";if (toName === res.fromName) {$("#content").append(str);}var chatData = sessionStorage.getItem(res.fromName);if (chatData != null){str = chatData + str;}//保存聊天消息sessionStorage.setItem(res.fromName,str);};}ws.onclose = function () {$("#username").html("<h3 style=\"text-align: center;\">用户:"+ username +"<span>-离线</span></h3>");}// 给发送按钮绑定单机事件//发送消息$("#submit").click(function () {//1.获取输入的内容var data = $("#input_text").val();//2.清空发送框$("#input_text").val("");var json = {"toName": toName ,"message": data};//将自己发送的数据也展示在聊天区var str = "<span id='mes_right'>"+ data +"</span></br>";$("#content").append(str);// 将聊天记录存放到sessionStorage中var chatData = sessionStorage.getItem(toName);if (chatData != null){str = chatData + str;}// 存放键和值,和谁的聊天,聊天的内容是什么sessionStorage.setItem(toName,str);//3.发送数据ws.send(JSON.stringify(json));})
})

5.3 登陆及拦截器实现:

5.3.1 登陆接口:
@RestController
//模拟登录操作
@Slf4j
public class CertificationController {@RequestMapping("/toLogin")public Result toLogin(String user, String pwd, HttpSession httpSession){Result result = new Result();httpSession.setMaxInactiveInterval(30*60);log.info(user+"登录验证中..");if(httpSession.getAttribute("user") != null){result.setFlag(false);result.setMessage("不支持一个浏览器登录多个用户!");return result;}if ("张三".equals(user)&&"123".equals(pwd)){result.setFlag(true);log.info(user+"登录验证成功");httpSession.setAttribute("user",user);}else if ("李四".equals(user)&&"123".equals(pwd)){result.setFlag(true);log.info(user+"登录验证成功");httpSession.setAttribute("user",user);}else if ("田七".equals(user)&&"123".equals(pwd)){result.setFlag(true);log.info(user+"登录验证成功");httpSession.setAttribute("user",user);}else if ("王五".equals(user)&&"123".equals(pwd)){result.setFlag(true);log.info(user+"登录验证成功");httpSession.setAttribute("user",user);}else {result.setFlag(false);log.info(user+"验证失败");result.setMessage("登录失败");}return result;}@RequestMapping("/getUsername")public String getUsername(HttpSession httpSession){String username = (String) httpSession.getAttribute("user");return username;}
}
5.3.2 拦截器:
//配置拦截路径
@Configuration
public class MvcConfigurer implements WebMvcConfigurer {@Autowiredprivate UserInterceptor userInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(userInterceptor).addPathPatterns("/main");}
}@Component
@Slf4j
public class UserInterceptor implements HandlerInterceptor {//没用数据库,暂且用集合来存储已登录等用户,进行拦截。public static Map<String, String> onLineUsers = new ConcurrentHashMap<>();;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {HttpSession httpSession = request.getSession();String username = (String) httpSession.getAttribute("user");log.info("进入拦截器"+"==="+"进入拦截器的用户是:"+username);if(username != null && !onLineUsers.containsKey(username)){onLineUsers.put(username,username);log.info("已进入拦截器判断");log.info("已存储的用户01"+onLineUsers);return true;}else {log.info("已存储的用户02" + onLineUsers);log.info("未进入判断,进行重定向");httpSession.removeAttribute("user");response.sendRedirect("/loginerror");return false;}}
}

5.4 WebSocketConfig配置类:

作用:使使用了@ServerEndpoint注解的bean注册到spring中

@Configuration
//websocket要做的配置类
public class WebSocketConfig {// 注入ServerEndpointExporter bean对象,可以自动注册使用了@ServerEndpoint注解的bean@Beanpublic ServerEndpointExporter serverEndpointExporter(){return new ServerEndpointExporter();}
}

5.5 从request中获取httpsession对象

//@Configuration
public class GetHttpSessionConfigurator extends ServerEndpointConfig.Configurator {//此方法用来获取httpssion对象@Overridepublic void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {HttpSession httpSession = (HttpSession) request.getHttpSession();if(httpSession != null) {sec.getUserProperties().put(HttpSession.class.getName(), httpSession);}}
}

5.6 聊天室方法:

@Slf4j
// 声明资源路径chat, 让之前编写的插入HttpSession到config的配置文件生效
@ServerEndpoint(value = "/chat", configurator = GetHttpSessionConfigurator.class)
// 交给spring处理,但是还没完,spring并不会创建这个类的对象,需要加上spring对endpoint支持,
// 我们可以添加一个configuration的配置类,注入一个ServerEndpointExporter的bean对象,可以自动注册使用了@ServerEndpoint注解的bean
@Component
public class ChatEndPoint {//用线程安全的map来保存每个对象对应的EndPoint对象private static Map<String, ChatEndPoint> onLineUsers = new ConcurrentHashMap<>();//声明一个session对象,通过该对象可以发送消息给指定用户,不能设置为静态,每个ChatEndPoint有一个session才能区分.(websocket的session)private Session session;//保存当前登录浏览器的用户private HttpSession httpSession;//建立连接时发送系统广播@OnOpenpublic void onOpen(Session session, EndpointConfig config) {// 将局部的session对象赋值给成员session// 因为websocket涉及到多例,也就是多个线程调用此方法,所以使用this关键字来确保是同一个客户端在操作this.session = session;HttpSession httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());this.httpSession = httpSession;String username = (String) httpSession.getAttribute("user");log.info("上线用户名称:" + username);// 将当前成功登陆的用户存储到容器中onLineUsers.put(username, this);// 将当前在线用户的用户名推送给所有的客户端// 1. 获取消息String message = MessageUtils.getMessage(true, null, getNames());// 2. 调用方法进行消息的推送broadcastAllUsers(message);}//获取当前登录的用户private Set<String> getNames() {return onLineUsers.keySet();}//发送系统消息private void broadcastAllUsers(String message) {try {// 需要将消息推送给所有在线用户Set<String> names = onLineUsers.keySet();for (String name : names) {ChatEndPoint chatEndPoint = onLineUsers.get(name);chatEndPoint.session.getBasicRemote().sendText(message);}} catch (Exception e) {e.printStackTrace();}}//用户之间的信息发送@OnMessage// 接收到客户端发送的数据时调用public void onMessage(String message, Session session) {try {// 将string类型的message转换为message对象ObjectMapper mapper = new ObjectMapper();Message mess = mapper.readValue(message, Message.class);// 找到服务器接收到的消息的客户端接收方(也就是找出消息是发送给谁的,消息接收方)String toName = mess.getToName();// 获取消息数据String data = mess.getMessage();// 获取当前当前登陆用户(消息发送方)String username = (String) httpSession.getAttribute("user");log.info(username + "向" + toName + "发送的消息:" + data);// 格式化需要发送的消息和接收人String resultMessage = MessageUtils.getMessage(false, username, data);if (StringUtils.hasLength(toName)) {// 发送数据onLineUsers.get(toName).session.getBasicRemote().sendText(resultMessage);}} catch (Exception e) {e.printStackTrace();}}//用户断开连接的断后操作@OnClosepublic void onClose(Session session) {String username = (String) httpSession.getAttribute("user");log.info("离线用户:" + username);if (username != null) {onLineUsers.remove(username);UserInterceptor.onLineUsers.remove(username);}httpSession.removeAttribute("user");String message = MessageUtils.getMessage(true, null, getNames());broadcastAllUsers(message);}
}

至此,使用原生websocket实现一个聊天窗完成,接下来,将使用websocket+stomp实现群聊的功能

WebSocket(二) -- 使用原生webSocket实现一个简单的聊天相关推荐

  1. 创建一个带参数的formgoup_基于原生Fabric-SDK-Go 实现一个简单的学历征信系统,状态数据库使用 CouchDB 来实现...

    [TOC] 1. 需求分析与架构设计 我们要基于 原生Fabric-SDK-Go 实现一个简单的学历征信系统(web项目),状态数据库使用 CouchDB 来实现. 1.1 需求分析 现在是一个信息化 ...

  2. 原生JS实现一个简单的打字小游戏

    原生JS实现一个简单的打字小游戏 利用javascript制作一个简单的打字小游戏 之前写了一个贪吃蛇小游戏好像反响不错 今天我来写一个比贪吃蛇更low更简单的打字小游戏 打字小游戏原理 接下来咋们直 ...

  3. C语言能干什么?手把手教你写一个简单的聊天软件

    一.服务端代码 因为端口号容易被占用的原因,所以IP地址和端口号采用参数传递的方法,即 int main(int argc,char **argv) 1.头文件 #include <stdio. ...

  4. Android(安卓)一个简单的聊天界面的实现(eclipse实现)

    这几天刚刚学习一下安卓的编程,尝试制作了一个简单的聊天界面(还没有实现网络等后续功能)软件界面如图.(使用eclipse实现) 当输入一些内容后,聊天界面可以下拉显示更多的聊天信息,如下图 首先对这个 ...

  5. 通过python 构建一个简单的聊天服务器

    构建一个 Python 聊天服务器 一个简单的聊天服务器 现在您已经了解了 Python 中基本的网络 API:接下来可以在一个简单的应用程序中应用这些知识了.在本节中,将构建一个简单的聊天服务器.使 ...

  6. 通信软件基础B-重庆邮电大学-Java-编程实现一个简单的聊天程序-多线程编程实现

    实验任务六 编程实现一个简单的聊天程序-多线程编程实现 1. 系统设计要求 编程实现一个简单的聊天程序,实现两台计算机间的信息交互,使用多线程编程实现:可同时连接多个客户端,服务器收到客户端发送的消息 ...

  7. 用ServletContext做一个简单的聊天室

    这里主要是ServletContext的一个特性:ServletContext是一个公共的空间,可以被所有的客户访问.由此可见ServletContext比cookie和session的作用范围要大[ ...

  8. Netty - 一个简单的聊天室小项目

     经过一段时间对Netty的学习,我们对Netty各版本以及像ProtocolBuffers等技术应用都有了不少相关的了解, 我们就用这段时间学到的只是做一个简单的聊天室的小项目来练习自己学到的技术. ...

  9. python开发一个简单的聊天室

    使用python的twisted框架编写一个简单的聊天室 下面是基本架构 基本架构图 -- coding:utf-8 -- from twisted.internet.protocol import ...

最新文章

  1. VUE v-if 和 v-for 的使用示例 VUE根据下标改变图片路径
  2. matlab示波器模拟,声卡虚拟示波器-使用matlab DAQ工具箱中API实现
  3. [Python 多线程] Semaphore、BounedeSemaphore (十二)
  4. 存储基础知识 - 磁盘寻址(CHS寻址方式、LBA寻址方式)
  5. DNF包管理命令在CentOS 8和RHEL 8上的使用
  6. 新手必看!电脑文件管理八条小技巧
  7. Java 自动化测试工具Selenium
  8. C语言:cJSON库用法详解
  9. 水果销售管理系统课程设计报告
  10. 企业OA办公系统选型技巧实用指南
  11. python使用126发邮件代码
  12. 商业智能bi是什么意思?
  13. 谷歌提出Flan-T5,一个模型解决所有NLP任务
  14. javascript 生成汉字和拼音对照
  15. Android适配阿拉伯语、波斯语语系
  16. 【图像处理】获取图片像素点
  17. 麻省理工学院计算机博士毕业,努力比起点更重要!有高职院校学生,拿下麻省理工大学博士offer...
  18. urllib库(二)parse模块:urlparse()/urlsplit(),parse_qs()/parse_qsl(),urlunparse()/urlunsplit(),urlencode()
  19. 搜索工具 Everything 的简单设置
  20. [渗透攻防] 三.数据库之差异备份及Caidao利器

热门文章

  1. GOOGLE搜索秘籍
  2. 利用python批量合并手机哔哩哔哩下载的视频各分段
  3. CentOS联网(图解超详细)
  4. 人在旅途——》张家界之旅:20180419
  5. STC89C52RC - 12 - 静、动数码管显示
  6. webpack高级运用,historyApiFallback(解决history,h5路由,方式刷新页面可能会报错),output中添加publicPath(公共路径)任意找不到的路径都会去访问/等等
  7. http://www.189qq.cn/soft?54563.htm
  8. 美团招聘视觉算法实习生
  9. 第八场多校联盟 Problem A: 序号互换 【模拟】
  10. 谷歌网页翻译失效解决方法