分享一个基于netty的websocket群聊demo,用于学习netty

Netty是什么?

Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients. It greatly simplifies and streamlines network programming such as TCP and UDP socket server.

以上是官网的原话,大致意思就是netty是一个NIO客户端服务端框架 ,可以快速轻松地开发网络应用程序,它极大地简化和简化了TCP和UDP套接字服务器等网络编程。

BIO、NIO、AIO

BIO(阻塞,同步) 全称Block-IO 是一种阻塞同步的通信模式。我们常说的Stock IO 一般指的是BIO。是一个比较传统的通信方式,模式简单使用方便。但并发处理能力低通信耗时依赖网速

NIO(非阻塞/阻塞,同步) 全称New IO,也叫Non-Block IO 是一种非阻塞同步的通信模式。

AIO(非阻塞,异步) 也叫NIO2.0 是一种非阻塞异步的通信模式。在NIO的基础上引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。

以上是简单介绍了IO的不同种类,想详细了解的可以看这里https://segmentfault.com/a/1190000012976683

三种线程模型

单线程模型

这种模型,是由一个Accept线程等待客户端进行请求,然后分配到处理的链路中,这种方式只适用于处理业务简单处理的比较快时,可以不用疯狂开线程节省资源。

多线程模型

这种模型,是基于单线程模型的基础上,使用了线程池,就是Accept线程接收到请求后,发送给线程池,线程池执行任务。

主从模型

这个模型也是netty使用的模型,一个主线程组,一个从线程组,主线程组负责接收客户端的请求,从线程组就负责处理,这个模型的唯一缺点是复杂,若是由原生API来实现可真是很麻烦,所以有了netty这个框架。

Netty

与一般主从模型一样,三个核心,两个EventLoopGroup主从线程组,Channel通道,Channel里面的Handler。

每一个客户端连接服务端发送消息后,都会创建一个Channel双向通道,而每个Channel里会经过一个个Handler处理后,再把消息返回给客户端,netty提供了许多handler,大大减少了我们的开发成本。

以上图取自其他博文,该博文分析了netty框架的原理,有兴趣的可以去看看https://blog.csdn.net/qq_18603599/article/details/80768390

下面直接上代码

服务启动类

package com.iw.netty.websocket;

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;

public class WSServer {

    public static void main(String[] args) throws Exception {
        //定义一对线程组
        //主线程组,用于接收客户端的连接,不做任何处理
        EventLoopGroup mainGroup = new NioEventLoopGroup();
        //从线程组,老板把任务丢给他,让手下线程组去做任务
        EventLoopGroup subGroup = new NioEventLoopGroup();

        try{
            //netty服务器创建,ServerBootstrap是一个启动类
            ServerBootstrap server = new ServerBootstrap();
            server.group(mainGroup,subGroup)//设置主从线程
                    .channel(NioServerSocketChannel.class)//设置nio双向通道
                    .childHandler(new WSServerInitializer());//子处理器,用于处理subGroup

            //启动server,并设置端口号为8088,启动方式为同步
            ChannelFuture future = server.bind(8088).sync();

            //监听关闭的channel,设置为同步方式
            future.channel().closeFuture().sync();
        }finally {
            mainGroup.shutdownGracefully();
            subGroup.shutdownGracefully();
        }

    }

}

handler子处理器类

package com.iw.netty.websocket;

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 WSServerInitializer extends ChannelInitializer<SocketChannel> {

    protected void initChannel(SocketChannel channel) throws Exception {
        ChannelPipeline pipeline = channel.pipeline();

        //websocket基于http协议,所以要有http编解码器
        pipeline.addLast(new HttpServerCodec());
        //对大数据流的支持
        pipeline.addLast(new ChunkedWriteHandler());
        //httpMessage进行聚合,聚合成FullHttpRequest或者FullHttpResponse
        //几乎所以netty编程都会用到此handler,参数是消息的最大长度
        pipeline.addLast(new HttpObjectAggregator(1024*64));

        //====================以上是用于支持http=========================================

        //====================以下是用于支持websocket====================================
        /**
         * websocket服务器处理的协议,用于指定给客户端连接访问的路由:/ws
         * 本handler会帮你处理一些繁重的复杂的事
         * 会帮你处理握手动作:handshaking(close,ping,pong) ping+pong = 心跳
         * 对于websocket来讲,都是以frames进行传输的,不同的数据类型对应的frames也不同
         */
        pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
        //自定义的handler
        pipeline.addLast(new ChatHandler());

    }
}

自定义的handler类,业务

package com.iw.netty.websocket;

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.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;

import java.time.LocalDateTime;

/**
 * 处理消息的handler
 * TextWebSocketFrame:在netty中,是用于websocket专门处理文本的对象,frame是消息的载体
 */
public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    //用于记录和管理所以客户端的channel
    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);

//        for (Channel channel: clients) {
//            channel.writeAndFlush(new TextWebSocketFrame("[服务器]"+ LocalDateTime.now()+":"+content));
//        }
        //下面这个方法和上面一致
        clients.writeAndFlush(new TextWebSocketFrame("[服务器]"+ LocalDateTime.now()+":"+content));



    }

    /**
     * 当客户端连接服务端后(打开连接)
     * 获取客户端的channel,并放到ChannelGroup中管理
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        clients.add(ctx.channel());
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        //当触发handlerRemoved,ChannelGroup会自动移除对应的客户端的channel
        //clients.remove(ctx.channel());这行代码是多余的
        System.out.println("客户端断开,对应的长id"+ctx.channel().id().asLongText());
        System.out.println("客户端断开,对应的短id"+ctx.channel().id().asShortText());
    }
}

pom依赖

<dependencies>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.25.Final</version>
        </dependency>
    </dependencies>

前端测试代码

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<title>聊天室</title>
	<body>
		
		<div>发送消息</div>
		<input type="text" id="msgContent" />
		<input type="button" value="点击发送" onclick="CHAT.chat()" />
		
		<div>接收消息</div>
		<div id="receiveMsg" style="background-color: greenyellow;"></div>
		
	</body>
	<script type="text/javascript">
		window.CHAT = {
			socket : null,
			init : function(){
				if(window.WebSocket){
					CHAT.socket = new WebSocket("ws://192.168.1.3:8088/ws");
					CHAT.socket.onopen = function(){
						console.log("连接成功...")
					},
					CHAT.socket.onclose = function(){
						console.log("连接关闭...")
					},
					CHAT.socket.onerror = function(){
						console.log("发生错误...")
					},
					CHAT.socket.onmessage = function(e){
						console.log("接收到消息:"+ e.data);
						var receiveMsg = document.getElementById("receiveMsg");
						var html = receiveMsg.innerHTML;
						receiveMsg.innerHTML = html +"<br/>"+ e.data;
					}
				}else{
					alert("当前浏览器不支持websocket")
				}
			},
			chat : function(){
				var msg = document.getElementById("msgContent").value;
				CHAT.socket.send(msg);
			}
		}

		CHAT.init();
	</script>
</html>

效果如下

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值