项目结构
导入依赖
<!-- 整合web开发依赖 -->
<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.42.Final</version>
</dependency>
application.properties
server.port=8081
spring.resources.static-locations=classpath:/templates/
spring.mvc.view.suffix=.html
ChatHandler
package com.qs.server;
import io.netty.channel.*;
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;
/**
* @author qingshi
* @date 2023/1/18 15:32
* 处理消息的handler
*/
public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame>{
// 用于记录和管理所有客户端的ChannelGroup
private static ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
// 获取客户端传输过来的消息
String content = msg.text();
System.out.println("接收到的数据:" + content);
//ctx.channel().writeAndFlush(new TextWebSocketFrame("服务器主动推送消息给指定客户端"));//给单个客户端发送消息
for(Channel channel : clients) {//给所有客户端发送消息
// 不能直接writeAndFlush收到的content字符串信息,必须封装到frame载体中输出
channel.writeAndFlush(new TextWebSocketFrame("系统消息:" + content));
}
//clients.writeAndFlush(new TextWebSocketFrame("系统消息:" + content));//给所有客户端发送消息,效果同上for循环
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
// 当客户端连接服务端之后,获取客户端的channel,并且放到ChannelGroup中去进行管理
clients.add(ctx.channel());
ChannelId id = ctx.channel().id();
System.out.println("客户端id---"+id+"---连接");
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
// 这步是多余的,当断开连接时候ChannelGroup会自动移除对应的channel
clients.remove(ctx.channel());
System.out.println(ctx.channel().id().asLongText());
}
}
WebSocketServerInitializer
package com.qs.server;
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;
/**
* @author qingshi
* @date 2023/1/18 15:29
*/
public class WebSocketServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
// 通过SocketChannel去获取对应的管道pipeline
ChannelPipeline pipeline = channel.pipeline();
// 通过管道,添加handler HttpServerCodec是netty自己提供的助手类
// 当请求到服务端时候,我们需要做解码,响应到客户端时候需要做编码
pipeline.addLast("HttpServerCodec", new HttpServerCodec());
// 对写大数据流的支持
pipeline.addLast("ChunkedWriteHandler", new ChunkedWriteHandler());
// 对HttpMessage进行聚合 聚合成FullHttpRequest或FullHttpResponse
// 几乎在netty的编程中,都会用到这个handler
pipeline.addLast("HttpObjectAggregator", new HttpObjectAggregator(1024*64));
// 以上用于支持http协议
// websocket服务器处理的协议 并且用于指定给客户端连接访问的路由:/ws
pipeline.addLast("WebSocketServerProtocolHandler", new WebSocketServerProtocolHandler("/ws"));
// 自定义的handler
pipeline.addLast(new ChatHandler());
}
}
NettyApplication
package com.qs;
import com.qs.server.WebSocketServerInitializer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class NettyApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(NettyApplication.class, args);
// 定义一对线程组 主线程组 用于接收客户端的连接请求,不做任何处理
EventLoopGroup bossGroup = new NioEventLoopGroup();
// 定义一对线程组 从线程组 主线程组会把任务丢给从线程组,让从线程组去处理
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// netty服务器的创建,ServerBootstrap是一个启动类
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup) // 设置主从线程组
.channel(NioServerSocketChannel.class) // 设置NIO双向通道类型
.childHandler(new WebSocketServerInitializer()); // 子处理器,用于处理workerGroup
// 启动server,绑定8088端口启动,并且同步等待方式启动
ChannelFuture channelFuture = serverBootstrap.bind(8088).sync();
// 监听关闭的channel, 设置为同步的方式
channelFuture.channel().closeFuture().sync();
} finally {
// 关闭我们的主线程组和从线程组
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
index.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Netty-Websocket</title>
<script type="text/javascript">
let socket;
if(window.WebSocket){
socket = new WebSocket("ws://localhost:8088/ws");
socket.onmessage = function(event){
let textarea = document.getElementById('responseText');
textarea.value += event.data+"\r\n";
//alert("服务器发送:"+event.data)
};
socket.onopen = function(event){
let textarea = document.getElementById('responseText');
textarea.value = "Netty-WebSocket服务器。。。。。。连接 \r\n";
};
socket.onclose = function(event){
let textarea = document.getElementById('responseText');
textarea.value = "Netty-WebSocket服务器。。。。。。关闭 \r\n";
};
} else {
alert("您的浏览器不支持WebSocket协议!");
}
function send(){
if(!window.WebSocket){return;}
if(socket.readyState === WebSocket.OPEN) {
let message = document.getElementById('message').value;
socket.send(message);
} else {
alert("WebSocket 连接没有建立成功!");
}
}
</script>
</head>
<body>
<form onSubmit="return false;">
<label>文本</label><input type="text" id="message" name="message" placeholder="这里输入消息" /> <br />
<br /> <input type="button" value="发送ws消息"
onClick="send()" />
<hr color="black" />
<h3>服务端返回的应答消息</h3>
<textarea id="responseText" style="width: 1024px;height: 300px;"></textarea>
</form>
</body>
</html>