目录

使用场景

原理和知识点

Java代码实现

服务端实现

Java实现客户端

Vue代码实现

总结


使用场景

希望做一个能够替代轮询的大屏后台系统,可以向多个客户端主动推送更改数据,而非轮询请求。

原理和知识点

WebSocket理解

看完让你彻底理解 WebSocket 原理

Http、Socket、WebSocket之间联系与区别

WebSocket介绍和Socket的区别

Websocket原理及基本属性和方法

上面的博客讲了一些基本的概念原理和理解,例子也比较容易理解,有时间可自行查看,不想看直接向下看代码。

说一下个人的理解,仅供参考(与Http作比较):

  • WebSocket类似于Http,都是一种可靠性传输协议,就是用来传输通信的。

  • 但是Http是短连接,每次连接完成后都需要断开,第二次连接时需要重新请求;而WebSocket则是连接一次后,便可以持久通信的,这也是两者最大的区别。

  • 同时Http只支持请求-回复,服务端不能主动向客户端发送消息,而WebSocket则可以双向通信(全双工)

  • 总的来说就是,WebSocket可以用来通信,一次连接后即可实现持久性双向通信。


Java代码实现

服务端实现

知识点了解后,还不来两行代码过过瘾?废话不多说,上码!

万事先依赖:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

首先,我们先配置WebSocketConfig,配置ServerEndpointExporter去查找带有@ServerEndPoint注解的服务类。

@Configuration
public class WebSocketConfig {
​
    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
}

然后,编写WebSocketServer服务类,添加上注解@ServerEndPoint(“/自定义地址”)(表明这是WebSocket的服务类)和@Component(注入容器)。

同时需要声明以下监听方法:

  • @OnOpen,当有客户端连接时触发

  • @OnClose,当有客户端关闭时触发

  • @OnMessage,当收到客户端消息时触发

  • @OnError,当通信发生错误时触发

@Component
@ServerEndpoint("/websocket/{userName}")
public class WebSocketServer {
​
    private static final Logger logger = LoggerFactory.getLogger(WebSocketServer.class);
    //在线客户端数量
    private static int  onlineCount = 0;
    //Map用来存储已连接的客户端信息
    private static ConcurrentHashMap<String, SocketDomain> websocketMap = new ConcurrentHashMap<>();
    //当前连接客户端的Session信息
    private Session session;
    //当前客户端名称
    private String userName="";
    
    @OnOpen
    public void onOpen(Session session, @PathParam("userName") String userName){
        if(!websocketMap.containsKey(userName)){
            WebSocketServer.onlineCount++;
        }
        this.session = session;
        this.userName = userName;
        SocketDomain socketDomain = new SocketDomain();
        socketDomain.setSession(session);
        socketDomain.setUri(session.getRequestURI().toString());
        websocketMap.put(userName, socketDomain);
        logger.info("用户连接:"+ userName + ",人数:"+onlineCount);
        try {
            sendMessage("连接成功");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @OnClose
    public void onClose(){
        if(websocketMap.containsKey(userName)){
            websocketMap.remove(userName);
            onlineCount--;
            logger.info("用户关闭:"+ userName + ",人数:"+onlineCount);
        }
    }
​
    @OnMessage
    public void onMessage(String message,Session session){
        if(StringUtil.isNotEmpty(message)){
            logger.info("收到用户消息:"+userName+",报文:"+message);
        }
    }
​
​
​
    //给当前客户端发消息
    private void sendMessage(String obj) {
        synchronized (session) {
            this.session.getAsyncRemote().sendText(obj);
        }
    }
​
​
​
    //给指定客户端发送消息,通过UserName找到Session发送
    private void sendMessageTo(String userName,String obj){
        SocketDomain socketDomain = websocketMap.get(userName);
        try {
            if(socketDomain !=null){
                socketDomain.getSession().getAsyncRemote().sendText(obj);
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }
    }
    //给除了当前客户端的其他客户端发消息
    private void sendMessageToAllExpectSelf(String message, Session fromSession) {
        for(Map.Entry<String, SocketDomain> client : websocketMap.entrySet()){
            Session toSeesion = client.getValue().getSession();
            if( !toSeesion.getId().equals(fromSession.getId())&&toSeesion.isOpen()){
                toSeesion.getAsyncRemote().sendText(message);
                logger.info("服务端发送消息给"+client.getKey()+":"+message);
            }
        }
    }
    //给包括当前客户端的全部客户端发送消息
    private void sendMessageToAll(String message){
        for(Map.Entry<String, SocketDomain> client : websocketMap.entrySet()){
            Session toSeesion = client.getValue().getSession();
            if(toSeesion.isOpen()){
                toSeesion.getAsyncRemote().sendText(message);
                logger.info("服务端发送消息给"+client.getKey()+":"+message);
            }
        }
    }
    //给外部调用的方法接口
    public void sendAll(String Message){
        sendMessageToAll(Message);
    }
​
​
}
​​​​​​​@Data
public class SocketDomain {
​
    private Session session;
​
    private String uri;
}

以上,服务端就写好了。WebSocket在线测试地址可以通过这个网站来测试自己的服务是否正常。

服务端OK后,就是实现客户端了,分别是java版本和Vue版本。Html和Vue差不多,可以自行编写和查找。


Java实现客户端

还是那句话,万事先依赖:

<dependency>
    <groupId>org.java-websocket</groupId>
    <artifactId>Java-WebSocket</artifactId>
    <version>1.4.0</version>
</dependency>

然后,编写客户端:

//继承WebSocketClient,重写监听方法和构造方法
public class MyWebSocketClient extends WebSocketClient {
    private static final Logger logger = LoggerFactory.getLogger(MyWebSocketClient.class);
​
    public MyWebSocketClient(URI serverUri){
        super(serverUri);
    }
    @Override
    public void onOpen(ServerHandshake serverHandshake) {
        logger.info("客户端和ws服务已经连接上了");
    }
​
    @Override
    public void onMessage(String s) {
        logger.info("收到服务器消息:"+ s);
    }
​
    @Override
    public void onClose(int i, String s, boolean b) {
        logger.info("客户端和服务端连接关闭");
        logger.info(String.valueOf(i));
        logger.info(s);
        logger.info(String.valueOf(b));
    }
​
    @Override
    public void onError(Exception e) {
        logger.info("通信发生错误");
    }
}

很简单,客户端已经写完了,但是总的让客户端和服务端连起来,所以,写个测试类:

这里有两点需要注意,很多人没有myWebSocketClient.connect();这一句,但是我自己测试发现,没有这一句连接是无法连接上的,于是我去构造函数里看了下,果真没发现有连接的方法,所以这个地方我添加了connect方法。(还有一个ConnectBlocking,里面包含了connect方法并返回布尔类型状态,所以效果一样,close方法相似)

还有一个地方需要注意,在客户端连接时,可能会存在延迟,如果客户端还没有连接上时就直接send,是会报错的,所以,我们先需要确保与服务端连接上再进行发送。

@RestController
public class WebSocketController {
​
    @GetMapping("/websocket/main")
    public void main() throws URISyntaxException, InterruptedException {
        MyWebSocketClient myWebSocketClient    = new MyWebSocketClient(new URI("ws://上面自定义的服务端地址"));
        //主动与服务端连接
        myWebSocketClient.connect();
        //确保与服务端连接上,再进行下一步
        while(!myWebSocketClient.getReadyState().equals(ReadyState.OPEN)){
            System.out.println("连接中。。。");
        }
        //给服务端发消息
        myWebSocketClient.send("我是Java客户端");
        //        关闭客户端
        //        myWebSocketClient.close();
        //        Thread.sleep(3000);
        //        System.out.println(myWebSocketClient.getReadyState());
    }
}

然后启动服务,发个消息试试吧。

客户端日志:

连接中。。。 2022-01-08 14:54:49.924 INFO 18124 --- [ctReadThread-37] c.e.t.Controller.MyWebSocketClient       : 客户端和ws服务已经连接上了 2022-01-08 14:54:49.925 INFO 18124 --- [ctReadThread-37] c.e.t.Controller.MyWebSocketClient       : 收到服务器消息:连接成功

服务端日志:

2022-01-08 14:54:49.921 INFO 6636 --- [io-8087-exec-10] c.e.testdemo2.WebSocket.WebSocketServer : 用户连接:main,人数:2 2022-01-08 14:54:49.926 INFO 6636 --- [nio-8087-exec-2] c.e.testdemo2.WebSocket.WebSocketServer : 收到用户消息:main,报文:我是Java客户端

以上,连接且通信成功!

Vue代码实现

Vue实现客户端

可以先看WebSocket API,写的也挺清楚的,具体代码直接贴了!

<template>
<button @click="webclick">给后台发送消息</button>
</template>
​
<script>
export default {
    name: "web2",
    data() {
        return {
            ws: null
        }
    },
    created() {
        //连接WebSocket服务端,然后初始化监听事件
        this.ws = new WebSocket("ws://localhost:8087/websocket/test");
        this.wsInit();
​
    },
    methods: {
        wsInit() {
            
            this.ws.onopen = () => {
                this.ws.send("服务已连接");
                console.log(this.ws.readyState)
            }
            this.ws.onclose = () => {
                console.log("服务器关闭")
                console.log(this.ws.readyState)
            }
            this.ws.onmessage = (message) => {
                console.log("收到服务器消息")
                console.log(message)
                console.log(this.ws.readyState)
            }
            this.ws.onerror = (error) => {
                console.log("报错了")
                console.log(error)
                console.log(this.ws.readyState)
            }
            
        }
    }
}
</script>

输出日志:

 

这种写法是比较简单的写法,还有一个稍微复杂的写法,添加监听事件,功能相似,但是更能让人理解。可以进入WebSocket类里查看或者可看springboot+vue实现Websocket这篇博客,我上面的服务端和vue客户端都是借鉴的这个博客写的。

总结

多个客户端和服务器

先看日志:

服务端发送日志:

Java客户端接受日志:

Vue客户端接收日志:

 

再看vue客户端发送日志:

再看服务端接收日志消息:

java客户端效果类似,不再重复看了!


以上总结:WebSocket支持客户端和服务端双向通信;服务端不仅可以指定发送给某一个客户端,可以群发给客户端,实现广播效果。而且连接后,可以持久性通信,不论是对于聊天通信等场景还是替换前端轮询请求都是一种很好的解决方案

最后,再推荐一个不错的代码博客WebSocket群聊(HTML前端写的简单界面,可以了解一下)。

🙉愿猿圆缘,愿猿圆愿🙈

 

Logo

前往低代码交流专区

更多推荐