最近想实现网页版的仿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)
由于自己的前端编码水平有限,单聊的前端界面不会写,不过后台数据已经可以用了(已验证),会前端的可以试试实现。
可以扫描关注下面的公众号(公众号:猿类进化论)
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/179627.html原文链接:https://javaforall.cn