SpringBoot应用WebSocket实现在线聊天

目录

一、简介

二、java服务端

1、引入包

2、配置

3、代码实现

三、H5客户端

1、代码实现

 

一、简介

WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。

WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

现在,很多网站为了实现推送技术,所用的技术都是轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。

而比较新的技术去做轮询的效果是Comet。这种技术虽然可以双向通信,但依然需要反复发出请求。而且在Comet中,普遍采用的长链接,也会消耗服务器资源。

在这种情况下,HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。

二、java服务端

1.引入包(gradle管理)

compile 'org.springframework.boot:spring-boot-starter-websocket:2.0.4.RELEASE'

2.配置

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * 简介:
 *
 * @Author: hzj
 * @Date: 2019/7/8 0008 17:44
 */
@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

}

3.代码实现

import com.getoncar.common.exception.BaseException;
import com.getoncar.common.exception.CustomException;
import com.getoncar.entity.Agent_Entity;
import com.getoncar.entity.Message_Entity;
import com.getoncar.host.agent.config.websupport.ResponseVo;
import com.getoncar.model.AgentSendDialogueRequest;
import com.getoncar.model.MessageModel;
import com.getoncar.service.MessageAgentService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.EOFException;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static com.getoncar.service.MessageAgentService.oneMessageCache;


/**
 * 简介:
 *
 * @Author: hzj
 * @Date: 2019/7/9 0009 9:05
 */
@Slf4j
@Api(description ="websocket对话中心-hzj")
@RequestMapping(value = "/websocketController")
@ServerEndpoint(value = "/websocket/{userId}")
@Component    //此注解千万千万不要忘记,它的主要作用就是将这个监听器纳入到Spring容器中进行管理
@RestController
public class WebSocketController {

    //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
    public static int onlineCount = 0;
    //concurrent包的线程安全Set[ConcurrentHashMap],用来存放每个客户端对应的MyWebSocket对象。
    public static ConcurrentHashMap webSocketSet = new ConcurrentHashMap<>();
    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    public Session session;
    //接收参数中的用户ID
    public Long userId;
    //接收用户中的平台类型
    public Long platformType;

    public Session getSession() {
        return session;
    }

    public void setSession(Session session) {
        this.session = session;
    }

    private static ExecutorService executorService = Executors.newCachedThreadPool();

    private static MessageAgentService messageService;
    @Autowired
    public void setMessageService(MessageAgentService messageService) {
        WebSocketController.messageService = messageService;
    }

    @ApiOperation(value ="服务器群发消息")
    @ApiImplicitParams(
            @ApiImplicitParam(paramType = "query",name = "content",value = "消息内容",dataType = "String")
    )
    @GetMapping("/ServerSendInfo")
    public void ServerSendInfo(String content) throws IOException {
        sendInfos("这是服务器给你推送的消息:"+content);
    }


    /**
     * 连接建立成功调用的方法
     * 接收url中的参数
     */
    @OnOpen
    public void onOpen(Session session,@PathParam("userId") Long userId) {
        log.info("进入websocket连接");
        if(webSocketSet.containsKey(userId+"")){
            log.info("重复登陆-"+userId);
            try {
                WebSocketController before = webSocketSet.get(userId+"");
                if(before.getSession().isOpen()){
                    log.info("发送被迫下线通知");
                    before.getSession().getBasicRemote().sendText("你的会话再另一地方登陆,被迫下线");
                    before.getSession().close();//关闭会话
                }
                webSocketSet.remove(before);
                log.info("重复登陆"+userId+"连接"+before.getSession().getId()+"被服务器主动关闭!当前在线人数为" + getOnlineCount());
            } catch (IOException e) {
                e.printStackTrace();
                return;
            }
        }
        int maxSize = 200 * 1024; // 200K
        // 可以缓冲的传入二进制消息的最大长度
        session.setMaxBinaryMessageBufferSize(maxSize);
        // 可以缓冲的传入文本消息的最大长度
        session.setMaxTextMessageBufferSize(maxSize);
        this.session = session;
        this.userId = userId;
        this.platformType = platformType;
        webSocketSet.put(userId+"",this);     //加入set中
        addOnlineCount();           //在线数加1
        log.info("有新连接加入!当前在线人数为" + getOnlineCount() + "  userId==== " + userId + "  platformType==== " + platformType);
//        try {
//            sendMessage("连接成功");
//        } catch (IOException e) {
//            log.error("websocket IO异常");
//        }
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(CloseReason reason) {
        try {
            webSocketSet.remove(this);  //从set中删除
            webSocketSet.remove(this.userId+"");  //从set中删除
            System.out.println("连接关闭***************"+this.userId);
            subOnlineCount();           //在线数减1
            log.info("有一连接"+this.session.getId()+"关闭!当前在线人数为" + getOnlineCount());
            log.info("连接"+this.session.getId()+"关闭原因:"+reason.getCloseCode()+"-"+reason.toString());
        }catch (Exception e){
            log.info("异常情况");
            e.printStackTrace();
        }
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
    //{"car_dealer_id": 198,"car_resource_id": 88, "content": "H你好啊","token":"56bd2cbf1e1349f29cdbbbc54ffc1b95"}
        log.info("来自客户端"+session.getId()+"的消息:" + message);
        if(message.equals("_0_")){
            log.info("心跳");
            System.err.println("心跳");
        }else {
            Runnable t = new Runnable() {
                @Override
                public void run() {
                    JSONObject json = JSONObject.fromObject(message);
                    AgentSendDialogueRequest asdr = (AgentSendDialogueRequest)JSONObject.toBean(json,AgentSendDialogueRequest.class);
                    Long receiverId = 0L;
                    Agent_Entity agent_entity = messageService.getAgentByAccessToken(asdr.getToken());
                    if(agent_entity != null) {
                        Long agent_id = agent_entity.getId();
                        asdr.setFromAgentId(agent_id);
                        receiverId = (asdr.getCar_dealer_id());//接收者id
                        try {
                            if (session.isOpen()) { //先确认 session是否已经打开 使用session.isOpen() 为true 则发送消息。
                                sendInfo(receiverId, JSONObject.fromObject(asdr).toString()); //把发送者userId的消息发给-->receiverId接收者id
                            }
                        } catch (EOFException e) {
                            log.info("报错EOFException" + e.getMessage());
                            System.err.println("报错EOFException" + e.getMessage());
                        } catch (IOException e) {
                            log.info("报错IOException" + e.getMessage());
                            e.printStackTrace();
                        }finally {
                            //入库操作
                            
 
                        }
                    }
                }
            };
            executorService.submit(t);
        }
    }

    /**
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("发生错误" + error);
        error.printStackTrace();
    }

    public void sendMessage(String message) throws IOException {
        synchronized (this.session) {
            this.session.getBasicRemote().sendText(message);
        }
    }

    /**
     * 私发
     *
     * @param message
     * @throws IOException
     */
    public static void sendInfo(Long userId, String message) throws IOException {
        for (WebSocketController item : webSocketSet.values()) {
            try {
                if (item.userId.equals(userId)) {
                    item.sendMessage(message);
                }
            } catch (IOException e) {
                continue;
            }
        }
    }

    /**
     * 群发自定义消息
     */
    public static void sendInfos(String message) throws IOException {
        log.info(message);
        for (WebSocketController item : webSocketSet.values()) {
            try {
                item.sendMessage(message);
            } catch (IOException e) {
                continue;
            }
        }
    }

    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        WebSocketController.onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        WebSocketController.onlineCount--;
    }

}

三、H5客户端

1.代码实现




    
    
    PC端聊天窗口
    
    
    
    




To: Dog Woofson

适用浏览器:360、FireFox、Chrome、Opera、傲游、搜狗、世界之窗. 不支持Safari、IE8及以下浏览器。

HTML、css、js资料下载地址

链接:https://pan.baidu.com/s/1VXYBiz5vKrMmlvWpqYw-4A 
提取码:2xtq 
 

开发过程如有疑问可交流讨论 WX:18637815946 

你可能感兴趣的:(java,JavaQuery)