用Netty+websocket实现简单的web弹幕系统
服务端代码
1. pom依赖
<!-- Netty -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.66.Final</version>
</dependency>
<!-- Netty WebSocket -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId> <!-- Use the appropriate transport for your system -->
<version>4.1.66.Final</version>
<scope>provided</scope>
</dependency>
2. NettyServer
public class DanmuServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new HttpServerCodec());
p.addLast(new HttpObjectAggregator(65536));
p.addLast(new WebSocketServerProtocolHandler("/websocket"));
p.addLast(new DanmuHandler());
}
});
Channel ch = b.bind(8080).sync().channel();
ch.closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
3. 业务Handler
public class DanmuHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
private static final CopyOnWriteArrayList<Channel> channels = new CopyOnWriteArrayList<>();
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Channel incoming = ctx.channel();
channels.add(incoming);
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
Channel incoming = ctx.channel();
channels.remove(incoming);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
// 处理收到的消息
String receivedMessage = msg.text();
System.out.println("Received message: " + receivedMessage);
String clientId = ctx.channel().id().asShortText();
// 广播消息给所有连接的客户端
for (Channel channel : channels) {
channel.writeAndFlush(new TextWebSocketFrame(clientId + ":" + receivedMessage));
}
}
}
客户端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Danmu System</title>
<style>
body {
margin: 0;
padding: 0;
}
#danmuContainer {
height: calc(100vh - 40px); /* 调整弹幕容器高度,让底部空出40px给输入框 */
overflow: hidden;
position: relative;
}
.danmuMessage {
position: absolute;
white-space: nowrap;
left: 0;
}
#inputContainer {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background-color: #f0f0f0;
padding: 10px;
box-sizing: border-box;
}
#messageInput {
width: calc(100% - 20px);
padding: 5px;
}
</style>
</head>
<body>
<div id="danmuContainer"></div>
<div id="inputContainer">
<input type="text" id="messageInput" placeholder="Type your message and press Enter">
</div>
<script>
const danmuContainer = document.getElementById("danmuContainer");
const messageInput = document.getElementById("messageInput");
let socket;
function connectWebSocket() {
socket = new WebSocket("ws://localhost:8080/websocket");
socket.onmessage = function (event) {
const receivedMessage = event.data;
displayDanmu(receivedMessage);
};
// 关闭连接时, 重连服务器
socket.onclose = function (event) {
console.error("WebSocket closed. Reconnecting...");
setTimeout(connectWebSocket, 1000); // 1秒后尝试重新连接
};
}
function sendMessage() {
const message = messageInput.value.trim();
if (message !== "") {
socket.send(message);
messageInput.value = "";
}
}
function displayDanmu(message) {
const danmuMessage = document.createElement("div");
danmuMessage.className = "danmuMessage";
danmuMessage.textContent = message;
// 设置初始位置,确保弹幕元素在屏幕最左侧开始滑动
danmuMessage.style.top = `${Math.floor(Math.random() * (danmuContainer.clientHeight - danmuMessage.clientHeight - 10))}px`;
danmuMessage.style.left = "0";
danmuContainer.appendChild(danmuMessage);
// 使用CSS动画实现滑动效果
danmuMessage.style.transition = `left 10s linear`; // 调整持续时间和缓动函数
// 设置动画结束后,移除弹幕元素
danmuMessage.addEventListener("transitionend", function() {
danmuContainer.removeChild(danmuMessage);
});
// 设置滑动终点位置
danmuMessage.style.left = `${danmuContainer.clientWidth}px`;
}
// 监听鼠标回车事件, 回车发送消息
messageInput.addEventListener("keydown", function (event) {
if (event.key === "Enter") {
sendMessage();
}
});
// 建立连接
connectWebSocket();
</script>
</body>
</html>
效果展示:浏览器访问html