演示效果:
一:Netty简介
Netty 是一个基于NIO的客户、服务器端的编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程,例如:基于TCP和UDP的socket服务开发。
Netty 是一个吸收了多种协议(包括FTP、SMTP、HTTP等各种二进制文本协议)的实现经验,并经过相当精心设计的项目。
这是官方的一个阐述,简而言之来说就是一套高性能 支持高并发场景下的通信中间件。
二:SpringBoot集成Netty
1: 新建项目 引入相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 集成Netty -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.25.Final</version>
</dependency>
2:新建Netty服务端实例 (WebSocketNettyServer)
package com.cposnettyim.cposim.im;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* @auth Lxl
* @since 2021/1/30
*/
public class WebSocketNettyServer {
public static void main(String[] args) {
//创建两个线程
NioEventLoopGroup mainGrp = new NioEventLoopGroup(); //主线程池
NioEventLoopGroup subGrp = new NioEventLoopGroup(); //从线程池
try {
//创建Netty服务器启动对象
ServerBootstrap bootstrap = new ServerBootstrap();
//创建服务器启动对象
bootstrap
//指定上述定义的主从线程
.group(mainGrp, subGrp)
//指定Netty通道类型
.channel(NioServerSocketChannel.class)
//指定通道初始化器用来加载当Channel收到消息事件后 如何进行业务处理
.childHandler(new WebsocketChannelInitializer());
//绑定服务器端口
ChannelFuture future = bootstrap.bind(9066);
//等待服务器关闭
future.channel().closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
}finally {
//关闭服务器
mainGrp.shutdownGracefully();
subGrp.shutdownGracefully();
}
}
}
3:新增Netty通道初始化器
package com.cposnettyim.cposim.im;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
/**
* @auth Lxl
* @since 2021/1/30
*
* 通道初始化器
*/
public class WebsocketChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//获取管道
ChannelPipeline pipeline = socketChannel.pipeline();
//设置http编解码器
pipeline.addLast(new HttpServerCodec());
//设置用于支持大数据流的支持
pipeline.addLast(new ChunkedWriteHandler());
//设置聚合器合器主要讲HttpMessage聚合成FullHttpRequest/Response
pipeline.addLast(new HttpObjectAggregator(1024*64));
//指定接手对应的路由信息
//必须使用ws后缀结尾的url才接收处理
pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
//设置自定义的Handler
pipeline.addLast(new MyChatHandler());
}
}
4:自定义Handlder处理数据
package com.cposnettyim.cposim.im;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @auth Lxl
* @since 2021/1/30
* 自定义Handlder
*/
public class MyChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
//用来保存所有客户端连接
private static ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
private SimpleDateFormat sdf =new SimpleDateFormat("YYYY-MM-dd HH:mm");
//当通道(channel)内有新的消息会自动调用
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
//获取客户端发送过来的文本消息
String text = msg.text();
System.out.println("接收到的消息:"+text);
for(Channel channel : clients){
String asLongText = channel.id().asLongText().substring(0,6);
channel.writeAndFlush(new TextWebSocketFrame(asLongText+"&"+sdf.format(new Date())+"&"+text));
}
}
//当有新的客户端连接服务器后 会自动调用这个方法
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
clients.add(ctx.channel());
}
}
5: 启动 测试!
1:web聊天界面默认地址:http://127.0.0.1:20999/Mychat.html
2:Netty服务地址:ws://127.0.0.1:9066/ws (前台调试)
<!DOCTYPE html>
<html lang="en">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<head>
<meta charset="UTF-8">
<title>CposIM在线聊天</title>
</head>
<link rel="icon" type="image/png" sizes="144x144" href="/icon/icon.png"/>
<link rel="apple-touch-icon" type="image/png" sizes="144x144" href="/icon/icon.png"/>
<body style="background-image: url('/img/imbk.jpg') ">
<center>
<input type="text" style="width: 420px;height: 40px;font-size: 15px" id ="message">
<input type="button" style="width:100px; height:46px;" value ="发送消息" onclick="sendMsg()">
<div style="width: 632px;height:1000px;border-color: aqua ">
<p id="server_message" style="background-color: blanchedalmond;float:left;padding-left:5px;padding-right:120px;padding-top:20px;"></p>
</div>
</center>
<script src="http://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script>
<script>
var websocket =null;
//浏览器是否支持websocket
if(window.WebSocket){
websocket = new WebSocket("ws://zvck7r.natappfree.cc/ws");
websocket.onopen = function () {
console.log("建立连接");
}
websocket.onclose = function () {
console.log("断开连接");
}
websocket.onmessage = function (e) {
console.log("接收到服务器消息:"+e.data.split("&"));
var name = e.data.split("&")[0];
var date = e.data.split("&")[1];
var text = e.data.split("&")[2];
var server_message = document.getElementById("server_message");
//"用户:"+name+
server_message.innerHTML +=" 时间: "+date+" <br/>内容: " +
" " +
"<span style='color: red'>"+text+"</span><br/>";
}
} else {
alert("暂不支持webSocket");
}
//回车键发送消息
$('#message').bind('keypress',function(event){
sendMsg()
});
function sendMsg(){
var message = document.getElementById("message");
websocket.send(message.value);
}
</script>
</body>
</html>
6:外网访问(配置内网穿透 修改地址)
为什么要提到外网访问呢? 因为这个地方容易踩坑,很多时候本地局域网测试了 然后拿到手机上一试发现点击的发送消息并没反应 其实就是本地地址需要配置成传统地址才能访问
推荐两块免费的内网穿透工具 超级好用! Natapp 和 Sunny-Ngrok 操作很便捷 可以将内网直接映射成外网访问 这样手机也能实时在线聊天了
穿透两个地址: 一个是Socket的服务地址 另外一个就是web页面访问地址了 记得穿透完以后改地址