前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >用websocket实现实时聊天功能

用websocket实现实时聊天功能

作者头像
全栈程序员站长
发布2022-11-01 16:26:50
2.1K0
发布2022-11-01 16:26:50
举报
文章被收录于专栏:全栈程序员必看
代码语言:javascript
复制
最近想实现网页版的仿QQ聊天工具,本来想用ajax实现的,但是一想到要一直轮询,就感觉有点蠢。后来在网上找到了websocket相关的资料,就拿来跟大家分享下(不是很熟练,现在只实现了群聊,单聊的前端不会写了。但可以跟大家说说思路)。
服务器端代码:
首先要创建类WebSocketConfig实现ServerApplicationConfig接口,ServerApplicationConfig项目启动时会自动启动,类似与ContextListener.是webSocket的核心配置
代码语言:javascript
复制
import java.util.Set;

import javax.websocket.Endpoint;
import javax.websocket.server.ServerApplicationConfig;
import javax.websocket.server.ServerEndpointConfig;

public class WebSocketConfig implements ServerApplicationConfig{

	@Override
	public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> channel) {
		System.out.println("Endpoint扫描到的数量"+channel.size());
		return channel;
	}

	@Override
	public Set<ServerEndpointConfig> getEndpointConfigs(Set<Class<? extends Endpoint>> channel) {
		System.out.println("实现EndPoint接口的类数量:"+channel.size());
		return null;
	}

}
代码语言:javascript
复制
第二步:在webSocket的服务程序类上面加上注解@ServerEndPoint("/groupChat")表示的连接路径是:ws://${serverIp}:8000/avod/groupChat;
onopen是打开连接时的响应事件,onmessage 是发送数据时的响应事件,onclose是关闭连接时的响应事件。
代码语言:javascript
复制
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import com.google.gson.Gson;
@ServerEndpoint("/groupChat")
public class GroupChatSocket {
//存放socket
private static Map<String,GroupChatSocket> socketMap = new HashMap<>();
//存放用户名(userLoginName)
private static List<String> userNameList = new ArrayList<>();
private Session session;
private String userName;
private Gson gson = new Gson();
@OnOpen
public void openSocket(Session session){
this.session = session;
//var url="ws://${serverIp}:8000/avod/groupChat?currentUser=${session_user.loginName}";?后面的值就是session传递的值
//reqString的值为:currentUser=${session_user.loginName}
String reqString = this.session.getQueryString();
this.userName = reqString.split("=")[1];
socketMap.put(this.userName, this);
}
@OnMessage
public void receive(Session session,String msgDataJson){
MsgData msgData = gson.fromJson(msgDataJson, MsgData.class);
Message message = new Message();
message.setMsgSender(this.userName);
message.setSendDate(new Date().toLocaleString());
message.setMsgContent(msgData.getMsgContent());
if(msgData.getIsFristJoin().equals("Y")){
userNameList.add(this.userName);
message.setHintMessage(this.userName+"加入群聊!!!!");
message.setUserNameList(userNameList);
broadcast(socketMap,gson.toJson(message),this.userName);
}
else{
message.setUserNameList(userNameList);
broadcast(socketMap,gson.toJson(message));
}
}
@OnClose
public void closeSocket(Session session){
socketMap.remove(this.userName, this);
userNameList.remove(this.userName);
if(userNameList.size()==1||userNameList.size()>1){
Message message = new Message();
message.setHintMessage(this.userName+"退出了群聊!!!!");
System.out.println(this.userName+"退出了群聊!!!!");
message.setUserNameList(userNameList);
broadcast(socketMap, gson.toJson(message));
}
}
//广播聊天内容
private void broadcast(Map<String,GroupChatSocket> socketMap, String message) {
for(Iterator<GroupChatSocket> iterator = socketMap.values().iterator();iterator.hasNext();){
GroupChatSocket socket = iterator.next();
try {
socket.session.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
//广播提示信息,除了自己以外的都要广播
private void broadcast(Map<String,GroupChatSocket> socketMap, String message,String userName) {
for(Iterator<GroupChatSocket> iterator = socketMap.values().iterator();iterator.hasNext();){
GroupChatSocket socket = iterator.next();
if(socketMap.get(userName) == socket){
Message msg = gson.fromJson(message, Message.class);
msg.setHintMessage(null);
try {
socket.session.getBasicRemote().sendText(gson.toJson(msg));
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
continue;
}
try {
socket.session.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
代码语言:javascript
复制
前端代码:groupWS.readyState可以获取websocket的状态:
根据readyState属性可以判断webSocket的连接状态,该属性的值可以是下面几种:
0 :对应常量CONNECTING (numeric value 0), 正在建立连接连接,还没有完成。The connection has not yet been established.
1 :对应常量OPEN (numeric value 1),连接成功建立,可以进行通信。The WebSocket connection is established and communication is possible.
2 :对应常量CLOSING (numeric value 2),连接正在进行关闭握手,即将关闭。The connection is going through the closing handshake.
3 : 对应常量CLOSED (numeric value 3),连接已经关闭或者根本没有建立。The connection has been closed or could not be opened.
打开连接:打开连接会触发后台@onopen注解下的方法
代码语言:javascript
复制
if(groupWS==null||groupWS==""||groupWS.readyState==3){
var url="ws://${serverIp}:8000/avod/groupChat?currentUser=${session_user.loginName}";
if ('WebSocket' in window) {
groupWS = new WebSocket(url);
} else if ('MozWebSocket' in window) {
groupWS = new MozWebSocket(url);
} else {
alert('WebSocket is not supported by this browser.');
return;
}
代码语言:javascript
复制
刚连接的时候有握手操作,所以会有些延迟。如果想在一连接上就发消息的话,最好先判断下连接状态(连接成功建立,再发消息)。
代码语言:javascript
复制
var findInterval = setInterval(function (){
console.log(groupWS.readyState);
if(groupWS.readyState==1){
var msgData = JSON.stringify({chatType : "groupChat",isFristJoin : "Y"});
groupWS.send(msgData);
clearInterval(findInterval);
}
}, 500); 
代码语言:javascript
复制
消息展示:展示消息的消息是后台广播出来的数据,即broadcast()的执行结果。
代码语言:javascript
复制
			//展示消息
groupWS.onmessage=function(event){
eval("var message="+event.data+";");
console.info(message);
}
代码语言:javascript
复制
发送消息:发生的消息为String类型,如果想传一个实体对象到后台,需要先转换为json字符串,可以用JSON.stringify()来转换。消息发送会触发后台的@onmessage注解下的方法。
代码语言:javascript
复制
function sendMesg(){		
var type = "groupChat";
var msg = $("#mesgContent").val().trim();
var joinFlag = "N";
var msgData = JSON.stringify({chatType : type,msgContent : msg,isFristJoin : joinFlag});
groupWS.send(msgData);
$("#mesgContent").val("");	
}
代码语言:javascript
复制
关闭:关闭会触发后台@onclose注解下的方法
代码语言:javascript
复制
	groupWS.close(); 
代码语言:javascript
复制
下面来看效果图:
![这里写图片描述](https://img-blog.csdn.net/20180225003126444?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvWkhBTkdMSV9XT1JC/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
总结下思路:当点击群聊的图标的时候,打开连接,并将userName为key,当前对象为value,加入socketMap中,并发送一条消息,表示加入聊天室,并广播给在聊天室中除了自己的所有人,同时将userName加入userNameList中。聊天时,广播给在聊天室中的所有人。关闭聊天是,socketMap移除userName为key的value,同时userNameList也移除userName,广播给在聊天室中的所有人。
单聊的代码如下:
代码语言:javascript
复制
@ServerEndpoint("/singleChat")
public class SingleChatSocket {
//存放socket
private static Map<String,SingleChatSocket> socketMap = new HashMap<>();
//存放用户请求的map(主要用来获取用户的头像),以用户名(userName)为key,以对应的请求该用户聊天的好友集合(userList)为value
private static Map<String,List<String>> requestMap = new HashMap<>();
//以用户名(userName)为key,以对应的与该用户聊天的好友集合(chatList)为value
private static Map<String,List<String>> chatMap = new HashMap<>();
private static final String SENDER_CODE = "PING";
private static final String RECEIVER_AGREE_CODE = "PONG";
private static final String RECEIVER_REJECT_CODE = "GUN";
private Session session;
private String userName;
private Gson gson = new Gson();
@OnOpen
public void openSocket(Session session){
this.session = session;
String reqString = this.session.getQueryString();
this.userName = reqString.split("=")[1];
socketMap.put(this.userName, this);
}
@OnMessage
public void receive(Session session,String msgDataJson){
MsgData msgData = gson.fromJson(msgDataJson, MsgData.class);
Message message = new Message();
message.setMsgSender(this.userName);
message.setSendDate(new Date().toLocaleString());
message.setMsgContent(msgData.getMsgContent());
String msgReceiver = msgData.getMsgReceiver();
SingleChatSocket onlineChatSocket = socketMap.get(msgReceiver);
//是否首次加入(只有点击“发消息”及点击消息提示的弹窗时,isFristJoin的值才为"Y",其他情景下为"N")
if(msgData.getIsFristJoin().equals("Y")){
//如果响应码为"PING",即点击“发消息”按钮时触发,发给消息接收者(msgReceiver)即可,等待其回应
if(SENDER_CODE.equals(msgData.getResponseCode())){
message.setResponseCode(SENDER_CODE);
if(onlineChatSocket.session!=null){
//普通类调用Service
UserServiceImpl userServiceImpl = (UserServiceImpl)SpringBeanFactoryUtils.getBean("userServiceImpl");
User user = userServiceImpl.findUserByName(this.userName);
String path = user.getHeadPath();
user.setImgPath(path);
if(requestMap.get(msgReceiver)!=null){
List<String> list = requestMap.get(msgReceiver);
list.add(gson.toJson(user));
requestMap.put(msgReceiver, list);
}else{
List<String> list = new ArrayList<>();
list.add(gson.toJson(user));
requestMap.put(msgReceiver, list);
}
message.setUserList(requestMap.get(msgReceiver));
broadcast(onlineChatSocket.session,gson.toJson(message));
}
}else if(RECEIVER_AGREE_CODE.equals(msgData.getResponseCode())){
/*如果响应码为"PONG",即同意进行聊天,这时将对应的值更新进chatMap中(同时考虑以发送方为key的情况及
以接收方为key的情况),同时前端弹出聊天界面(包括发送方和接收方)*/
message.setResponseCode(RECEIVER_AGREE_CODE);
if(requestMap.get(this.userName)!=null){
List<String> list = requestMap.get(this.userName);
list.removeAll(list);
requestMap.put(this.userName,list);
}
if(onlineChatSocket.session!=null){
flushChatMap(chatMap,this.userName,msgReceiver);
//接收方显示当前的聊天队列(userList)
message.setUserNameList(chatMap.get(msgReceiver));
broadcast(onlineChatSocket.session,gson.toJson(message));
}
}else if(RECEIVER_REJECT_CODE.equals(msgData.getResponseCode())){
//如果响应码为"GUN",即不同意进行聊天,发给消息接收者(msgReceiver)即可
message.setResponseCode(RECEIVER_REJECT_CODE);
if(onlineChatSocket.session!=null){
broadcast(onlineChatSocket.session,gson.toJson(message));
}
}
}else{
//正常聊天情景下,isFristJoin的值才为"N",发给消息接收者(msgReceiver)即可
if(chatMap.get(this.userName).contains(msgReceiver)){
message.setUserNameList(chatMap.get(this.userName));
if(onlineChatSocket.session!=null){
broadcast(onlineChatSocket.session,gson.toJson(message));
}
}
/*else{
message.setMsgContent("");
message.setHintMessage(msgReceiver+"已离开");
}*/
}
}
@OnClose
public void closeSocket(Session session){
socketMap.remove(this.userName, this);
List<String> receiverList = chatMap.get(this.userName);
if(receiverList!=null){
if(receiverList.size()==1||receiverList.size()>1){
Message message = new Message();
message.setMsgSender(this.userName);
message.setHintMessage(this.userName+"下线了!!");
//broadcast(socketMap, gson.toJson(message));
broadcast(receiverList,gson.toJson(message));
}
chatMap.remove(this.userName);
}
}
private void broadcast(Session session, String message) {
try {
session.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
private void broadcast(List<String> receiverList,String message){
for(String receiver:receiverList){
Message msg = gson.fromJson(message, Message.class);
msg.setUserNameList(chatMap.get(receiver));
SingleChatSocket onlineChatSocket = socketMap.get(receiver);
if(onlineChatSocket!=null){
broadcast(onlineChatSocket.session,gson.toJson(message));
}
}
}
private void flushChatMap(Map<String,List<String>> chatMap,String msgSender,String msgReceiver){
if(chatMap.get(msgSender)!=null){
List<String> list = chatMap.get(msgSender);
list.add(msgReceiver);
chatMap.put(msgSender, list);
flushChatMap(chatMap,msgReceiver,msgSender);
}else{
List<String> list = new ArrayList<>();
list.add(msgReceiver);
chatMap.put(msgSender, list);
flushChatMap(chatMap,msgReceiver,msgSender);
}
}
}
代码语言:javascript
复制
单聊的思路用口头说有点啰嗦,大家看代码及下面的流程图即可:
![这里写图片描述](https://img-blog.csdn.net/20180225003521503?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvWkhBTkdMSV9XT1JC/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
由于自己的前端编码水平有限,单聊的前端界面不会写,不过后台数据已经可以用了(已验证),会前端的可以试试实现。

关于我


可以扫描关注下面的公众号(公众号:猿类进化论)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/179627.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年10月21日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 关于我
相关产品与服务
云服务器
云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档