Java實(shí)戰(zhàn)之用springboot+netty實(shí)現(xiàn)簡單的一對(duì)一聊天
一、引入pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.chat.info</groupId>
<artifactId>chat-server</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.33.Final</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.56</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
二、創(chuàng)建netty 服務(wù)端
package com.chat.server;
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 lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Component
@Slf4j
public class ChatServer {
private EventLoopGroup bossGroup;
private EventLoopGroup workGroup;
private void run() throws Exception {
log.info("開始啟動(dòng)聊天服務(wù)器");
bossGroup = new NioEventLoopGroup(1);
workGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChatServerInitializer());
//啟動(dòng)服務(wù)器
ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
log.info("開始啟動(dòng)聊天服務(wù)器結(jié)束");
channelFuture.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
/**
* 初始化服務(wù)器
*/
@PostConstruct()
public void init() {
new Thread(() -> {
try {
run();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
@PreDestroy
public void destroy() throws InterruptedException {
if (bossGroup != null) {
bossGroup.shutdownGracefully().sync();
}
if (workGroup != null) {
workGroup.shutdownGracefully().sync();
}
}
}
package com.chat.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;
public class ChatServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
//使用http的編碼器和解碼器
pipeline.addLast(new HttpServerCodec());
//添加塊處理器
pipeline.addLast(new ChunkedWriteHandler());
pipeline.addLast(new HttpObjectAggregator(8192));
pipeline.addLast(new WebSocketServerProtocolHandler("/chat"));
//自定義handler,處理業(yè)務(wù)邏輯
pipeline.addLast(new ChatServerHandler());
}
}
package com.chat.server;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.chat.config.ChatConfig;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.AttributeKey;
import lombok.extern.slf4j.Slf4j;
import java.time.LocalDateTime;
@Slf4j
public class ChatServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {
//傳過來的是json字符串
String text = textWebSocketFrame.text();
JSONObject jsonObject = JSON.parseObject(text);
//獲取到發(fā)送人的用戶id
Object msg = jsonObject.get("msg");
String userId = (String) jsonObject.get("userId");
Channel channel = channelHandlerContext.channel();
if (msg == null) {
//說明是第一次登錄上來連接,還沒有開始進(jìn)行聊天,將uid加到map里面
register(userId, channel);
} else {
//有消息了,開始聊天了
sendMsg(msg, userId);
}
}
/**
* 第一次登錄進(jìn)來
*
* @param userId
* @param channel
*/
private void register(String userId, Channel channel) {
if (!ChatConfig.concurrentHashMap.containsKey(userId)) { //沒有指定的userId
ChatConfig.concurrentHashMap.put(userId, channel);
// 將用戶ID作為自定義屬性加入到channel中,方便隨時(shí)channel中獲取用戶ID
AttributeKey<String> key = AttributeKey.valueOf("userId");
channel.attr(key).setIfAbsent(userId);
}
}
/**
* 開發(fā)發(fā)送消息,進(jìn)行聊天
*
* @param msg
* @param userId
*/
private void sendMsg(Object msg, String userId) {
Channel channel1 = ChatConfig.concurrentHashMap.get(userId);
if (channel1 != null) {
channel1.writeAndFlush(new TextWebSocketFrame("服務(wù)器時(shí)間" + LocalDateTime.now() + " " + msg));
}
}
/**
* 一旦客戶端連接上來,該方法被執(zhí)行
*
* @param ctx
* @throws Exception
*/
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
log.info("handlerAdded 被調(diào)用" + ctx.channel().id().asLongText());
}
/**
* 斷開連接,需要移除用戶
*
* @param ctx
* @throws Exception
*/
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
removeUserId(ctx);
}
/**
* 移除用戶
*
* @param ctx
*/
private void removeUserId(ChannelHandlerContext ctx) {
Channel channel = ctx.channel();
AttributeKey<String> key = AttributeKey.valueOf("userId");
String userId = channel.attr(key).get();
ChatConfig.concurrentHashMap.remove(userId);
log.info("用戶下線,userId:{}", userId);
}
/**
* 處理移除,關(guān)閉通道
*
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
三、存儲(chǔ)用戶channel 的map
package com.chat.config;
import io.netty.channel.Channel;
import java.util.concurrent.ConcurrentHashMap;
public class ChatConfig {
public static ConcurrentHashMap<String, Channel> concurrentHashMap = new ConcurrentHashMap();
}
四、客戶端html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
var socket;
//判斷當(dāng)前瀏覽器是否支持websocket
if (window.WebSocket) {
//go on
socket = new WebSocket("ws://localhost:7000/chat");
//相當(dāng)于channelReado, ev 收到服務(wù)器端回送的消息
socket.onmessage = function (ev) {
var rt = document.getElementById("responseText");
rt.value = rt.value + "\n" + ev.data;
}
//相當(dāng)于連接開啟(感知到連接開啟)
socket.onopen = function (ev) {
var rt = document.getElementById("responseText");
rt.value = "連接開啟了.."
var userId = document.getElementById("userId").value;
var myObj = {userId: userId};
var myJSON = JSON.stringify(myObj);
socket.send(myJSON)
}
//相當(dāng)于連接關(guān)閉(感知到連接關(guān)閉)
socket.onclose = function (ev) {
var rt = document.getElementById("responseText");
rt.value = rt.value + "\n" + "連接關(guān)閉了.."
}
} else {
alert("當(dāng)前瀏覽器不支持websocket")
}
//發(fā)送消息到服務(wù)器
function send(message) {
if (!window.socket) { //先判斷socket是否創(chuàng)建好
return;
}
if (socket.readyState == WebSocket.OPEN) {
//通過socket 發(fā)送消息
var sendId = document.getElementById("sendId").value;
var myObj = {userId: sendId, msg: message};
var messageJson = JSON.stringify(myObj);
socket.send(messageJson)
} else {
alert("連接沒有開啟");
}
}
</script>
</head>
<body>
<h1 th:text="${userId}"></h1>
<input type="hidden" th:value="${userId}" id="userId">
<input type="hidden" th:value="${sendId}" id="sendId">
<form onsubmit="return false">
<textarea name="message" style="height: 300px; width: 300px"></textarea>
<input type="button" value="發(fā)送" onclick="send(this.form.message.value)">
<textarea id="responseText" style="height: 300px; width: 300px"></textarea>
<input type="button" value="清空內(nèi)容" onclick="document.getElementById('responseText').value=''">
</form>
</body>
</html>
五、controller 模擬用戶登錄以及要發(fā)送信息給誰
package com.chat.controller;
import com.chat.config.ChatConfig;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class ChatController {
@GetMapping("login")
public String login(Model model, @RequestParam("userId") String userId, @RequestParam("sendId") String sendId) {
model.addAttribute("userId", userId);
model.addAttribute("sendId", sendId);
return "chat";
}
@GetMapping("sendMsg")
public String login(@RequestParam("sendId") String sendId) throws InterruptedException {
while (true) {
Channel channel = ChatConfig.concurrentHashMap.get(sendId);
if (channel != null) {
channel.writeAndFlush(new TextWebSocketFrame("test"));
Thread.sleep(1000);
}
}
}
}
六、測(cè)試
登錄成功要發(fā)消息給bbb
登錄成功要發(fā)消息給aaa


到此這篇關(guān)于Java實(shí)戰(zhàn)之用springboot+netty實(shí)現(xiàn)簡單的一對(duì)一聊天的文章就介紹到這了,更多相關(guān)springboot+netty實(shí)現(xiàn)一對(duì)一聊天內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實(shí)戰(zhàn)之鮮花商城系統(tǒng)的實(shí)現(xiàn)
這篇文章主要介紹了如何利用Java語言實(shí)現(xiàn)鮮花商城系統(tǒng),文中采用的技術(shù)有Spring、SpringMVC、Mybatis、JSP等,感興趣的小伙伴可以了解一下2022-05-05
springboot程序啟動(dòng)慢-未配置hostname的解決
這篇文章主要介紹了springboot程序啟動(dòng)慢-未配置hostname的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
JAVA反射機(jī)制中g(shù)etClass和class對(duì)比分析
這篇文章主要介紹了JAVA反射機(jī)制中g(shù)etClass和class對(duì)比分析,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11
IDEA?Debug過程中使用Drop?Frame或Reset?Frame實(shí)現(xiàn)操作回退的方法
在IDEA中就提供了一個(gè)幫助你回退代碼的機(jī)會(huì),但這個(gè)方法并不是萬能的,好了,下面就來具體說說IDEA?Debug過程中使用Drop?Frame或Reset?Frame實(shí)現(xiàn)操作回退的方法,感興趣的朋友一起看看吧2022-04-04
JAVA編程實(shí)現(xiàn)TCP網(wǎng)絡(luò)通訊的方法示例
這篇文章主要介紹了JAVA編程實(shí)現(xiàn)TCP網(wǎng)絡(luò)通訊的方法,簡單說明了TCP通訊的原理并結(jié)合具體實(shí)例形式分析了java實(shí)現(xiàn)TCP通訊的步驟與相關(guān)操作技巧,需要的朋友可以參考下2017-08-08
Java?easyExcel的復(fù)雜表頭多級(jí)表頭導(dǎo)入
最近在項(xiàng)目開發(fā)中遇到的一個(gè)excel復(fù)雜表頭的導(dǎo)入數(shù)據(jù)庫操作,下面這篇文章主要給大家介紹了關(guān)于Java?easyExcel的復(fù)雜表頭多級(jí)表頭導(dǎo)入的相關(guān)資料,需要的朋友可以參考下2022-06-06

