一、消息提醒的难点
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("/"));