手动实现一个Nginx加聊天室的案例
闲来无事?🔥快来拿个小案例练手
简单易懂快速上手 案例不长快快 上车! 上车 🚗GO! GO! GO! 🚗
相信通过这个案例能让你学到更多东西
关注收藏是我最大动力
代码全部粘贴下面了,如果想要一个完整的包可以私信我,我也会把打包的链接留在后面
来介绍一下这个小案例:
1.服务器
- 实现一个资源通过http映射类似Nginx
我们的案例都在我们自己实现的服务器上面运行,没有使用Tomcat Nginx Spring等第三方
2.聊天业务
- 实现单聊
- 实现群聊
- 房间人数实时检查
- 实现离线消息
- 实现账号冲突强制下线
- 实现心跳在线检测
案例简介📄 Netty是Java的一个通信框架,是基于NIO来实现的,有许多的java中间件都是Netty来开发的,Netty的高性能是大家有目共睹,大家都认可的
本案例都是通过Netty来编写的,如果有netty基础可能更好理解一些,如果没有不要慌,如有需要私信我,马上更新Netty基础
这个案例分为两个阶段:
话不多说开始编写代码
服务器开发
环境准备
先导入pom的配置 Netty的包,复制下来放到pom文件里就行了
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.75.Final</version>
</dependency>
环境准备就这么多,没错就这么多!都手写的还要这么多框架干嘛
代码编写
服务器后端代码
给大家简单介绍一下原理,非常简单
是通过Netty 实现Http 协议,然后根据浏览器发来需要的文件地址,我们通过IO流 把文件字节码读取出来,然后再通过Http 协议发送到浏览器
下面是详细介绍,最后有完整代码
先写一个想要映射的文件夹的路径 我想要映射这个文件夹
创建Netty的启动类,并绑定端口
handler处理器操作http并进行io操作把数据返回给浏览器
这样我们的服务器后端代码就结束了
来测试 在浏览器里输入我们的地址 localhost:8081 回车
OHHHHHHHHHHHHHHHHHH!
OHHHHHHHHHHHHHHHHHH! 我们的页面加载出来了,我写的路径为 / 默认加载index.html
服务器后端的全部代码,简短精悍
public static void main(String[] args) {
String baseUrl="C:\\Users\\Alie\\Desktop\\demo";
new ServerBootstrap()
.group(new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
nioSocketChannel.pipeline().addLast(new HttpServerCodec());
nioSocketChannel.pipeline().addLast(new SimpleChannelInboundHandler<HttpRequest>() {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpRequest httpRequest) throws Exception {
System.out.println(httpRequest.uri());
String url=httpRequest.uri();
url=url.equals("/")?"/index.html":url;
DefaultFullHttpResponse defaultHttpResponse=new DefaultFullHttpResponse(httpRequest.protocolVersion(), HttpResponseStatus.OK);
FileInputStream fileInputStream=new FileInputStream(new File(baseUrl+url));
byte[] bytes=new byte[1024];
int len=0;
while (-1!=(len=fileInputStream.read(bytes))) {
defaultHttpResponse.content().writeBytes(bytes,0,len);
}
fileInputStream.close();
channelHandlerContext.writeAndFlush(defaultHttpResponse);
channelHandlerContext.close();
}
});
}
}).bind(8081);
}
聊天室前端代码
由于前端代码比较简单就直接给大家,直接用就好了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>图片提示</title>
<style type="text/css">
body {
margin: 0;
padding: 40px;
}
li {
list-style: none;
float: left;
display: inline;
margin-right: 10px;
border: 1px solid #AAAAAA;
}
.room {
float: left;
width: 100%;
margin-top: 30px;
height: 400px;
border:1px solid black;
}
.room span{
font-size: 14px;
}
/* tooltip */
#tooltip {
position: absolute;
border: 1px solid #ccc;
background: #333;
padding: 2px;
display: none;
color: #fff;
}
</head>
<body>
<h3>图片提示效果</h3>
<div class="room">
<div class="top">
定义用户名:<input type="text" id="userid"><input type="button" id="connt" value="立即连接">
</div>
<br>
接收用户:<input type="text" id="toargetid"> <input type="text" id="msgContent">
<input type="button" id="send" value="发送消息">
<hr>
接收消息:
<div id="receiveMsg"></div>
</div>
<div class="room">
<div class="top">
房间号:<input type="text" id="roomid"><input type="button" id="jionroom" value="加入房间">
</div>
<br>
<input type="text" id="roommsgContent"><input type="button" id="roomsend" value="发送消息">
<hr>
接收消息:
<div id="roomreceiveMsg"></div>
</div>
<div class="video">
</div>
</body>
<script>
messageObj = {
userid: "",
toTarget: "",
msg: "",
msgType: 0,
singe: "",
}
var ChatType = {
CHANNEL_INIT: 0,
CHAT_MSG: 1,
CHAT_ROOM_MSG: 2,
SYSTEM_MSG: 3,
JOIN_ROOM: 4,
PING:5
}
function initWS() {
mes = messageObj
mes.msgType = ChatType.CHANNEL_INIT
return JSON.stringify(mes)
}
function sendMsg(target, msg) {
mes = messageObj
mes.toTarget=target
mes.msgType=ChatType.CHAT_MSG
mes.msg=msg
return JSON.stringify(mes)
}
function sendMsgTORoom(target, msg) {
mes = messageObj
mes.toTarget=target
mes.msgType=ChatType.CHAT_ROOM_MSG
mes.msg=msg
return JSON.stringify(mes)
}
function addRoom(target) {
mes = messageObj
mes.toTarget=target
mes.msgType=ChatType.JOIN_ROOM
return JSON.stringify(mes)
}
function ping() {
mes = messageObj
mes.msgType=ChatType.PING
return JSON.stringify(mes)
}
function dealMsg(data) {
var type=data.msgType
var rece=document.getElementById("receiveMsg")
var roomrece=document.getElementById("roomreceiveMsg")
if(type==ChatType.SYSTEM_MSG){
rece.innerHTML+="<span style='color: rgb(255, 118, 14);' >[系统通知]"+data.msg+"</span><br>"
}else if(type==ChatType.CHANNEL_INIT){
roomrece.innerHTML+="<span>["+data.userid+"]:"+data.msg+"</span><br>"
}else if(type==ChatType.CHAT_ROOM_MSG){
roomrece.innerHTML+="<span>["+data.userid+"]:"+data.msg+"</span><br>"
}else if(type==ChatType.CHAT_MSG){
rece.innerHTML+="<span>["+data.userid+"]:"+data.msg+"</span><br>"
}else if(type==ChatType.PING){
websocket.send(ping())
}
}
document.getElementById("send").onclick = function () {
var str = document.getElementById("msgContent").value
var target=document.getElementById("toargetid").value
var tr=sendMsg(target,str)
console.log(tr);
websocket.send(tr)
}
document.getElementById("connt").onclick = function () {
var userid = document.getElementById("userid").value
messageObj.userid = userid
InitWebSocket()
}
document.getElementById("jionroom").onclick = function () {
var roomid=document.getElementById("roomid").value
websocket.send(addRoom(roomid))
}
document.getElementById("roomsend").onclick = function () {
var str = document.getElementById("roommsgContent").value
var target=document.getElementById("roomid").value
var tr=sendMsgTORoom(target,str)
websocket.send(tr)
}
function InitWebSocket() {
websocket = new WebSocket("ws://192.168.43.237:8080/ws")
websocket.onopen = function () {
websocket.send(initWS())
}
websocket.onclose = function () {
console.log("连接断开");
}
websocket.onerror = function () {
console.log("连接出错");
}
websocket.onmessage = function (e) {
var data=JSON.parse(e.data)
dealMsg(data)
console.log(e);
console.log(data);
}
}
</script>
</html>
聊天室后端代码
配置环境文件
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.75.Final</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.72</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.10</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
我先把代码和结构发出来按照这个结构还原后就可直接运行 server类
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class Server {
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup group1 = new NioEventLoopGroup();
NioEventLoopGroup group2 = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap()
.group(group1, group2)
.channel(NioServerSocketChannel.class)
.childHandler(new ConnInit());
ChannelFuture channel = serverBootstrap.bind(8080).sync();
channel.channel().closeFuture().sync();
}finally {
group1.shutdownGracefully();
group2.shutdownGracefully();
}
}
}
Conninit类 连接初始化
package myChatRoom;
import io.netty.channel.*;
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.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import myChatRoom.pojo.MyMessage;
public class ConnInit extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new IdleStateHandler(10,5,0));
pipeline.addLast(new ChannelDuplexHandler(){
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
IdleStateEvent event=(IdleStateEvent) evt;
if(event.state()==IdleState.WRITER_IDLE){
MyMessage myMessage = new MyMessage();
myMessage.setMsgType(ChatType.PING);
ctx.channel().writeAndFlush(new TextWebSocketFrame(myMessage.toString()));
System.out.println("ping");
}
if(event.state()==IdleState.READER_IDLE){
System.out.println("10s未读到数据");
}
super.userEventTriggered(ctx, evt);
}
});
pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new ChunkedWriteHandler());
pipeline.addLast(new HttpObjectAggregator(1024*64));
pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
pipeline.addLast(new MyChatHandler());
}
}
ChatType类 指定消息类型
public class ChatType{
public static final int CHANNEL_INIT=0;
public static final int CHAT_MSG=1;
public static final int CHAT_ROOM_MSG=2;
public static final int SYSTEM_MSG=3;
public static final int JOIN_ROOM=4;
public static final int PING=5;
}
MyMessage 消息实体类
import com.alibaba.fastjson.JSON;
import lombok.Data;
@Data
public class MyMessage {
String userid;
String toTarget;
String msg;
int msgType;
int singe;
@Override
public String toString() {
return JSON.toJSONString(this);
}
}
MyChatHandler类 我们自己的聊天消息处理类
package myChatRoom;
import com.alibaba.fastjson.JSON;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import myChatRoom.server.Impl.ChatRoomHandlerImpl;
import myChatRoom.server.Impl.ConnectHandlerImpl;
import myChatRoom.pojo.MyMessage;
import myChatRoom.server.Impl.MsgHandlerImpl;
public class MyChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
ConnectHandlerImpl connectHandler = new ConnectHandlerImpl();
ChatRoomHandlerImpl chatRoomHandler=new ChatRoomHandlerImpl();
MsgHandlerImpl msgHandler=new MsgHandlerImpl();
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame frame) throws Exception {
String text = frame.text();
System.out.println(text);
MyMessage myMessage = JSON.parseObject(text, MyMessage.class);
int msgType = myMessage.getMsgType();
if(msgType==ChatType.CHANNEL_INIT){
connectHandler.addUserConnect(myMessage.getUserid(), ctx.channel());
msgHandler.checkMsg(myMessage.getUserid());
}else if(msgType==ChatType.CHAT_MSG){
msgHandler.send(myMessage);
}else if (msgType==ChatType.CHAT_ROOM_MSG){
chatRoomHandler.sendRoomMsg(myMessage.getUserid(), myMessage);
}else if (msgType==ChatType.JOIN_ROOM){
chatRoomHandler.addUserInRoom(myMessage.getUserid(), myMessage.getToTarget());
}else if(msgType==ChatType.SYSTEM_MSG){
System.out.println("系统通知");
}
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
chatRoomHandler.leafRoom(connectHandler.getIdbyChannel().get(ctx.channel()));
connectHandler.removeConnect(ctx.channel());
System.out.println("断开连接");
}
}
server接口和实现类 1.ChatRoomHandler和ChatRoomHandlerImpl 聊天室消息处理类
package myChatRoom.server;
import myChatRoom.pojo.MyMessage;
public interface ChatRoomHandler {
void addUserInRoom(String userid,String roomid);
void sendRoomMsg(String userid, MyMessage message);
void leafRoom(String userid);
}
ChatRoomHandlerImpl 实现
package myChatRoom.server.Impl;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import myChatRoom.server.ChatRoomHandler;
import myChatRoom.pojo.MyMessage;
import java.util.*;
public class ChatRoomHandlerImpl implements ChatRoomHandler {
static Map<String, Set<String>> roomMap=new HashMap<>();
ConnectHandlerImpl connectHandler=new ConnectHandlerImpl();
@Override
public void addUserInRoom(String userid, String roomid) {
if (roomMap.containsKey(roomid)) {
roomMap.get(roomid).add(userid);
}else {
Set<String> list=new HashSet<>();
list.add(userid);
roomMap.put(roomid,list);
}
MyMessage myMessage = new MyMessage();
myMessage.setUserid(userid);
myMessage.setToTarget(roomid);
myMessage.setMsg("[加入房间] 当前房间在线人数:"+roomMap.get(roomid).size());
sendRoomMsg(userid,myMessage);
}
@Override
public void sendRoomMsg(String userid, MyMessage message) {
String toTarget = message.getToTarget();
Set<String> list = roomMap.get(toTarget);
Map<String, Channel> connects = connectHandler.getConnects();
for (String s : list) {
Channel channel = connects.get(s);
channel.writeAndFlush(new TextWebSocketFrame(message.toString()));
}
}
@Override
public void leafRoom(String userid) {
roomMap.forEach((k,v)->{
if (v.contains(userid)) {
v.remove(userid);
MyMessage myMessage = new MyMessage();
myMessage.setUserid(userid);
myMessage.setToTarget(k);
myMessage.setMsg(userid+"[离开房间]当前房间人数"+v.size());
sendRoomMsg(userid,myMessage);
}
});
}
}
2.ConnectionHandler 用户连接处理 ConnectionHandler
package myChatRoom.server;
import io.netty.channel.Channel;
import java.util.Map;
public interface ConnectHandler {
void addUserConnect(String userid, Channel channel);
void removeConnect(Channel channel);
Map<String,Channel> getConnects();
Map<Channel,String> getIdbyChannel();
}
package myChatRoom.server.Impl;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import myChatRoom.pojo.MyMessage;
import myChatRoom.server.ConnectHandler;
import java.util.HashMap;
import java.util.Map;
public class ConnectHandlerImpl implements ConnectHandler {
static Map<String,Channel> userMap=new HashMap<>();
static Map<Channel,String> channelMap=new HashMap<>();
@Override
public void addUserConnect(String userid, Channel channel) {
MyMessage myMessage = new MyMessage();
myMessage.setMsgType(3);
if(userMap.containsKey(userid)){
Channel channel1 = userMap.get(userid);
myMessage.setMsg("账号异地登录强制下线!");
channel1.writeAndFlush(new TextWebSocketFrame(myMessage.toString()));
channel1.close();
channelMap.remove(channel1);
}
userMap.put(userid,channel);
channelMap.put(channel,userid);
myMessage.setMsg("欢迎用户:"+userid);
channel.writeAndFlush(new TextWebSocketFrame(myMessage.toString()));
}
@Override
public void removeConnect(Channel channel) {
userMap.remove(channelMap.get(channel));
channelMap.remove(channel);
}
@Override
public Map<String,Channel> getConnects() {
System.out.println(userMap);
return userMap;
}
@Override
public Map<Channel,String> getIdbyChannel() {
return channelMap;
}
}
3.MsgHandler 好友聊天消息处理 MsgHandler
package myChatRoom.server;
import myChatRoom.pojo.MyMessage;
public interface MsgHandler {
void send(MyMessage message);
}
MsgHandlerImpl实现类
package myChatRoom.server.Impl;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import myChatRoom.server.MsgHandler;
import myChatRoom.pojo.MyMessage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MsgHandlerImpl implements MsgHandler {
ConnectHandlerImpl connectHandler=new ConnectHandlerImpl();
Map<String, Channel> connects = connectHandler.getConnects();
public static Map<String, List<MyMessage>> messageQeueu=new HashMap<>();
@Override
public void send(MyMessage message) {
String toTarget = message.getToTarget();
Channel channel = connects.get(toTarget);
if (channel!=null){
channel.writeAndFlush(new TextWebSocketFrame(message.toString()));
}else {
if (messageQeueu.containsKey(message.getToTarget())) {
messageQeueu.get(message.getToTarget()).add(message);
}else {
List<MyMessage> list = new ArrayList<>();
list.add(message);
messageQeueu.put(message.getToTarget(),list);
}
}
System.out.println(message.getToTarget()+":"+channel);
}
public void checkMsg(String userid){
List<MyMessage> myMessages = messageQeueu.get(userid);
if(myMessages!=null){
Channel channel = connects.get(userid);
System.out.println(myMessages);
System.out.println(channel);
for (MyMessage myMessage : myMessages) {
channel.writeAndFlush(new TextWebSocketFrame(myMessage.toString()));
}
messageQeueu.remove(userid);
}
}
}
至此我们聊天的代码也结束了
效果测试
开始测试 输入我们的地址 localhost:8081 1.连接两个用户 成功! 2. 1,2用户互发消息 成功! 3.账号顶替下线 成功! 4.离线消息 此时右边为离线状态,让左边发消息给1,然后再登录1 登录1后立即受到消息 成功!
5,聊天室聊天
成功!
6.心跳检测 这些全部都是心跳包 也是成功了
案例到此结束
希望可以帮助到你
如果有什么问题和疑问可以私信我,我一定尽力解答
如果感觉还不错欢迎 收藏 和 关注! 后面我会持续更新
|