Java网络编程进化史:从IO到NIO再到Netty

目录

前言

一.分别基于IO、NIO、Netty的Java网络程序

1.基于IO的Java网络程序

1.1 IO简介

1.2原理

 1.3代码

1.4运行结果

2.基于NIO的Java网络程序

1.1 NIO简介

1.2NIO原理

 1.3代码

1.4运行结果

3.基于Netty的Java网络程序

1.Netty简介

2.Netty原理

 3.代码

1.4运行结果

二.基于Web的聊天室

1.新建项目

2.环境配置 

3.代码

4.运行结果

三.总结

四.参考资料



前言

一. 通过前面的学习我们已经知道: 网络通信是源于最早的UNIX操作系统设计的 socket API(套接字函数调用,C函数形式)实现的;应用程序通过调用这些API,驱动操作系统的低层网络内核模块(TCP/IP协议栈)进行基于TCP/IP协议的数据通信;这些API函数在运行方法上分为阻塞/非阻塞、同步/异步方式,各有优缺点,用户可灵活选择应用在各种场景下。

Java(JDK)也实现了类似C函数socket的JAVA IO标准网络编程(参考 Java网络教程之Socket | 并发编程网 – ifeve.com),然后JDK1.4实现高级网络编程 Java NIO(Java NIO系列教程(一) Java NIO 概述 | 并发编程网 – ifeve.com),最后JBoss公司推出一个基于NIO的高性能异步事件驱动的非阻塞的高级网络编程框架 Netty。

请学习和理解网上参考案例,实践练习其中的demo代码,完成:

1)分别基于IO、NIO、Netty的Java网络程序(如基于TCP的C/S模式的聊天程序);

2)基于Web的聊天室(比如用Springboot+netty实现)

一.分别基于IO、NIO、Netty的Java网络程序

1.基于IO的Java网络程序

1.1 IO简介

IO(BIO Blocking IO)阻塞IO网络模型:服务器启动后会进入阻塞状态,等待client连接,每一个client端连接上服务器后,服务器会为每一个客户端起一个线程来处理客户端的需求。服务器的accept()方法、服务器新起的thread中,Socket的read()和write()方法都是阻塞的。我们都知道unix世界里、一切皆文件、而文件是什么呢?文件就是一串二进制流而已、不管socket、还是FIFO、管道、终端、对我们来说、一切都是文件、一切都是流、在信息交换的过程中、我们都是对这些流进行数据的收发操作、简称为I/O操作(input and output)、往流中读出数据、系统调用read、写入数据、系统调用write、不过话说回来了、计算机里有这么多的流、我怎么知道要操作哪个流呢?做到这个的就是文件描述符、即通常所说的fd、一个fd就是一个整数、所以对这个整数的操作、就是对这个文件(流)的操作、我们创建一个socket、通过系统调用会返回一个文件描述符、那么剩下对socket的操作就会转化为对这个描述符的操作、不能不说这又是一种分层和抽象的思想.

1.2原理

å¨è¿éæå¥å¾çæè¿°

 1.3代码

服务器

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class server {
    public static void main(String[] args) throws IOException {
        //创建客户端的Socket对象(SevereSocket)
        //ServerSocket (int port)创建绑定到指定端口的服务器套接字
        ServerSocket ss=new ServerSocket(50000);

        //Socket accept()侦听要连接到此套接字并接受他
        Socket s=ss.accept();

        //获取输入流,读数据,并把数据显示在控制台
        InputStream is=s.getInputStream();
        byte[] bys=new byte[1024];
        int len=is.read(bys);
        String data=new String(bys,0,len);
        System.out.println("数据是:"+data);

        //释放资源
        s.close();
        ss.close();
    }

}

客户端

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

public class client {
    public static void main(String[] args) throws IOException{
        //创建客户端的Socket对象
        //Socket (InetAddress adress,int port)创建流套接字并将其连接到指定IP地址的指定端口号
//		Socket s=new Socket(InetAddress.getByName("192.168.224.1"), 10000);
        //Socket (String host,int port)创建流套接字并将其连接到指定主机的指定端口号
        Socket s=new Socket("127.0.0.1", 50000);

        //获取输出流,写数据
        //OutputStream getOutputStream();返回此套接字的输出流
        OutputStream os=s.getOutputStream();
        os.write("helloWorld!".getBytes());

        //释放资源
        s.close();

    }

}

1.4运行结果

 

2.基于NIO的Java网络程序

1.1 NIO简介


NIO(Non-blocking I/O ,在Java领域,也称为New I/O),是一种同步非阻塞的I/O模型,也是I/O多路复用的基础,已经被越来越多地应用到大型应用服务器,成为解决高并发与大量连接、I/O处理问题的有效方式。

1.2NIO原理

NIO其实就是主要利用缓冲区来进行传输字节

 1.3代码

服务器

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class server {
    //网络通信IO操作,TCP协议,针对面向流的监听套接字的可选择通道(一般用于服务端)
    private ServerSocketChannel serverSocketChannel;
    private Selector selector;

    /*
     *开启服务端
     */
    public void start(Integer port) throws Exception {
        serverSocketChannel = ServerSocketChannel.open();
        selector = Selector.open();
        //绑定监听端口
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        //设置为非阻塞模式
        serverSocketChannel.configureBlocking(false);
        //注册到Selector上
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        startListener();
    }
    private void startListener() throws Exception {
        while (true) {
            // 如果客户端有请求select的方法返回值将不为零
            if (selector.select(1000) == 0) {
                System.out.println("当前没有任务!!!");
                continue;
            }
            // 如果有事件集合中就存在对应通道的key
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            // 遍历所有的key找到其中事件类型为Accept的key
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                if (key.isAcceptable())
                    handleConnection();
                if (key.isReadable())
                    handleMsg(key);
                iterator.remove();
            }
        }
    }
    /**
     * 处理建立连接
     */
    private void handleConnection() throws Exception {
        SocketChannel socketChannel = serverSocketChannel.accept();
        socketChannel.configureBlocking(false);
        socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
    }
    /*
     * 接收信息
     */
    private void handleMsg(SelectionKey key) throws Exception {
        SocketChannel channel = (SocketChannel) key.channel();
        ByteBuffer attachment = (ByteBuffer) key.attachment();
        channel.read(attachment);
        System.out.println("当前信息: " + new String(attachment.array()));
    }

    public static void main(String[] args) throws Exception {
        server myServer = new server();
        myServer.start(8881);
    }
}

客户端

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class client {
    public static void main(String[] args) throws Exception {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);

        // 连接服务器
        if (!socketChannel.connect(new InetSocketAddress("127.0.0.1", 8881))) {
            while (!socketChannel.finishConnect()) {
                System.out.println("connecting...");
            }
        }
        //发送数据
        String str = "Hello~,要加油呀!";
        ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
        socketChannel.write(byteBuffer);
        System.in.read();
    }

}

1.4运行结果

 

3.基于Netty的Java网络程序

1.Netty简介

  • Netty 是一个基于NIO的客户、服务器端编程框架。
    Netty封装了JDK的NIO,Netty是一个异步事件驱动的网络应用框架,用于快速开发可维护的高性能服务器和客户端。

2.Netty原理

 3.代码

服务器

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

import java.net.InetSocketAddress;

/**
 *
 */
public class server {

    private int port;

    public static void main(String[] args){
        new server(12345).start();
    }

    public server(int port) {
        this.port = port;
    }

    public void start() {
        /**
         * 创建两个EventLoopGroup,即两个线程池,boss线程池用于接收客户端的连接,
         * 一个线程监听一个端口,一般只会监听一个端口所以只需一个线程
         * work池用于处理网络连接数据读写或者后续的业务处理(可指定另外的线程处理业务,
         * work完成数据读写)
         */
        EventLoopGroup boss = new NioEventLoopGroup(1);
        EventLoopGroup work = new NioEventLoopGroup();
        try {
            /**
             * 实例化一个服务端启动类,
             * group()指定线程组
             * channel()指定用于接收客户端连接的类,对应java.nio.ServerSocketChannel
             * childHandler()设置编码解码及处理连接的类
             */
            ServerBootstrap server = new ServerBootstrap()
                    .group(boss, work).channel(NioServerSocketChannel.class)
                    .localAddress(new InetSocketAddress(port))
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline()
                                    .addLast("decoder", new StringDecoder())
                                    .addLast("encoder", new StringEncoder())
                                    .addLast(new HelloWorldServerHandler());
                        }
                    });
            //绑定端口
            ChannelFuture future = server.bind().sync();
            System.out.println("server started and listen " + port);
            future.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            boss.shutdownGracefully();
            work.shutdownGracefully();
        }
    }

    public static class HelloWorldServerHandler extends ChannelInboundHandlerAdapter {

        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("HelloWorldServerHandler active");
        }

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            System.out.println("server channelRead..");
            System.out.println(ctx.channel().remoteAddress()+"->Server :"+ msg.toString());
            ctx.write("server write"+msg);
            ctx.flush();
        }
    }
}

客户端

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

/**
 *
 */
public class client {
    private static final String HOST = "localhost";
    private static final int PORT= 12345;

    public static void main(String[] args){
        new client().start(HOST, PORT);
    }

    public void start(String host, int port) {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap client = new Bootstrap().group(group).channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true).handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline()
                                    .addLast("decoder", new StringDecoder())
                                    .addLast("encoder", new StringEncoder())
                                    .addLast(new HelloWorldClientHandler());
                        }
                    });
            ChannelFuture future = client.connect(host, port).sync();
            future.channel().writeAndFlush("Hello Netty Server ,I am a netty client");
            future.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            group.shutdownGracefully();

        }

    }

    public static class HelloWorldClientHandler extends ChannelInboundHandlerAdapter {
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("HelloWorldClientHandler Active");
        }

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            System.out.println("HelloWorldClientHandler read Message:"+msg);
        }
    }
}

1.4运行结果

 

二.基于Web的聊天室

1.新建项目

1.1

 1.2

1.3 

 1.4

 1.5

2.环境配置 

在pom.xml里

加上

 <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </dependency>

 

3.代码

 User

package com.example.webchat;

import java.util.Objects;

public class User {

    public String id;
    public String nickname;

    public User(String id, String nickname) {
        super();
        this.id = id;
        this.nickname = nickname;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        User user = (User) o;
        return id.equals(user.getId());
    }

    @Override
    public int hashCode() {

        return Objects.hash(id);
    }

    public String getUid() {

        return id;
    }
}
  • SessionGroup
package com.example.webchat;

import com.google.gson.Gson;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.ChannelGroupFuture;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.ImmediateEventExecutor;
import org.springframework.util.StringUtils;

import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public final class SessionGroup {

    private static SessionGroup singleInstance = new SessionGroup();

    // 组的映射
    private ConcurrentHashMap<String, ChannelGroup> groupMap = new ConcurrentHashMap<>();

    public static SessionGroup inst() {
        return singleInstance;
    }

    public void shutdownGracefully() {

        Iterator<ChannelGroup> groupIterator = groupMap.values().iterator();
        while (groupIterator.hasNext()) {
            ChannelGroup group = groupIterator.next();
            group.close();
        }
    }

    public void sendToOthers(Map<String, String> result, SocketSession s) {
        // 获取组
        ChannelGroup group = groupMap.get(s.getGroup());
        if (null == group) {
            return;
        }
        Gson gson=new Gson();
        String json = gson.toJson(result);
        // 自己发送的消息不返回给自己
//      Channel channel = s.getChannel();
        // 从组中移除通道
//      group.remove(channel);
        ChannelGroupFuture future = group.writeAndFlush(new TextWebSocketFrame(json));
        future.addListener(f -> {
            System.out.println("完成发送:"+json);
//          group.add(channel);//发送消息完毕重新添加。

        });
    }

    public void addSession(SocketSession session) {

        String groupName = session.getGroup();
        if (StringUtils.isEmpty(groupName)) {
            // 组为空,直接返回
            return;
        }
        ChannelGroup group = groupMap.get(groupName);
        if (null == group) {
            group = new DefaultChannelGroup(ImmediateEventExecutor.INSTANCE);
            groupMap.put(groupName, group);
        }
        group.add(session.getChannel());
    }

    /**
     * 关闭连接, 关闭前发送一条通知消息
     */
    public void closeSession(SocketSession session, String echo) {
        ChannelFuture sendFuture = session.getChannel().writeAndFlush(new TextWebSocketFrame(echo));
        sendFuture.addListener(new ChannelFutureListener() {
            public void operationComplete(ChannelFuture future) {
                System.out.println("关闭连接:"+echo);
                future.channel().close();
            }
        });
    }

    /**
     * 关闭连接
     */
    public void closeSession(SocketSession session) {

        ChannelFuture sendFuture = session.getChannel().close();
        sendFuture.addListener(new ChannelFutureListener() {
            public void operationComplete(ChannelFuture future) {
                System.out.println("发送所有完成:"+session.getUser().getNickname());
            }
        });

    }

    /**
     * 发送消息
     * @param ctx 上下文
     * @param msg 待发送的消息
     */
    public void sendMsg(ChannelHandlerContext ctx, String msg) {
        ChannelFuture sendFuture = ctx.writeAndFlush(new TextWebSocketFrame(msg));
        sendFuture.addListener(f -> {//发送监听
            System.out.println("对所有发送完成:"+msg);
        });
    }
}

  • SocketSession
package com.example.webchat;


import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.AttributeKey;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

public class SocketSession {

    public static final AttributeKey<SocketSession> SESSION_KEY = AttributeKey.valueOf("SESSION_KEY");

    /**
     * 用户实现服务端会话管理的核心
     */
// 通道
    private Channel channel;
    // 用户
    private User user;

    // session唯一标示
    private final String sessionId;

    private String group;

    /**
     * session中存储的session 变量属性值
     */
    private Map<String, Object> map = new HashMap<String, Object>();

    public SocketSession(Channel channel) {//注意传入参数channel。不同客户端会有不同channel
        this.channel = channel;
        this.sessionId = buildNewSessionId();
        channel.attr(SocketSession.SESSION_KEY).set(this);
    }

    // 反向导航
    public static SocketSession getSession(ChannelHandlerContext ctx) {//注意ctx,不同的客户端会有不同ctx
        Channel channel = ctx.channel();
        return channel.attr(SocketSession.SESSION_KEY).get();
    }

    // 反向导航
    public static SocketSession getSession(Channel channel) {
        return channel.attr(SocketSession.SESSION_KEY).get();
    }

    public String getId() {
        return sessionId;
    }

    private static String buildNewSessionId() {
        String uuid = UUID.randomUUID().toString();
        return uuid.replaceAll("-", "");
    }

    public synchronized void set(String key, Object value) {
        map.put(key, value);
    }

    public synchronized <T> T get(String key) {
        return (T) map.get(key);
    }

    public boolean isValid() {
        return getUser() != null ? true : false;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public String getGroup() {
        return group;
    }

    public void setGroup(String group) {
        this.group = group;
    }

    public Channel getChannel() {
        return channel;
    }
}

  • WebSocketServer
package com.example.webchat;


import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
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.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;

import java.util.concurrent.TimeUnit;
public class WebSocketServer {

    private static WebSocketServer wbss;

    private static final int READ_IDLE_TIME_OUT = 60; // 读超时
    private static final int WRITE_IDLE_TIME_OUT = 0;// 写超时
    private static final int ALL_IDLE_TIME_OUT = 0; // 所有超时

    public static WebSocketServer inst() {
        return wbss = new WebSocketServer();
    }

    public void run(int port) {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        ServerBootstrap b = new ServerBootstrap();
        b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer <SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();
                        // Netty自己的http解码器和编码器,报文级别 HTTP请求的解码和编码
                        pipeline.addLast(new HttpServerCodec());
                        // ChunkedWriteHandler 是用于大数据的分区传输
                        // 主要用于处理大数据流,比如一个1G大小的文件如果你直接传输肯定会撑暴jvm内存的;
                        // 增加之后就不用考虑这个问题了
                        pipeline.addLast(new ChunkedWriteHandler());
                        // HttpObjectAggregator 是完全的解析Http消息体请求用的
                        // 把多个消息转换为一个单一的完全FullHttpRequest或是FullHttpResponse,
                        // 原因是HTTP解码器会在每个HTTP消息中生成多个消息对象HttpRequest/HttpResponse,HttpContent,LastHttpContent
                        pipeline.addLast(new HttpObjectAggregator(64 * 1024));
                        // WebSocket数据压缩
                        pipeline.addLast(new WebSocketServerCompressionHandler());
                        // WebSocketServerProtocolHandler是配置websocket的监听地址/协议包长度限制
                        pipeline.addLast(new WebSocketServerProtocolHandler("/ws", null, true, 10 * 1024));

                        // 当连接在60秒内没有接收到消息时,就会触发一个 IdleStateEvent 事件,
                        // 此事件被 HeartbeatHandler 的 userEventTriggered 方法处理到
                        pipeline.addLast(
                                new IdleStateHandler(READ_IDLE_TIME_OUT, WRITE_IDLE_TIME_OUT, ALL_IDLE_TIME_OUT, TimeUnit.SECONDS));

                        // WebSocketServerHandler、TextWebSocketFrameHandler 是自定义逻辑处理器,
                        pipeline.addLast(new WebSocketTextHandler());
                    }
                });
        Channel ch = b.bind(port).syncUninterruptibly().channel();
        ch.closeFuture().syncUninterruptibly();

        // 返回与当前Java应用程序关联的运行时对象
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                SessionGroup.inst().shutdownGracefully();
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            }
        });
    }
}
  • WebSocketTextHandler
package com.example.webchat;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;

import java.util.HashMap;
import java.util.Map;

import static com.fasterxml.jackson.databind.type.LogicalType.Map;

public class WebSocketTextHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        SocketSession session = SocketSession.getSession(ctx);
        TypeToken<HashMap<String, String>> typeToken = new TypeToken<HashMap<String, String>>() {
        };

        Gson gson=new Gson();
        java.util.Map<String,String> map = gson.fromJson(msg.text(), typeToken.getType());
        User user = null;
        switch (map.get("type")) {
            case "msg":
                Map<String, String> result = new HashMap<>();
                user = session.getUser();
                result.put("type", "msg");
                result.put("msg", map.get("msg"));
                result.put("sendUser", user.getNickname());
                SessionGroup.inst().sendToOthers(result, session);
                break;
            case "init":
                String room = map.get("room");
                session.setGroup(room);
                String nick = map.get("nick");
                user = new User(session.getId(), nick);
                session.setUser(user);
                SessionGroup.inst().addSession(session);
                break;
        }
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {

        // 是否握手成功,升级为 Websocket 协议
        if (evt == WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE) {
            // 握手成功,移除 HttpRequestHandler,因此将不会接收到任何消息
            // 并把握手成功的 Channel 加入到 ChannelGroup 中
            new SocketSession(ctx.channel());
        } else if (evt instanceof IdleStateEvent) {
            IdleStateEvent stateEvent = (IdleStateEvent) evt;
            if (stateEvent.state() == IdleState.READER_IDLE) {
                System.out.println("bb22");
            }
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }
}
  • DemoApplication
package com.example.webchat;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;

import java.util.HashMap;
import java.util.Map;

import static com.fasterxml.jackson.databind.type.LogicalType.Map;

public class WebSocketTextHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        SocketSession session = SocketSession.getSession(ctx);
        TypeToken<HashMap<String, String>> typeToken = new TypeToken<HashMap<String, String>>() {
        };

        Gson gson = new Gson();
        java.util.Map<String, String> map = gson.fromJson(msg.text(), typeToken.getType());
        User user = null;
        switch (map.get("type")) {
            case "msg":
                Map<String, String> result = new HashMap<>();
                user = session.getUser();
                result.put("type", "msg");
                result.put("msg", map.get("msg"));
                result.put("sendUser", user.getNickname());
                SessionGroup.inst().sendToOthers(result, session);
                break;
            case "init":
                String room = map.get("room");
                session.setGroup(room);
                String nick = map.get("nick");
                user = new User(session.getId(), nick);
                session.setUser(user);
                SessionGroup.inst().addSession(session);
                break;
        }
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {

        // 是否握手成功,升级为 Websocket 协议
        if (evt == WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE) {
            // 握手成功,移除 HttpRequestHandler,因此将不会接收到任何消息
            // 并把握手成功的 Channel 加入到 ChannelGroup 中
            new SocketSession(ctx.channel());
        } else if (evt instanceof IdleStateEvent) {
            IdleStateEvent stateEvent = (IdleStateEvent) evt;
            if (stateEvent.state() == IdleState.READER_IDLE) {
                System.out.println("bb22");
            }
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }
}

index.html

<!DOCTYPE HTML>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>群聊天室</title>
    <style type="text/css">
        body {
            margin-right:50px;
            margin-left:50px;
        }
        .ddois {
            position: fixed;
            left: 120px;
            bottom: 30px;
        }
    </style>
</head>
<body>
群名:<input type="text" id="room" name="group" placeholder="请输入群">
<br /><br />
昵称:<input type="text" id="nick" name="name" placeholder="请输入昵称">
<br /><br />
<button type="button" onclick="enter()">进入聊天群</button>
<br /><br />
<div id="message"></div>
<br /><br />
<div class="ddois">
    <textarea name="send" id="text" rows="10" cols="30" placeholder="输入发送消息"></textarea>
    <br /><br />
    <button type="button" onclick="send()">发送</button>
</div>
<script type="text/javascript">
    var webSocket;

    if (window.WebSocket) {
        webSocket = new WebSocket("ws://localhost:8088/ws");
    } else {
        alert("抱歉,您的浏览器不支持WebSocket协议!");
    }

    //连通之后的回调事件
    webSocket.onopen = function() {
        console.log("已经连通了websocket");
//                setMessageInnerHTML("已经连通了websocket");
    };
    //连接发生错误的回调方法
    webSocket.onerror = function(event){
        console.log("出错了");
//              setMessageInnerHTML("连接失败");
    };

    //连接关闭的回调方法
    webSocket.onclose = function(){
        console.log("连接已关闭...");

    }

    //接收到消息的回调方法
    webSocket.onmessage = function(event){
        console.log("bbdds");
        var data = JSON.parse(event.data)
        var msg = data.msg;
        var nick = data.sendUser;
        switch(data.type){
            case 'init':
                console.log("mmll");
                break;
            case 'msg':
                console.log("bblld");
                setMessageInnerHTML(nick+":  "+msg);
                break;
            default:
                break;
        }
    }
    function enter(){
        var map = new Map();
        var nick=document.getElementById('nick').value;
        var room=document.getElementById('room').value;
        map.set("type","init");
        map.set("nick",nick);
        console.log(room);
        map.set("room",room);
        var message = Map2Json(map);
        webSocket.send(message);
    }

    function send() {
        var msg = document.getElementById('text').value;
        var nick = document.getElementById('nick').value;
        console.log("1:"+msg);
        if (msg != null && msg != ""){
            var map = new Map();
            map.set("type","msg");
            map.set("msg",msg);
            var map2json=Map2Json(map);
            if (map2json.length < 8000){
                console.log("4:"+map2json);
                webSocket.send(map2json);
            }else {
                console.log("文本太长了,少写一点吧😭");
            }
        }
    }

    //将消息显示在网页上
    function setMessageInnerHTML(innerHTML) {
        document.getElementById("message").innerHTML += innerHTML + "<br/>";
    }

    function Map2Json(map) {
        var str = "{";
        map.forEach(function (value, key) {
            str += '"'+key+'"'+':'+ '"'+value+'",';
        })
        str = str.substring(0,str.length-1)
        str +="}";
        return str;
    }

</script>

</body>
</html>


4.运行结果

 

 

 

三.总结

照着一步一步就可实现。

四.参考资料

基于IO、NIO、Netty的TCP聊天程序_junseven164的博客-CSDN博客

分别基于IO、NIO、Netty的Java网络程序_机智的橙子的博客-CSDN博客

Springboot+netty网络聊天_一只特立独行的猪️的博客-CSDN博客

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值