netty+springBoot+webSocket+redis实现各个客户端之间通信、及时聊天等功能

说明:本文主要写的是即时聊天功能的一些设计思路,文末有完整项目地址,仅有后端代码。本人经验尚浅,有错误、不妥之处望各位大神与本人联系,不胜感激。文章中有些许借用的地方,如侵权,请及时联系本人,立马删除。
邮箱:2540628013@qq.com

本项目用到的技术

netty框架,为什么要使用netty框架呢?

Bio

BIO 有的称之为 basic(基本) IO,有的称之为 block(阻塞) IO,主要应用于文件 IO 和网络 IO, 这里不再说文件 IO, 在 JDK1.4 之前,我们建立网络连接的时候只能采用 BIO,需要先在服务端启动一个 ServerSocket,然后在客户端启动 Socket 来对服务端进行通信,默认情况下服务端需要对每个请求建立一个线程等待请求,而客户端发送请求后,先咨询服务端是否有线程响应,如果没有则会一直等待或者遭到拒绝,如果有的话,客户端线程会等待请求结束后才继续执行, 这就是阻塞式 IO

 //BIO 服务器端程序
    public class TCPServer {
   
        public static void main(String[] args) throws Exception {
   
            //1.创建 ServerSocket 对象
            ServerSocket ss = new ServerSocket(9999);
            while (true) {
   
                //2.监听客户端
                Socket s = ss.accept(); //阻塞
                // 3.从连接中取出输入流来接收消息
                InputStream is = s.getInputStream();
                //阻塞
                byte[] b = new byte[10];
                is.read(b);
                String clientIP = s.getInetAddress().getHostAddress();
                System.out.println(clientIP + "说:" + new String(b).trim());
                //4.从连接中取出输出流并回话
                OutputStream os = s.getOutputStream();
                os.write("hello".getBytes());
                //5.关闭
                s.close();
            }
        }
    }

上述代码编写了一个服务器端程序,绑定端口号 9999,accept 方法用来监听客户端连接, 如果没有客户端连接,就一直等待,程序会阻塞到这里。

 //BIO 客户端程序
    public class TCPClient {
   
        public static void main(String[] args) throws Exception {
   
            while (true) {
   
                //1.创建 Socket 对象
                Socket s = new Socket("127.0.0.1", 9999);
                //2.从连接中取出输出流并发消息 
                OutputStream os = s.getOutputStream();
                System.out.println("请输入:");
                Scanner sc = new Scanner(System.in);
                String msg = sc.nextLine();
                os.write(msg.getBytes());
                //3.从连接中取出输入流并接收回话 
                InputStream is = s.getInputStream();
                //阻塞 
                byte[] b = new byte[20];
                is.read(b);
                System.out.println("说:" + new String(b).trim());
                //4.关闭
                s.close();
            }
        }
    }

上述代码编写了一个客户端程序,通过 9999 端口连接服务器端,getInputStream 方法用来 等待服务器端返回数据,如果没有返回,就一直等待,程序会阻塞到这里。
结果请拷贝到自己idea自行查看

这个仅仅只是简单的演示代码,如果真正的使用怎么做呢?加线程呗,一个用户过来就新建一个线程,然后每个线程里面一个while循环阻塞在里面,这是很恐怖的一件事,这些就会带来下面三个问题:

1、资源受限,大量的线程阻塞在那里,对于服务器来说是很浪费资源的一件事。
2、线程切换频繁,我们知道java线程如果优先级相同是抢占式的也就是随机的,线程数量过多,对于单核cpu切换来说是很影响性能的。
3、上面例子可以看到Bio是以byte为单位的。

Nio

Jdk1.4之后提出了Nio,NIO 和 BIO 有着相同的目的和作用,但是它们的实现方式完全不同,BIO 以流的方式处理数据,**而 NIO 以块的方式处理数据,**块 I/O 的效率比流 I/O 高很多。另外,NIO 是非阻塞式的, 这一点跟 BIO 也很不相同,使用它可以提供非阻塞式的高伸缩性网络。 NIO 主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(选择器)。传统的 BIO 基于字节流和字符流进行操作,而 NIO 基于 Channel(通道)和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通 道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道。

这里借大神“闪电侠”举的例子说明一下:
(强烈推荐Netty入门学习小册https://juejin.im/book/5b4bc28bf265da0f60130116
在一家幼儿园里,小朋友有上厕所的需求,小朋友都太小以至于你要问他要不要上厕所,他才会告诉你。幼儿园一共有 100 个小朋友,有两种方案可以解决小朋友上厕所的问题:

每个小朋友配一个老师。每个老师隔段时间询问小朋友是否要上厕所,如果要上,就领他去厕所,100 个小朋友就需要 100 个老师来询问,并且每个小朋友上厕所的时候都需要一个老师领着他去上,这就是IO模型,一个连接对应一个线程。
所有的小朋友都配同一个老师。这个老师隔段时间询问所有的小朋友是否有人要上厕所,然后每一时刻把所有要上厕所的小朋友批量领到厕所,这就是 NIO 模型,所有小朋友都注册到同一个老师,对应的就是所有的连接都注册到一个线程,然后批量轮询。

这就是 NIO 模型解决线程资源受限的方案,实际开发过程中,我们会开多个线程,每个线程都管理着一批连接,相对于 IO 模型中一个线程管理一条连接,消耗的线程资源大幅减少。

但是传统的Nio网络编程是很麻烦的一件事,想去了解的自行百度,这里不过多赘述。

那netty到底是什么?官方解释:Netty 是一个异步事件驱动的网络应用框架,用于快速开发可维护的高性能服务器和客户端。netty就是一个对Jdk的Nio进行封装的一个框架,和其他框架一样,目的就是让你用得爽。

webSocket

WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
(注意:如果对于协议很了解的话,这里也可以使用自己写的协议,但是对于中小型企业或者非专门做即时聊天的企业来说,这是得不偿失的)

这里主要注意几个大坑,包括后面也会说这个问题

1、返回对象的包裹

//第一个坑,这里整合了webSocket对象后一定要用TextWebSocketFrame或者其他WebSocketFrame对象进行包裹
  public void sendWebSocket(Channel channel, String res) {
   
        if (channel != null && channel.isActive()) {
   
            channel.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(res)));
        }
    }

2、跨域问题的处理,如果跨域没有处理,一切等于0,这个坑一定要注意

 /**
     * Http返回
     *
     * @param ctx
     * @param request
     * @param response
     */
    private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest request, FullHttpResponse response) {
   
        // 返回应答给客户端
        if (response.status().code() != 200) {
   
            ByteBuf buf = Unpooled.copiedBuffer(response.status().toString(), CharsetUtil.UTF_8);
            response.content().writeBytes(buf);
            buf.release();
            //允许跨域访问 设置头部信息
            response.headers().
  • 3
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值