package com.kidgrow.websocket.server; import com.kidgrow.websocket.entity.Client; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.websocket.*; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.List; import java.util.concurrent.CopyOnWriteArraySet; import java.util.stream.Collectors; /** * 石家庄喜高科技有限责任公司 版权所有 © Copyright 2020
* * @Description: websocket服务端
* @Project:
* @CreateDate: Created in 2020/3/1 16:05
* @Author: liuke */ @ServerEndpoint(value = "/socketServer/{userName}") @Component public class SocketServer { private static final Logger logger = LoggerFactory.getLogger(SocketServer.class); /** * * 用线程安全的CopyOnWriteArraySet来存放客户端连接的信息 */ private static CopyOnWriteArraySet socketServers = new CopyOnWriteArraySet<>(); /** * * websocket封装的session,信息推送,就是通过它来信息推送 */ private Session session; /** * * 服务端的userName,因为用的是set,每个客户端的username必须不一样,否则会被覆盖。 * 要想完成ui界面聊天的功能,服务端也需要作为客户端来接收后台推送用户发送的信息 */ private final static String SYS_USERNAME = "kidgrow"; /** * * 用户连接时触发,我们将其添加到 * 保存客户端连接信息的socketServers中 * * @param session * @param userName */ @OnOpen public void open(Session session,@PathParam(value="userName")String userName){ this.session = session; socketServers.add(new Client(userName,session)); logger.info("客户端:【{}】连接成功",userName); } /** * * 收到客户端发送信息时触发 * 我们将其推送给客户端(niezhiliang9595) * 其实也就是服务端本身,为了达到前端聊天效果才这么做的 * * @param message */ @OnMessage public void onMessage(String message){ Client client = socketServers.stream().filter( cli -> cli.getSession() == session) .collect(Collectors.toList()).get(0); sendMessage(client.getUserName()+"<--"+message,SYS_USERNAME); logger.info("客户端:【{}】发送信息:{}",client.getUserName(),message); } /** * * 连接关闭触发,通过sessionId来移除 * socketServers中客户端连接信息 */ @OnClose public void onClose(){ socketServers.forEach(client ->{ if (client.getSession().getId().equals(session.getId())) { logger.info("客户端:【{}】断开连接",client.getUserName()); socketServers.remove(client); } }); } /** * * 发生错误时触发 * @param error */ @OnError public void onError(Throwable error) { socketServers.forEach(client ->{ if (client.getSession().getId().equals(session.getId())) { socketServers.remove(client); logger.error("客户端:【{}】发生异常",client.getUserName()); error.printStackTrace(); } }); } /** * * 信息发送的方法,通过客户端的userName * 拿到其对应的session,调用信息推送的方法 * @param message * @param userName */ public synchronized static void sendMessage(String message,String userName) { socketServers.forEach(client ->{ if (userName.equals(client.getUserName())) { try { client.getSession().getBasicRemote().sendText(message); logger.info("服务端推送给客户端 :【{}】",client.getUserName(),message); } catch (IOException e) { e.printStackTrace(); } } }); } /** * * 获取服务端当前客户端的连接数量, * 因为服务端本身也作为客户端接受信息, * 所以连接总数还要减去服务端 * 本身的一个连接数 * * 这里运用三元运算符是因为客户端第一次在加载的时候 * 客户端本身也没有进行连接,-1 就会出现总数为-1的情况, * 这里主要就是为了避免出现连接数为-1的情况 * * @return */ public synchronized static int getOnlineNum(){ return socketServers.stream().filter(client -> !client.getUserName().equals(SYS_USERNAME)) .collect(Collectors.toList()).size(); } /** * * 获取在线用户名,前端界面需要用到 * @return */ public synchronized static List getOnlineUsers(){ List onlineUsers = socketServers.stream() .filter(client -> !client.getUserName().equals(SYS_USERNAME)) .map(client -> client.getUserName()) .collect(Collectors.toList()); return onlineUsers; } /** * * 信息群发,我们要排除服务端自己不接收到推送信息 * 所以我们在发送的时候将服务端排除掉 * @param message */ public synchronized static void sendAll(String message) { //群发,不能发送给服务端自己 socketServers.stream().filter(cli -> cli.getUserName() != SYS_USERNAME) .forEach(client -> { try { client.getSession().getBasicRemote().sendText(message); } catch (IOException e) { e.printStackTrace(); } }); logger.info("服务端推送给所有客户端 :【{}】",message); } /** * * 多个人发送给指定的几个用户 * @param message * @param persons */ public synchronized static void SendMany(String message,String [] persons) { for (String userName : persons) { sendMessage(message,userName); } } }