用户提醒功能的设计与实现

一、消息提醒的难点

1、当用户提醒设置数据量庞大时的处理
2、提醒消息的实时性
3、提醒功能的实现

二、消息中心的设计与搭建

方案一:Socket(BIO)


#服务端
public class BioServer {


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


    //创建socket服务端
    ServerSocket serverSocket = new ServerSocket(8080);


    //等待客户端的连接,返回的socket对象就是和客户端的连接对象
    Socket socket = serverSocket.accept();//阻塞的方法,如果没有客户端连接,程序不会往下执行
    System.out.println("有客户端连接了服务器!!!");




    new Thread(){
        @Override
        public void run() {
            while(true) {
                try {
                    //服务器给客户端回复消息
                    System.out.println("请输入需要发送的内容:");
                    Scanner scanner = new Scanner(System.in);
                    String result = scanner.next();


                    byte[] bytes = result.getBytes("utf-8");
                    OutputStream out = socket.getOutputStream();
                    out.write(bytes);
                    out.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }.start();




    while(true){
        //获取客户端的数据
        InputStream in = socket.getInputStream();
        //从客户端中读取数据,放入10kb的字节数组中
        byte[] buffer = new byte[1024 * 10];
        int len = in.read(buffer);//该方法是一个阻塞方法,如果客户端没有给服务器发消息,这服务器会阻塞在这行代码上
        //打印byte数组
        String content = new String(buffer, 0, len, "utf-8");
        System.out.println("收到客户端的消息:" + content);
    }
}

}

#客户端
public class BioClient {


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


    new Thread(){
        @Override
        public void run() {
            while(true) {
                try {
                    //给服务器发送消息
                    Scanner scanner = new Scanner(System.in);
                    System.out.println("请输入发送的内容:");
                    String content = scanner.next();


                    byte[] bytes = content.getBytes("utf-8");
                    OutputStream out = socket.getOutputStream();
                    out.write(bytes);
                    out.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }.start();


    while(true) {
        //获取服务器回复的数据
        InputStream in = socket.getInputStream();
        //从客户端中读取数据,放入10kb的字节数组中
        byte[] buffer = new byte[1024 * 10];
        int len = in.read(buffer);//该方法是一个阻塞方法,如果客户端没有给服务器发消息,这服务器会阻塞在这行代码上
        //打印byte数组
        String result = new String(buffer, 0, len, "utf-8");
        System.out.println("收到服务器回复的消息:" + result);
    }
}

}

问题:服务器必须给每个客户端准备一个线程,用来处理客户端发送过来的消息,如果客户端一多,则服务器的线程资源就比较吃紧了

方案二:NIO(no-block I/O 非阻塞IO, JDK1.4之后提供的)

NIO的核心 - 多路复用器

方案三:Netty(基于NIO封装的一个高性能的网络编程框架)

Netty支持的三种线程模型:
单线程模型:只有一个线程处理客户端的连接和读写操作
多线程模型:同时又多个线程处理客户端的连接和读写
主从线程模型:有一个主线程池只负责客户端的连接,另一个从线程池用来处理客户端的读写

Netty的demo:

#服务端
public static void main(String[] args) {


//主线程池 - 负责处理客户端的连接操作
EventLoopGroup master = new NioEventLoopGroup();


//从线程池 - 负责处理客户端的读写操作
EventLoopGroup slave = new NioEventLoopGroup();




//通过引导类创建服务端
ServerBootstrap serverBootstrap = new ServerBootstrap();
//初始化引导类 - 基本配置
serverBootstrap
        //配置netty的线程模型,主从线程池
        .group(master, slave)
        //配置服务端的channel类型
        .channel(NioServerSocketChannel.class)
        //配置服务端的消息处理器
        .childHandler(new NettyServerChannelHandler());


//绑定端口 - 该绑定是一个异步处理方法
ChannelFuture future = serverBootstrap.bind(8080);
//调用该方法表示,让当前线程同步的阻塞,如果端口没有绑定完成,就不会执行后面的代码
try {
    future.sync();
    System.out.println("端口绑定成功!");
} catch (InterruptedException e) {
    e.printStackTrace();
    System.out.println("端口绑定失败!");


    //回收线程池
    master.shutdownGracefully();
    slave.shutdownGracefully();
}

}

#客户端:
EventLoopGroup eventLoops = new NioEventLoopGroup();


//引导类
Bootstrap bootstrap = new Bootstrap();


//配置引导类
bootstrap
    //多线程模型
    .group(eventLoops)
    //设置channel的类型
    .channel(NioSocketChannel.class)
    //设置消息处理器
    .handler(new NettyClientChannelHandler());


//连接服务器
ChannelFuture future = bootstrap.connect("localhost", 8080);//异步的
try {
    future.sync();//同步等待
    System.out.println("服务器连接成功!");




    //获得和服务器通讯的Channel对象
    Channel channel = future.channel();


    //发送消息
    while(true) {
        System.out.println("请输入需要发送的消息内容:");
        Scanner scanner = new Scanner(System.in);
        String content = scanner.next();


        ByteBuf byteBuf = Unpooled.buffer(1024 * 10);
        byteBuf.writeBytes(content.getBytes("utf-8"));
        //发送byteBuf
        channel.writeAndFlush(byteBuf);
    }
} catch (Exception e) {
    e.printStackTrace();
    System.out.println("服务器连接失败!");
    eventLoops.shutdownGracefully();
}

}

#ChannelHandler
入站处理器:继承SimpleChannelInboundHandler类
出站处理器:继承ChannelOutboundHandlerAdapter类

#Netty的事件处理器链(类似Java里面过滤器链)
//配置服务端的消息处理器

      .childHandler(new ChannelInitializer() {
        @Override
        protected void initChannel(Channel channel) throws Exception {
        //获得channel对应的事件处理链对象
           ChannelPipeline pipeline = channel.pipeline();


        //添加各种事件处理器
        pipeline.addLast(xxxxxx);
        pipeline.addLast(new NettyServerChannelHandler());
    }
});

netty提供了很多官方默认的事件预处理器(编解码器):
pipeline.addLast(new StringEncoder());//出站消息处理器,将String -> bytebuf
pipeline.addLast(new StringDecoder());//入站消息处理器,将bytebuf -> String

#Netty对Http的支持:

什么是http协议? - 就是一个规定好的字符串格式

GET / HTTP/1.1 - 请求行

Host: localhost:8080
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Sec-Fetch-Site: none
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: login_token=dd3d5325-7adc-48f8-bea2-944ef9c8397e - 请求头

xxxx - 请求体


实现方式:
//pipeline.addLast(new HttpRequestDecoder());//将bytebuffer(完整的http请求) -> HttpRequest(请求行、请求头)、HttpContent(请求体)
//pipeline.addLast(new HttpResponseEncoder());//将HttpRespseon(响应) -> bytebuffer(标准的http响应流)

pipeline.addLast(new HttpServerCodec());//HttpRequestDecoder + HttpResponseEncoder

//设置一个Http的聚合器(就是将HttpRequest和HttpContent组合在一起变成FullHttpRequest对象)
//简单来讲,这个事件处理器之后的所有入站消息收到的就是FullHttpRequest(完整的http请求)对象
pipeline.addLast(new HttpObjectAggregator(1024 * 1024));

#Netty对WebSocket的支持 

什么是WebSocket?- WebSocket是让网页能够和TCP服务器保持长连接的一种升级协议

WebSocket的数据类型(帧):
数据帧:
        1、文本数据帧
        2、二进制数据帧
        3、混合数据帧(文本+进制帧)
状态帧:
        1、ping帧
        2、pong帧
        3、close帧

netty对Websocket的支持:
//核心的事件处理器链
pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new HttpObjectAggregator(1024 * 1024));
//设置websocket的升级处理器,该处理器可以将Http协议升级成WebSocket协议
//同时所有客户端的状态帧数据,都会默认处理掉
pipeline.addLast(new WebSocketServerProtocolHandler("/"));

你可能感兴趣的:(java)