4. 消息中心的设计与实现

消息中心的设计与实现

一、引言

运用场景:

1、消息的主动提醒(客户端被动接收)
2、客户模块(及时通讯)
3、单一登录(一个账号只能在一个设备登录)

消息中心的实现方案:

1、客户端轮询

2、TCP长连接(常用)

Java的长连接的方案

Java - Blocking I/O - JDK1.0 (同步阻塞式IO)
Java - Non Blocking I/O - JDK1.4(同步非阻塞式IO)
第三方的组织 - Mina、Netty(NIO)
Java - Async I/O -JDK1.7(异步非阻塞式IO)

二、BIO的实现点对点发送消息

服务器

public class Server {
    
    

    public static void main(String[] args) throws IOException {
    
    

        //创建Socket服务对象
        ServerSocket serverSocket = new ServerSocket(8888);
        //服务端要接收客户端的连接 - 阻塞式的
        final Socket socket = serverSocket.accept();//一旦有客户端连接,该方法就会返回连接该客户端的Socket对象,如果没有客户端连接,就会一直阻塞
        System.out.println("有一个客户端连接!");

        Scanner scanner = new Scanner(System.in);

        //开了一个子线程监听客户端的发送消息
        new Thread(){
    
    
            @Override
            public void run() {
    
    
                while(true) {
    
    
                    //获得客户端的请求
                    try {
    
    
                        InputStream in = socket.getInputStream();
                        byte[] bytes = new byte[10 * 1024];
                        int len = 0;//read方法是一个阻塞式的方法,如果没有客户端的消息,线程会阻塞在该方法上
                        len = in.read(bytes);
                        System.out.println("获取到客户端的请求数据:" + new String(bytes, 0, len));
                    } catch (IOException e) {
    
    
                        e.printStackTrace();
                    }
                }
            }
        }.start();


        while (true) {
    
    

            //返回客户端的响应
            System.out.println("请输入发送的内容:");
            String content = scanner.next();
            OutputStream out = socket.getOutputStream();
            out.write(content.getBytes());
        }

    }
}

客户端

public class Client {
    
    

    public static void main(String[] args) throws IOException {
    
    
        //创建客户端的socket对象,并且连接服务器
        final Socket socket = new Socket("127.0.0.1", 8888);

        Scanner scanner = new Scanner(System.in);

        //开启子线程接收服务器的响应
        new Thread(){
    
    
            @Override
            public void run() {
    
    
                while(true) {
    
    
                    //接收响应
                    InputStream in = null;
                    try {
    
    
                        in = socket.getInputStream();
                        byte[] bytes = new byte[10 * 1024];
                        int len = in.read(bytes);
                        System.out.println("接收到服务器的响应:" + new String(bytes, 0, len));
                    } catch (IOException e) {
    
    
                        e.printStackTrace();
                    }
                }
            }
        }.start();

        while (true) {
    
    
            //发消息
            System.out.println("请输入发送的内容:");
            String content = scanner.next();
            OutputStream out = socket.getOutputStream();
            out.write(content.getBytes());
            out.flush();
        }
    }
}

群发服务器

public class Server {
    
    

    public static List<Socket> socketList = new ArrayList<Socket>();

    public static void main(String[] args) throws IOException {
    
    

        //创建Socket服务对象
        ServerSocket serverSocket = new ServerSocket(8888);
        //服务端要接收客户端的连接 - 阻塞式的
        //死循环反复监听客户端的连接
        while (true) {
    
    
            final Socket socket = serverSocket.accept();//一旦有客户端连接,该方法就会返回连接该客户端的Socket对象,如果没有客户端连接,就会一直阻塞
            //保存当前的连接对象
            socketList.add(socket);
            System.out.println("有一个客户端连接!");

            //开了一个子线程监听客户端的发送消息
            new Thread() {
    
    
                @Override
                public void run() {
    
    
                    //获得客户端的请求
                    try {
    
    
                        while (true) {
    
    
                            InputStream in = socket.getInputStream();
                            byte[] bytes = new byte[10 * 1024];
                            int len = 0;//read方法是一个阻塞式的方法,如果没有客户端的消息,线程会阻塞在该方法上
                            len = in.read(bytes);
                            String content = new String(bytes, 0, len);
                            System.out.println("获取到客户端的请求数据:" + content + ", 并且将数据群发给其他的客户端!");

                            //群发给其他的客户端
                            for (Socket sock : socketList) {
    
    
                                if (sock != socket) {
    
    
                                    //不发送给自己
                                    sock.getOutputStream().write(content.getBytes());
                                }
                            }
                        }
                    } catch (IOException e) {
    
    
                        e.printStackTrace();
                    }
                }
            }.start();
        }
    }
}

群发客户端

public class Client {
    
    

    public static void main(String[] args) throws IOException {
    
    
        //创建客户端的socket对象,并且连接服务器
        final Socket socket = new Socket("127.0.0.1", 8888);

        Scanner scanner = new Scanner(System.in);

        //开启子线程接收服务器的响应
        new Thread(){
    
    
            @Override
            public void run() {
    
    
                try {
    
    
                    while(true) {
    
    
                        //接收响应
                        InputStream in = socket.getInputStream();
                        byte[] bytes = new byte[10 * 1024];
                        int len = in.read(bytes);
                        System.out.println("接收到服务器的响应:" + new String(bytes, 0, len));

                    }
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }
            }
        }.start();

        while (true) {
    
    
            //发消息
            System.out.println("请输入发送的内容:");
            String content = scanner.next();
            OutputStream out = socket.getOutputStream();
            out.write(content.getBytes());
            out.flush();
        }
    }
}

三、NIO的介绍与使用

BIO:
ServerSocket - 服务端
Socket - 连接对象
byte[] - 传递的数据类型

NIO:
ServerSocketChannel -
SocketChannel -
ByteBuffer - 本质还是byte数组
Selector - 多路复用器

多路复用器 (非阻塞)

在这里插入图片描述
在这里插入图片描述

ByteBuffer - NIO数据传递的对象,本质还是Byte数组

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qRPPExJe-1629036777488)(img/image-20200724113948220.png)]

使用NIO编写一个本地的文件拷贝

/**
 * 文件拷贝的demo - NIO
 */
public class Demo {
    
    

    public static void main(String[] args)  {
    
    

        try (
            FileChannel inChannel = new FileInputStream("C:\\Users\\Ken\\Pictures\\Saved Pictures\\奥格瑞玛.jpg").getChannel();
            FileChannel outChannel = new FileOutputStream("C:\\Users\\Ken\\Desktop\\a.jpg").getChannel();
        ) {
    
    
            //准备ByteBuffer
            ByteBuffer byteBuffer = ByteBuffer.allocate(10 * 1024);//位置为0  容量最大 界限指到容量
            //循环写入数据到byteBuffer
            while (inChannel.read(byteBuffer) != -1) {
    
    
                //byteBuffer  位置指向写入的数据的末端 容量最大 界限指到容量
                byteBuffer.flip();//将界限移动到位置的地方,位置重置为0 -- 为读取数据做准备
                //读取byteBuffer的数据,写入输出管道
                outChannel.write(byteBuffer);
                //重置byteBuffer
                byteBuffer.clear(); // 将界限移动到容量的位置,位置重置为0 -- 为写入数据做准备
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

使用NIO实现一个消息群发的功能

服务器

public class NioServer {
    
    

    public static void main(String[] args) throws IOException {
    
    

        List<SocketChannel> socketChannels = new ArrayList<>();

        //创建一个服务端的Channel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //绑定端口
        serverSocketChannel.bind(new InetSocketAddress(9999));
        //设置Channel的类型
        serverSocketChannel.configureBlocking(false);//设置当前Channel为非阻塞模式

        //创建一个多路复用器
        Selector selector = Selector.open();
        //将Channel注册到多路复用器上
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);//注册到多路复用器,注册的动作为(等待客户端连接)

        //主线程轮询多路复用器
        while(true){
    
    

            //询问多路复用器是否有准备好的动作
            int select = selector.select();
            if(select > 0){
    
    
                //有客户端进行了某些操作
                //SelectionKey - 封装(channel, 动作)
                Set<SelectionKey> selectionKeys = selector.selectedKeys();//从多路复用器中,返回有动作的Channel集合
                //循环处理channel
                for (SelectionKey selectionKey : new HashSet<>(selectionKeys)) {
    
    
                    //从集合中移除该channel
                    selectionKeys.remove(selectionKey);

                    //判断当前发生了什么动作
                    if (selectionKey.isAcceptable()){
    
    
                        //说明有一个新的客户端连接了!
                        ServerSocketChannel serverChannel = (ServerSocketChannel) selectionKey.channel();
                        //等待客户端连接
                        SocketChannel socketChannel = serverChannel.accept();
                        System.out.println("接收到客户端的连接!!!!");
                        //将客户端的Channel注册到多路复用器上
                        socketChannel.configureBlocking(false);//非阻塞模式
                        socketChannel.register(selector, SelectionKey.OP_READ);//注册多路复用器

                        //保存管理客户端的Channel集合
                        socketChannels.add(socketChannel);
                    } else if(selectionKey.isReadable()){
    
    
                        //说明有一个客户端发送了消息
                        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                        //读取channel中的数据
                        ByteBuffer byteBuffer = ByteBuffer.allocate(10 * 1024);
                        socketChannel.read(byteBuffer);

                        //群发
                        for (SocketChannel otherChannel : socketChannels) {
    
    
                            if (otherChannel != socketChannel) {
    
    //排除本人
                                byteBuffer.flip();//bytebuffer读取准备
                                otherChannel.write(byteBuffer);
                            }
                        }
                        byteBuffer.flip();
                        byte[] bytes = byteBuffer.array();
                        System.out.println("接收到客户端的数据:" + new String(bytes) + ", 群发给其他的客户端");
                    }
                }
            }
        }
    }
}

客户端

public class NioClient {
    
    

    public static void main(String[] args) throws IOException {
    
    
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress("127.0.0.1", 9999));
        socketChannel.configureBlocking(true);//设置为阻塞模式

        //开启子线程处理服务器的读请求
        new Thread(() -> {
    
    
            try {
    
    
                while (true) {
    
    
                    ByteBuffer byteBuffer = ByteBuffer.allocate(10 * 1024);
                    socketChannel.read(byteBuffer);
                    //打印数据
                    byteBuffer.flip();
                    byte[] array = byteBuffer.array();
                    System.out.println("读取到服务器的消息:" + new String(array));
                }
            } catch (Exception e){
    
    
                System.out.println("服务器读取异常!");
            }
        }).start();

        //主线程本身 - 死循环 写入
        Scanner scanner = new Scanner(System.in);
        while(true){
    
    
            System.out.println("请输入需要群发的消息:");
            String content = scanner.next();

            //String -> byte[] -> ByteBuffer
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 10);
            byteBuffer.put(content.getBytes(), 0, content.getBytes().length);
            byteBuffer.flip();
            socketChannel.write(byteBuffer);
        }
    }
}

四、Netty的基本使用

1、添加依赖

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.49.Final</version>
</dependency>

Netty线程模型

* Netty的线程模型
* 1、单线程模型 - 1个线程处理所有客户端的连接和数据读取
* 2、多线程模型 - 1个线程处理客户端的连接、线程池处理客户端数据的读写
* 3、主从线程池模型 - 主线程池处理客户端的连接、从线程池处理客户端数据的读写

4.1 Netty的ChannelHandler

入站消息处理器:ChannelInboundHandlerAdapter(SimpleChannelInboundHandler)
出站消息处理器:ChannelOutboundHandlerAdapter

处理器链:

在这里插入图片描述

编解码器:

什么是编解码器?

编解码器就是netty用来对消息格式转换的工具类

本质上编解码器就是入站出站消息的处理器。解码器 -> 入站消息,编码器 -> 出站消息

常用的编码解码器

StringDecoder - String的解码器

StringEncoder - String的编码器

LineBasedFrameDecoder - 按照行进行拆包的解码器

TCP的拆包和粘包

什么是拆包?-在一个TCP请求中,一个完整的消息,可能被拆分成多个消息处理
什么是粘包?-在一个TCP请求中,多个消息可能被粘在一起,成为一个消息处理

4.2 Netty对Http协议的支持

什么是HTTP协议?

本质上就是一个按照固定规则编写的字符串(请求行、请求头、请求体,响应码,响应头,响应体)

Netty处理Http请求的编解码器

Http服务端使用:
HttpRequestDecoder - Http请求的解码器
HttpResponseEncoder - Http响应的编码器
HttpServerCodec - 上面两个编解码器的集合体

Http客户端使用:
HttpRequestEncoder - Http请求的编码器
HttpResponseDocoder - Http响应的解码器
HttpClientCodec - 上面两个编解码器的集合体

使用了HttpServerCodec 之后,消息就会被拆解成为:

HttpRequest(请求行、请求头)、HttpContent、LastHttpContent(请求体)

Netty编写Http文件服务器

package com.qf.httpfileserver;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.stream.ChunkedWriteHandler;

/**
 * 基于Netty实现的一个HTTP文件服务器
 */
public class HttpFileServer {
    
    

    public static void main(String[] args) {
    
    
        EventLoopGroup master = new NioEventLoopGroup();
        EventLoopGroup slave = new NioEventLoopGroup();

        ServerBootstrap serverBootstrap = new ServerBootstrap()
                .group(master, slave)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer() {
    
    
                    @Override
                    protected void initChannel(Channel ch) throws Exception {
    
    

                        //自定义处理器
                        ChannelPipeline pipeline = ch.pipeline();

                        //添加分片文件处理的编码器
                        pipeline.addLast(new ChunkedWriteHandler());

                        //Http服务器使用
                        //一旦使用了Http的编解码器,后续的消息就会变成3个对象,HttpRequest,HttpContent(N个),LastHttpContent
                        pipeline.addLast(new HttpServerCodec());
                        //这是一个Http请求的聚合器,该聚合器会将所有的HttpRequest、HttpContent、LastHttpContent聚合成为一个FullHttpRequest对象
                        pipeline.addLast(new HttpObjectAggregator(10 * 1024 * 1024));
                        pipeline.addLast(new MyFileChannelHandler());
                    }
                });

        //绑定端口
        try {
    
    
            serverBootstrap.bind(8080).sync();
            System.out.println("端口绑定成功,服务已经启动!");
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}
package com.qf.httpfileserver;

import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.handler.stream.ChunkedFile;

import java.io.File;
import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.Arrays;

/**
 * Http请求处理器
 */
public class MyFileChannelHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
    
    

    //本地开发的路径url
    private String path = "C:\\worker\\Linux";

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
    
    

        //获得请求类型
        HttpMethod method = request.method();

        //只接受Get请求
        if (!method.name().equalsIgnoreCase("GET")) {
    
    
            //非GET请求
            setError(ctx, "请求类型异常!");
            return;
        }

        //获得请求的URL
        String uri = request.uri();
        //解码
        uri = URLDecoder.decode(uri, "UTF-8");
        System.out.println("请求的url:" + uri);
        //判断请求的uri是否存在
        File file = new File(path, uri);
        if (!file.exists()){
    
    
            //请求的路径不存在
            setError(ctx, "请求的路径不存在,请不要乱来!");
            return;
        }

        //判断请求的文件类型
        if(file.isFile()){
    
    
            //请求的是文件,进行下载
            fileHandler(ctx, file);
        } else {
    
    
            //请求的是文件夹,返回该文件夹下的文件列表
            dirHandler(ctx, file, uri);
        }
    }

    /**
     * 文件的处理方式
     * @param ctx
     * @param file
     */
    private void fileHandler(ChannelHandlerContext ctx, File file){
    
    
        //下载该文件 - 读取该文件

        //编写响应行 响应头
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
        //响应体的内容
        if (file.getName().endsWith(".jpg") || file.getName().endsWith(".png")) {
    
    
            response.headers().add("Content-Type", "image/jpeg");
        } else {
    
    
            response.headers().add("Content-Type", "application/octet-stream");
        }
        //响应体的大小
        response.headers().add("Content-Length", file.length());
        //返回响应头和响应码给客户端
        ctx.writeAndFlush(response);

        //下载的文件(响应体)分块传递给客户端
        try {
    
    
            ChunkedFile cFile = new ChunkedFile(file, 1024 * 10);
            ChannelFuture channelFuture = ctx.writeAndFlush(cFile);
            channelFuture.addListener(new ChannelFutureListener() {
    
    
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
    
    
                    if (future.isSuccess()){
    
    
                        System.out.println("文件下载完成!");
                        ctx.close();
                    }
                }
            });

        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }

    /**
     * 路径的处理方式
     */
    private void dirHandler(ChannelHandlerContext ctx, File file, String uri){
    
    
        StringBuilder sb = new StringBuilder();
        sb.append("<ul>");
        
        //获取该路径下的所有子文件
        File[] files = file.listFiles();
        Arrays.stream(files).forEach(f -> {
    
    
            sb.append("<li><a href='").append(uri.equals("/") ? "" : uri).append("/").append(f.getName()).append("'>")
                    .append("(").append(f.isFile() ? "文件" : "文件夹").append(")")
                    .append(f.getName()).append("</a></li>");
        });

        sb.append("</ul>");

        //响应给客户端
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
        response.headers().add("Content-Type", "text/html;charset=utf-8");
        response.content().writeBytes(sb.toString().getBytes(Charset.forName("UTF-8")));
        //返回响应
        ctx.writeAndFlush(response);
        //关闭连接
        ctx.close();
    }

    /**
     * 返回错误页面
     */
    private void setError(ChannelHandlerContext ctx, String errorMsg) {
    
    
        //响应给客户端
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST);
        response.headers().add("Content-Type", "text/html;charset=utf-8");
        response.content().writeBytes(errorMsg.getBytes(Charset.forName("UTF-8")));
        //返回响应
        ctx.writeAndFlush(response);
        //关闭连接
        ctx.close();
    }
}

4.3 Netty对WebSocket协议的支持

什么是WebSocket协议?

WebSocket是一种基于TCP协议的长连接协议,简单来说,就是客户端和服务器可以随意发送消息。

优势:可以直接在浏览器和服务器之间构建长连接(也支持程序和程序之间构建长连接)

WebSocket协议的构建构成

在这里插入图片描述

WebSocket的数据帧

数据帧:
文本帧
二进制帧
文本+二进制帧

状态帧:
ping帧
pong帧
close帧

Netty搭建一个WebSocket服务器

服务端:

package com.qf.websocket;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;

public class WebSocketServer {
    
    

    public static void main(String[] args) {
    
    
        EventLoopGroup master = new NioEventLoopGroup();
        EventLoopGroup slave = new NioEventLoopGroup();

        ServerBootstrap serverBootstrap = new ServerBootstrap()
                .group(master, slave)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer() {
    
    
                    @Override
                    protected void initChannel(Channel ch) throws Exception {
    
    

                        //自定义处理器
                        ChannelPipeline pipeline = ch.pipeline();

                        //Http服务器使用
                        //一旦使用了Http的编解码器,后续的消息就会变成3个对象,HttpRequest,HttpContent(N个),LastHttpContent
                        pipeline.addLast(new HttpServerCodec());
                        //这是一个Http请求的聚合器,该聚合器会将所有的HttpRequest、HttpContent、LastHttpContent聚合成为一个FullHttpRequest对象
                        pipeline.addLast(new HttpObjectAggregator(10 * 1024 * 1024));
                        //WebSocket升级握手的编解码器
                        //该编解码器的作用
                        //1、进行websocket的握手升级
                        //2、自动处理客户端发送的所有状态帧
                        pipeline.addLast(new WebSocketServerProtocolHandler("/"));
                        //后续的消息,都是数据帧
                        pipeline.addLast(new MyWebSocketHandler());
                    }
                });

        //绑定端口
        try {
    
    
            serverBootstrap.bind(8080).sync();
            System.out.println("端口绑定成功,服务已经启动!");
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}

客户端(浏览器):

var ws;

//连接服务器
function conn(){
    
    
	if(window.WebSocket){
    
    
		
		//浏览器支持WebSocket协议
		ws = new WebSocket("ws://127.0.0.1:8080/");
		//设置websocket的回调方法
		ws.onopen = function (){
    
    
			//websocket连接成功后触发
			console.log("服务器连接成功!");
			$("#chatlist").append("<font color='green'>服务器连接成功!</font><br/>");
		}
		
		ws.onerror = function(){
    
    
			//连接异常时触发
			console.log("服务器连接异常!!");
			$("#chatlist").append("<font color='red'>服务器连接异常!</font><br/>");
		}
		
		ws.onclose = function(){
    
    
			//连接关闭时触发
			console.log("服务器连接已经关闭!!");
			$("#chatlist").append("<font color='red'>服务器连接关闭!</font><br/>");
		}
		
		ws.onmessage = function(data){
    
    
			//服务器给客户端发消息时触发
			console.log("接收到服务器的消息:" + data.data);
			$("#chatlist").append("<font color='blueviolet'>服务器:" + data.data + "</font><br/>");
		}
		
	} else {
    
    
		alert("骚瑞,浏览器不支持WebSocket协议,请换个电脑!");
	}
}

/**
 * 发送消息给服务器
 */
function sendMsg(){
    
    
	var text = $("#sendInput").val();
	$("#sendInput").val("");
	
	//将内容写入div
	$("#chatlist").append("<font color='blue'>我:" + text + "</font><br/>");
	
	ws.send(text);
}

客户端的重连与心跳机制:

重连的实现:

//重连方法
function reconn(){
    
    
	
	console.log("重新连接服务器!!!");
	setTimeout(function(){
    
    
		
		//重连
		conn();
		
	}, 10000);
}

......
ws.onclose = function(){
    
    
	//连接关闭时触发
	console.log("服务器连接已经关闭!!");
				
	//开始重连
	reconn();
}

心跳的实现:

1、什么时候发送心跳?
2、如何循环发送心跳?
3、如果收不到心跳回复,如何关闭连接进行重连?
4、在线要发送心跳,离线就停止发送心跳

//定时关闭方法
var closeTimeout;
function closeConn(){
    
    
	
	closeTimeout = setTimeout(function(){
    
    
		
		//关闭服务器的连接
		ws.close();
		
	}, 10000);
}


//心跳的实现
var heartTimeout;
function heart(){
    
    
	
	//循环发送心跳
	heartTimeout = setTimeout(function(){
    
    
		
		console.log("发送心跳信息.....");
		ws.send("heart");
		
		heart();
	}, 5000);
	
}


...
//设置websocket的回调方法
ws.onopen = function (){
    
    
	//websocket连接成功后触发
	console.log("服务器连接成功!");
	
			
	//开始发送心跳
	heart();
	//定时关闭连接
	closeConn();
}

...
ws.onclose = function(){
    
    
	//连接关闭时触发
	console.log("服务器连接已经关闭!!");

			
	//停止心跳发送
	if(heartTimeout){
    
    
		clearTimeout(heartTimeout);
	}		
}

4.4 Netty的集群构建

Netty集群的搭建:

在这里插入图片描述

Netty服务器Channel管理的方案:
在这里插入图片描述

Netty集群发行消息的方案:

在这里插入图片描述

五、消息中心的设计方案与实现

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_44949002/article/details/119721210