用websocket实现实时聊天功能

用websocket实现实时聊天功能最近想实现网页版的仿QQ聊天工具,本来想用ajax实现的,但是一想到要一直轮询,就感觉有点蠢。后来在网上找到了websocket相关的资料,就拿来跟大家分享下(不是很熟练,现在只实现了群聊,单聊的前端不会写了。但可以跟大家说说思路)。服务器端代码:首先要创建类WebSocketConfig实现ServerApplicationConfig接口,ServerApplicationConfig项目…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

最近想实现网页版的仿QQ聊天工具,本来想用ajax实现的,但是一想到要一直轮询,就感觉有点蠢。后来在网上找到了websocket相关的资料,就拿来跟大家分享下(不是很熟练,现在只实现了群聊,单聊的前端不会写了。但可以跟大家说说思路)。
服务器端代码:
首先要创建类WebSocketConfig实现ServerApplicationConfig接口,ServerApplicationConfig项目启动时会自动启动,类似与ContextListener.是webSocket的核心配置

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;
	}

}
第二步:在webSocket的服务程序类上面加上注解@ServerEndPoint("/groupChat")表示的连接路径是:ws://${serverIp}:8000/avod/groupChat;
onopen是打开连接时的响应事件,onmessage 是发送数据时的响应事件,onclose是关闭连接时的响应事件。
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();
				}
			}
			
		}
}
前端代码: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注解下的方法
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;
			}
刚连接的时候有握手操作,所以会有些延迟。如果想在一连接上就发消息的话,最好先判断下连接状态(连接成功建立,再发消息)。
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); 
消息展示:展示消息的消息是后台广播出来的数据,即broadcast()的执行结果。
			//展示消息
			groupWS.onmessage=function(event){
			  	eval("var message="+event.data+";");
				console.info(message);
				}
发送消息:发生的消息为String类型,如果想传一个实体对象到后台,需要先转换为json字符串,可以用JSON.stringify()来转换。消息发送会触发后台的@onmessage注解下的方法。
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("");	
	}
关闭:关闭会触发后台@onclose注解下的方法
	groupWS.close(); 
下面来看效果图:
![这里写图片描述](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,广播给在聊天室中的所有人。
单聊的代码如下:
@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);
		}
		
	}
	
}
单聊的思路用口头说有点啰嗦,大家看代码及下面的流程图即可:
![这里写图片描述](https://img-blog.csdn.net/20180225003521503?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvWkhBTkdMSV9XT1JC/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)

由于自己的前端编码水平有限,单聊的前端界面不会写,不过后台数据已经可以用了(已验证),会前端的可以试试实现。

关于我


    可以扫描关注下面的公众号(公众号:猿类进化论)
在这里插入图片描述

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

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号