基于netty实现简单的聊天功能1对1(私信/群发)

9 篇文章 0 订阅
1 篇文章 0 订阅

说明:

发现写博客要好多精力,为了多留点时间晋升自己就直接点贴代码了,学习地址: https://netty.io/ 和netty官网例子https://github.com/netty/netty/tree/4.1/example/src/main/java/io/netty/example,想学的同学多跑跑官网.

我的想法

以下实现了,某个人针对某个人的私信聊天,点击发送只能是你指定的人收到消息,群发功能是所有人都能接收到消息,具体我先截下图(三个对象测试,请复制粘贴,直接硬怼)

演示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
点击开启聊天后聊天

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
演示地址:
不见得哪天失效了…有哪位大哥服务器可用…
http://112.35.164.210:18089/

聊天服务端

直接注册成springboot的bean对象了,打包直接运行

public class JiayangApplication {

    public static void main(String[] args) {
        SpringApplication.run(JiayangApplication.class, args);
    }
    @Bean
    public void chat() throws Exception{
        EventLoopGroup mainGroup = new NioEventLoopGroup();
        EventLoopGroup subGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(mainGroup,subGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new WSServerInitialzer());//初始化器
            ChannelFuture future = serverBootstrap.bind(8080).sync();
            future.channel().closeFuture().sync();
        }finally {
            mainGroup.shutdownGracefully();
            subGroup.shutdownGracefully();
        }
    }
}

聊天初始化配置

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 WSServerInitialzer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel channel) throws Exception {
        ChannelPipeline pipeline = channel.pipeline();
//        pipeline.addLast(new HttpServerCodec());
//        pipeline.addLast(new StringDecoder());
//        pipeline.addLast(new StringEncoder());
//        //webcocket基于http协议,所以要有http编解码器
//        pipeline.addLast(new HttpServerCodec());
//        //对写大数据流的支持
//        pipeline.addLast(new ChunkedWriteHandler());//支持大数据流
//        //对httpmessage进行聚合,聚合成fullhttprequest或fullhttpresponse
//        pipeline.addLast(new HttpObjectAggregator(1024*64));
//        //==========================以上是用于支持http协议支持
//        //websocket 服务器处理的协议,用于指定给客户端连接访问的路由:/ws
//        //本handler会帮你处理一些繁重的复杂的事
//        //会帮你处理握手动作 :handshaking(close,ping,pong)ping +pong = 心跳
//        //对于websocket来讲,都是frames进行传输的,不同的数据类型对应的frames是不同的
//        pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
//        //自定义的handler
//        pipeline.addLast(new ChatHandler());
        pipeline.addLast("http-decoder", new HttpServerCodec());
        // 加入ObjectAggregator解码器,作用是他会把多个消息转换为单一的FullHttpRequest或者FullHttpResponse
        pipeline.addLast("http-aggregator", new HttpObjectAggregator(65536));
        // 加入chunked 主要作用是支持异步发送的码流(大文件传输),但不专用过多的内存,防止java内存溢出
        pipeline.addLast(new ChunkedWriteHandler());
        // 加入自定义handler
        pipeline.addLast( new ChatHandler());
        // 加入webSocket的hanlder
        pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
    }
}

客户端处理消息

import com.alibaba.fastjson.JSON;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;

import java.net.URLDecoder;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 处理消息的handler
 * TextWebSocketFrame:在netty中,适用于为websocket专门处理文本的对象,frame是消息的载体
 */
public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
    //用于记录和管理所有客户端
    private static ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    // concurrent包的线程安全Map,用来存放每个客户端对应的MyWebSocket对象。(以后用数据库代替)
    private static ConcurrentHashMap<String, Channel> webSocketMap = new ConcurrentHashMap<>();

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {

//      //获取客户端消息
        String content = msg.text();
        Msgs msgs = JSON.parseObject(content, Msgs.class);
        if(msgs.getType()==2){
            clients.writeAndFlush(new TextWebSocketFrame(msgs.getFromUserId()+"(群发):"+ msgs.getMsg()));
        }else{
            String toUserId = msgs.getToUserId();
            Channel channel = webSocketMap.get(toUserId);
            if (channel == null || !channel.isActive()) {
//                ctx.writeAndFlush(new TextWebSocketFrame("你说: "+msgs.getMsg()));
            ctx.writeAndFlush(new TextWebSocketFrame(msgs.getToUserId() + " 不在线 "+ "\n"));
            } else {
//                ctx.writeAndFlush(new TextWebSocketFrame("你说: "+msgs.getMsg()));
                channel.writeAndFlush(new TextWebSocketFrame(msgs.getFromUserId() + " : " + msgs.getMsg()));
            }
        }
//        System.out.println("接收到的数据: " + content);
//        //下面方法和上面一致
//        clients.writeAndFlush(new TextWebSocketFrame("[服务器在: ]"+
//                LocalDateTime.now()+"接收到消息, 消息为: "+content));
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (null != msg && msg instanceof FullHttpRequest) {
            //转化为http请求
            FullHttpRequest request = (FullHttpRequest) msg;
            //拿到请求地址
            String uri = request.uri();
            System.out.println(uri);
            //判断是不是websocket请求,如果是拿出我们传递的参数(我的是token)
            String origin = request.headers().get("Origin");
            if (null == origin) {
                ctx.close();
            } else {
                String userId = StringUtils.substringAfter(uri, "/ws/");
                userId = URLDecoder.decode(userId, "UTF-8");
                webSocketMap.put(userId, ctx.channel());
                //重新设置请求地址
                request.setUri("/ws");
                clients.writeAndFlush(new TextWebSocketFrame(userId+" 上线了----------"));
                clients.add(ctx.channel());
            }
        }
        //接着建立请求
        super.channelRead(ctx, msg);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channel...活跃");
        super.channelActive(ctx);
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channel...移除");
        super.channelUnregistered(ctx);
    }


    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channel...不活跃");
        super.channelInactive(ctx);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channel...读取完毕");
        super.channelReadComplete(ctx);
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        System.out.println("channel...用户事件触发");
        super.userEventTriggered(ctx, evt);
    }

    @Override
    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channel...可更改");
        super.channelWritabilityChanged(ctx);
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        System.out.println("助手类添加");
        super.handlerAdded(ctx);
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("助手类移除");
        super.handlerRemoved(ctx);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("捕获到异常");
        super.exceptionCaught(ctx, cause);
    }
}

@Data
class Msgs {
    private String fromUserId;
    private String toUserId;
    private String msg;
    private int type;
}

前端代码

渣渣的前端代码,手机看的效果还行

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title>我的小黑屋</title>
		<script src="js/jquery-2.1.1.min.js"></script>
	</head>
	<body>
		<div style="background-color: #C9394A;text-align: center;">
		<div id="tt" style="background-color: #C9394A;text-align: center;font-weight:800;font-size:3.75em;padding-top: 25px;align-items: center;
            vertical-align: middle;
		font-family: Georgia;">小黑屋</div>
		<div style="background-color: #C9394A;text-align: center;">
			<label>我的昵称:</label>
			<input  id="nm" placeholder="请输入你的名字" onkeyup="event.keyCode==13?sure():''"/>
			<input type="button" value="确定" onclick="sure()" id="an"/>
		</div>
		
		<div style="background-color: #C9394A;text-align: center;">
			<label>聊天对象:</label>
			<input  id="nm1" placeholder="请输入聊天对象" onkeyup="event.keyCode==13?sure1():''"/>
			<input type="button" value="确定" onclick="sure1()" id="an1"/>
		</div>
		<div  style="background-color: #C9394A;text-align: center;">
			<input id="bu" type="button" value="开启聊天" style="width: 19.01rem;" onclick="click1()"/>
		</div>
		<div style="background-color: #C9394A;text-align: center;">
			<label >发送消息:</label>
			<input type="text" id="msgContent"  placeholder="请输入你想发送的内容"  onkeyup="event.keyCode==13?CHAT.chat():''"/>
			<input id="in" type="button" value="发送" onclick="CHAT.chat()" />
			<input type="button" value="群发" onclick="CHAT.chat1()" />
		</div>
		<div style="background-color: #C9394A;">
			<label>接收消息:</label>
			<div id="reveiveMsg"></div>
		</div>
		</div>
		<script type="application/javascript">
			 function keyupt(e){ 
				 console.log(111)
			 var evt = window.event || e; 
			   if (evt.keyCode == 13){
				   alert('11')
				   i
			  //回车事件
			 }
			 }
			
			var msg = {
			    fromUserId :"",
			    toUserId  : "",
			    msg       : "",
				type      : 1
			};
			function click1(){
				if(this.msg.fromUserId==""){
					alert("昵称不能为空")
					return
				}
				if(this.msg.toUserId == ""){
					alert("聊天对象不能为空")
					return
				}
				
				$("#bu").hide();
				var that = this
				window.CHAT={
					socket: null,
					init: function(){
						
						if(window.WebSocket){
							var m = document.getElementById("nm");
							CHAT.socket = new WebSocket("ws://47.107.235.154:8080/ws/"+m.value);
							CHAT.socket.onopen = function(){
								console.log("连接建立成功...")
							},
							CHAT.socket.onclose = function(){
								CHAT.socket.close()
								console.log("连接关闭...")
							},
							CHAT.socket.onerror = function(){
								console.log("连接发生错误...")
							},
							CHAT.socket.onmessage = function(e){
								console.log("接收到消息..."+e.data);
								var reveiveMsg = document.getElementById("reveiveMsg");
								var html = reveiveMsg.innerHTML;
								reveiveMsg.innerHTML = "<span style='background-color: green;display:block;text-align:block;margin-right:310px;margin-left:310px;float:left;'>"+e.data+"</span>"+"<br/>"+html
							}
						}else{
							alert("浏览器不支持websocket协议...")
						}
					},
					chat: function(){
						var msg = document.getElementById("msgContent");
						that.msg.msg = msg.value
						if(msg.value==""){
							alert('请输入内容')
							return 
						}
						reveiveMsg.innerHTML = "<span style='background-color: skyblue;display:block;text-align:block;margin-right:310px;margin-left:310px;float:right;'>你说: "+msg.value+"</span>"+ "<br/>"+reveiveMsg.innerHTML
						that.msg.type = 1
						console.log(JSON.stringify(that.msg))
						CHAT.socket.send(JSON.stringify(that.msg));
						 document.getElementById("msgContent").value = "";
						
					},
					chat1: function(){
						var msg = document.getElementById("msgContent");
						that.msg.msg = msg.value
						if(msg.value==""){
							alert('请输入内容')
							return 
						}
						
						that.msg.type = 2
						console.log(JSON.stringify(that.msg))
						CHAT.socket.send(JSON.stringify(that.msg));
						 document.getElementById("msgContent").value = "";
						 
					},
				}
				CHAT.init();
			}
			function sure(){
				var msg = document.getElementById("nm");
				$('#nm').prop("disabled", msg.disabled?false:true)
				$('#an').prop("value",msg.disabled?"修改":"确定")
				if(msg.disabled){
					this.msg.fromUserId = msg.value 
				}
			}
			function sure1(){
				var msg = document.getElementById("nm1");
				$('#nm1').prop("disabled", msg.disabled?false:true)
				$('#an1').prop("value",msg.disabled?"修改":"确定")
				if(msg.disabled){
					this.msg.toUserId = msg.value
				}else{
					CHAT.socket.close()
					$("#bu").show();
					this.msg.toUserId = ""
					document.getElementById("nm1").value = ""
				}
			}
			
		</script>
		<style>
			div{
				border:solid 2px #C9394A;
			}
		
		</style>
	</body>
</html>

结语

用心写博客要好多时间,时间不太多,更新东西有点慢,有时间写博客的人,年长的要么是大佬,要么就是大神,年轻的天天发博客的人,要么是时间多到没地方用,要么就是有奉献精神,牺牲自己时间的人…

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
首先,需要了解什么是NettyNetty是一个异步事件驱动的网络应用框架,用于快速开发可维护的高性能协议服务器和客户端。 接下来,我们可以开始实现聊天功能。一个简单聊天室应该具备以下功能: 1. 用户连接和断开连接的处理; 2. 用户发送消息和接收消息的处理; 3. 消息广播给所有在线用户。 下面是一个简单实现: 1. 用户连接和断开连接的处理 Netty提供了ChannelHandlerAdapter和ChannelInboundHandlerAdapter两个抽象类,我们可以继承其中一个来实现自己的Handler。这里我们使用ChannelInboundHandlerAdapter。 ```java public class ChatServerHandler extends ChannelInboundHandlerAdapter { // 用户列表,用于保存所有连接的用户 private static List<Channel> channels = new ArrayList<>(); // 新用户连接时,将连接加入用户列表 @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { channels.add(ctx.channel()); System.out.println(ctx.channel().remoteAddress() + " 上线了"); } // 用户断开连接时,将连接从用户列表中移除 @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { channels.remove(ctx.channel()); System.out.println(ctx.channel().remoteAddress() + " 下线了"); } } ``` 2. 用户发送消息和接收消息的处理 Netty的数据传输是通过ByteBuf来实现的,因此我们需要将ByteBuf转换为字符串进行处理。 ```java public class ChatServerHandler extends ChannelInboundHandlerAdapter { // 用户列表,用于保存所有连接的用户 private static List<Channel> channels = new ArrayList<>(); // 新用户连接时,将连接加入用户列表 @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { channels.add(ctx.channel()); System.out.println(ctx.channel().remoteAddress() + " 上线了"); } // 用户断开连接时,将连接从用户列表中移除 @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { channels.remove(ctx.channel()); System.out.println(ctx.channel().remoteAddress() + " 下线了"); } // 接收用户发送的消息并处理 @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf buf = (ByteBuf) msg; String received = buf.toString(CharsetUtil.UTF_8); System.out.println(ctx.channel().remoteAddress() + ": " + received); broadcast(ctx, received); } // 将消息广播给所有在线用户 private void broadcast(ChannelHandlerContext ctx, String msg) { for (Channel channel : channels) { if (channel != ctx.channel()) { channel.writeAndFlush(Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8)); } } } } ``` 3. 消息广播给所有在线用户 我们可以使用broadcast方法将接收到的消息广播给所有在线用户。 ```java public class ChatServerHandler extends ChannelInboundHandlerAdapter { // 用户列表,用于保存所有连接的用户 private static List<Channel> channels = new ArrayList<>(); // 新用户连接时,将连接加入用户列表 @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { channels.add(ctx.channel()); System.out.println(ctx.channel().remoteAddress() + " 上线了"); } // 用户断开连接时,将连接从用户列表中移除 @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { channels.remove(ctx.channel()); System.out.println(ctx.channel().remoteAddress() + " 下线了"); } // 接收用户发送的消息并处理 @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf buf = (ByteBuf) msg; String received = buf.toString(CharsetUtil.UTF_8); System.out.println(ctx.channel().remoteAddress() + ": " + received); broadcast(ctx, received); } // 将消息广播给所有在线用户 private void broadcast(ChannelHandlerContext ctx, String msg) { for (Channel channel : channels) { if (channel != ctx.channel()) { channel.writeAndFlush(Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8)); } } } } ``` 接下来我们需要编写一个启动类,用于启动聊天室服务器。 ```java public class ChatServer { public static void main(String[] args) throws Exception { // 创建EventLoopGroup EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { // 创建ServerBootstrap ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new ChatServerHandler()); } }); // 启动服务器 ChannelFuture channelFuture = serverBootstrap.bind(8888).sync(); System.out.println("服务器启动成功"); // 关闭服务器 channelFuture.channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } } ``` 现在,我们就完成了一个简单聊天室服务器。可以通过运行ChatServer类启动服务器,然后使用telnet命令连接服务器进行聊天。 ```sh telnet localhost 8888 ``` 输入发送的消息,即可将消息广播给所有在线用户。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值