2000字范文,分享全网优秀范文,学习好帮手!
2000字范文 > 习惯了微信聊天 利用WebSocket手动实现个聊天功能怎么样?

习惯了微信聊天 利用WebSocket手动实现个聊天功能怎么样?

时间:2023-02-14 22:14:39

相关推荐

习惯了微信聊天 利用WebSocket手动实现个聊天功能怎么样?

1.背景

基于项目需求,最近需要实现一个简单的聊天功能。日常生活中,大家对于聊天也习以为常,微信、QQ等软件也经常用到,其实我们也可以引入一些第三方的sdk包等去实现,也可以利用WebSocket通信协议去手动实现简单的聊天。本文主要讲述下WebSocket实现的具基于项目需求,最近需要实现一个简单的聊天功能。日常生活中,大家对于聊天也习以为常,微信、QQ等软件也经常用到,其实我们也可以引入一些第三方的sdk包等去实现,也可以利用WebSocket通信协议去手动实现简单的聊天。本文主要讲述下WebSocket实现的具体步骤及实现的效果图。体步骤及实现的效果图。

2.方案选型及优缺点介绍

方案一利用http接口手动实现三个接口:sengMsg(消息发送)、receiveMsg(消息接收)、getHistoryMsg(获取历史消息) ,然后前端发送消息时调用sendMsg接口,将数据写入数据库以便获取历史消息使用,接收消息时前端声明一个定时器,每一秒钟去刷新消息接收接口,来获取消息内容显示到聊天框中,最后,如果用户需要翻看历史消息,调用getHistoryMsg接口即可。优点后端实现简单,且能将聊天消息持久化到数据库永久保存,可以根据聊天室id随时获取消息内容缺点由于频繁调用接口,服务器和api接口压力比较大,高并发情况下服务器可能会宕机,而且不进行消息发送时,由于定时器的使用,前端频繁请求会造成空跑,显然不太合理方案二利用已有的WebSocket服务实现聊天功能优点不用额外自己实现接口,直接按照WebSocket定义的规则直接套用即可缺点消息没有持久化,如果服务宕机,可能无法查看历史消息

3.服务搭建及实现

3.1 引入依赖

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

3.2 声明socket配置类

@Configurationpublic class WebSocketConfig {//注入一个ServerEndpointExporter@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}}

3.3 声明聊天Controller

/*** 聊天控制器* @ServerEndpoint("/chat/{userId}")中的userId是前端创建会话窗口时当前用户的id,即消息发送者的id*/@ServerEndpoint("/chat/{userId}")@Componentpublic class ChatWebSocketController {private final Logger logger = Logger.getLogger(ChatWebSocketController.class);//onlineCount:在线连接数private static AtomicInteger onlineCount = new AtomicInteger(0);//webSocketSet:用来存放每个客户端对应的MyWebSocket对象。public static List<ChatWebSocketController> webSocketSet = new ArrayList<>();//存放所有连接人信息public static List<String> userList = new ArrayList<>();//与某个客户端的连接会话,需要通过它来给客户端发送数据private Session session;//用户IDpublic String userId = "";/*** 连接建立成功调用的方法*/@OnOpenpublic void onOpen(Session session, @PathParam("userId") String userId) {this.session = session;this.userId = userId;this.userList.add(userId) ;//加入set中webSocketSet.add(this);//在线数加1onlineCount.incrementAndGet();logger.info("有新连接加入!" + userId + "当前在线用户数为" + onlineCount.get());JSONObject msg = new JSONObject();try {msg.put("msg", "连接成功");msg.put("status", "SUCCESS");msg.put("userId", userId);sendMessage(JSON.toJSONString(msg));} catch (Exception e) {logger.debug("IO异常");}}/*** 连接关闭调用的方法*/@OnClosepublic void onClose(@PathParam("userId") String userId ) {//从set中删除webSocketSet.remove(this);onlineCount.decrementAndGet(); // 在线数减1logger.info("用户"+ userId +"退出聊天!当前在线用户数为" + onlineCount.get());}/*** 收到客户端消息后调用的方法** @param message 客户端发送过来的消息*/@OnMessagepublic void onMessage(String message, @PathParam("userId") String userId ) {//客户端输入的消息message要经过处理后封装成新的message,后端拿到新的消息后进行数据解析,然后判断是群发还是单发,并调用对应的方法logger.info("来自客户端" + userId + "的消息:" + message);try {MyMessage myMessage = JSON.parseObject(message, MyMessage.class);String messageContent = myMessage.getMessage();//messageContent:真正的消息内容String messageType = myMessage.getMessageType();if("1".equals(messageType)){ //单聊String recUser = myMessage.getUserId();//recUser:消息接收者sendInfo(messageContent,recUser,userId);//messageContent:输入框实际内容 recUser:消息接收者 userId 消息发送者}else{ //群聊sendGroupInfo(messageContent,userId);//messageContent:输入框实际内容 userId 消息发送者}} catch (Exception e) {logger.error("解析失败:{}", e);}}/*** 发生错误时调用的方法** @OnError**/@OnErrorpublic void onError(Throwable error) {logger.debug("Websocket 发生错误");error.printStackTrace();}public synchronized void sendMessage(String message) {this.session.getAsyncRemote().sendText(message);}/*** 单聊* message : 消息内容,输入的实际内容,不是拼接后的内容* recUser : 消息接收者* sendUser : 消息发送者*/public void sendInfo( String message , String recUser,String sendUser) {JSONObject msgObject = new JSONObject();//msgObject 包含发送者信息的消息for (ChatWebSocketController item : webSocketSet) {if (StringUtil.equals(item.userId, recUser)) {logger.info("给用户" + recUser + "传递消息:" + message);//拼接返回的消息,除了输入的实际内容,还要包含发送者信息msgObject.put("message",message);msgObject.put("sendUser",sendUser);item.sendMessage(JSON.toJSONString(msgObject));}}}/*** 群聊* message : 消息内容,输入的实际内容,不是拼接后的内容* sendUser : 消息发送者*/public void sendGroupInfo(String message,String sendUser) {JSONObject msgObject = new JSONObject();//msgObject 包含发送者信息的消息if (StringUtil.isNotEmpty(webSocketSet)) {for (ChatWebSocketController item : webSocketSet) {if(!StringUtil.equals(item.userId, sendUser)) { //排除给发送者自身回送消息,如果不是自己就回送logger.info("回送消息:" + message);//拼接返回的消息,除了输入的实际内容,还要包含发送者信息msgObject.put("message",message);msgObject.put("sendUser",sendUser);item.sendMessage(JSON.toJSONString(msgObject));}}}}/*** Map/Set的key为自定义对象时,必须重写hashCode和equals。* 关于hashCode和equals的处理,遵循如下规则:* 1)只要重写equals,就必须重写hashCode。* 2)因为Set存储的是不重复的对象,依据hashCode和equals进行判断,所以Set存储的对象必须重写这两个方法。* 3)如果自定义对象做为Map的键,那么必须重写hashCode和equals。** @param o* @return*/@Overridepublic boolean equals(Object o) {if (this == o) {return true;}if (o == null || getClass() != o.getClass()) {return false;}ChatWebSocketController that = (ChatWebSocketController) o;return Objects.equals(session, that.session);}@Overridepublic int hashCode() {return Objects.hash(session);}}

3.4 声明Controller中的MyMessage实体类

public class MyMessage implements Serializable {private static final long serialVersionUID = 1L;private String userId;private String message;//消息内容private String messageType;//消息类型 1 代表单聊 2 代表群聊public String getUserId() {return userId;}public void setUserId(String userId) {this.userId = userId;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}public String getMessageType() {return messageType;}public void setMessageType(String messageType) {this.messageType = messageType;}}

3.5 声明Controller中的StringUtil工具类

public final class StringUtil {/*** 对象为空** @param object* @return*/public static boolean isEmpty(Object object) {if (object == null) {return true;}if (object instanceof String && "".equals(((String) object).trim())) {return true;}if (object instanceof List && ((List) object).size() == 0) {return true;}if (object instanceof Map && ((Map) object).isEmpty()) {return true;}if (object instanceof CharSequence && ((CharSequence) object).length() == 0) {return true;}if (object instanceof Arrays && (Array.getLength(object) == 0)) {return true;}return false;}/*** 对象不为空** @param object* @return*/public static boolean isNotEmpty(Object object) {return !isEmpty(object);}/*** 查询字符串中某个字符首次出现的位置 从1计数** @param string 字符串* @param c* @return*/public static int strFirstIndex(String c, String string) {Matcher matcher = pile(c).matcher(string);if (matcher.find()) {return matcher.start() + 1;} else {return -1;}}/*** 两个对象是否相等** @param obj1* @param obj2* @return*/public static boolean equals(Object obj1, Object obj2) {if (obj1 instanceof String && obj2 instanceof String) {obj1 = ((String) obj1).replace("\\*", "");obj2 = ((String) obj2).replaceAll("\\*", "");if (obj1.equals(obj2) || obj1 == obj2) {return true;}}if (obj1.equals(obj2) || obj1 == obj2) {return true;}return false;}/*** 根据字节截取内容** @param bytes 自定义字节数组* @param content 需要截取的内容* @return*/public static String[] separatorByBytes(double[] bytes, String content) {String[] contentArray = new String[bytes.length];double[] array = new double[bytes.length + 1];array[0] = 0;//复制数组System.arraycopy(bytes, 0, array, 1, bytes.length);for (int i = 0; i < bytes.length; i++) {content = content.substring((int) (array[i] * 2));contentArray[i] = content;}String[] strings = new String[bytes.length];for (int i = 0; i < contentArray.length; i++) {strings[i] = contentArray[i].substring(0, (int) (bytes[i] * 2));}return strings;}/*** 获取指定字符串出现的次数** @param srcText 源字符串* @param findText 要查找的字符串* @return*/public static int appearNumber(String srcText, String findText) {int count = 0;Pattern p = pile(findText);Matcher m = p.matcher(srcText);while (m.find()) {count++;}return count;}/*** 将字符串str每隔2个分割存入数组** @param str* @return*/public static String[] setStr(String str) {int m = str.length() / 2;if (m * 2 < str.length()) {m++;}String[] strings = new String[m];int j = 0;for (int i = 0; i < str.length(); i++) {if (i % 2 == 0) {//每隔两个strings[j] = "" + str.charAt(i);} else {strings[j] = strings[j] + str.charAt(i);j++;}}return strings;}/*** 定义一个StringBuffer,利用StringBuffer类中的reverse()方法直接倒序输出* 倒叙字符串** @param s*/public static String reverseString2(String s) {if (s.length() > 0) {StringBuffer buffer = new StringBuffer(s);return buffer.reverse().toString();} else {return "";}}/*** 截取字符串中的所有日期时间** @param str* @return*/public static List<String> dateTimeSubAll(String str) {try {List<String> dateTimeStrList = new ArrayList<>();String regex = "[0-9]{4}[-][0-9]{1,2}[-][0-9]{1,2}[ ][0-9]{1,2}[:][0-9]{1,2}[:][0-9]{1,2}";Pattern pattern = compile(regex);Matcher matcher = pattern.matcher(str);while (matcher.find()) {String group = matcher.group();dateTimeStrList.add(group);}return dateTimeStrList;} catch (Exception e) {e.getMessage();return null;}}/*** 截取字符串中的所有日期** @param str* @return*/public static List<String> dateSubAll(String str) {try {List<String> dateStrList = new ArrayList<>();Pattern pattern = compile("[0-9]{4}[-][0-9]{1,2}[-][0-9]{1,2}");Matcher matcher = pattern.matcher(str);while (matcher.find()) {String group = matcher.group();dateStrList.add(group);}return dateStrList;} catch (Exception e) {e.getMessage();return null;}}/*** 获取随机字符串** @param length* @return*/public static String getRandomString(int length) {String base = "abcdefghijklmnopqrstuvwxyz0123456789";Random random = new Random();StringBuffer sb = new StringBuffer();for (int i = 0; i < length; i++) {int number = random.nextInt(base.length());sb.append(base.charAt(number));}return sb.toString();}}

3.6 后台声明测试的html页面

<!DOCTYPE HTML><html><head><title>WebSocket Chat Demo</title></head><body><input id="inputContent" type="text" style="width:600px;"/><button onclick="send()">Send</button><button onclick="closeConnection()">Close</button><div id="msg"></div></body><script type="text/javascript">var websocket = null;//声明自己搭建的websocket服务if ('WebSocket' in window) {var random = parseInt(Math.random() * 1000000) + "";websocket = new WebSocket("ws://localhost:8005/chat/"+ random);} else {alert('Not support websocket')}//连接发生错误的回调方法websocket.onerror = function() {setMessageInnerHTML("error");};//连接成功建立的回调方法websocket.onopen = function(event) {//setMessageInnerHTML("open");}//接收到消息的回调方法websocket.onmessage = function(event) {setMessageInnerHTML(event.data);}//连接关闭的回调方法websocket.onclose = function() {setMessageInnerHTML("close");}//监听窗口关闭事件,当窗口关闭时关闭对应websocket连接window.onbeforeunload = function() {websocket.close();}//将消息回显在页面上function setMessageInnerHTML(innerHTML) {document.getElementById('msg').innerHTML += innerHTML + '<br/>';}//关闭连接function closeConnection() {websocket.close();}//发送消息function send() {var msg = document.getElementById('inputContent').value;websocket.send(msg);}</script></html>

该类对应的路径如下:

4.启动服务并测试

页面输入ip+端口建立websocket连接并发送一条消息,测试结果如图:

注意

注意

1.正常情况下,输入框中只输入要发送的实际聊天内容即可,比如“在吗老公,急事”,但是为了更容易测试,页面中输入的是拼接后的json消息体,接收者用户id,以及消息类型,实际开发中数据格式让前端处理即可,前端根据输入的内容拼接成如输入框图所示的数据格式即可2.messageType来区分单聊还是群聊,但是此处的群聊是建立连接的所有websocket服务,没有区分组概念,如果区分的话,后台接口请求路径中要添加上roomId参数,然后建立连接时将进入该聊天室的用户放入一个map集合中,群聊发送消息时,根据不同的roomId,只给该组的用户推送群聊消息即可

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。