netty学习(1):1个客户端与服务器通信

news2024/11/14 14:12:19

1. 新建maven工程,添加netty依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>nettyTest</artifactId>
    <version>1.0-SNAPSHOT</version>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

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

2. 新建netty服务器

package server;

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.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

import java.util.concurrent.ConcurrentHashMap;

public class NettyService {
    /**
     * 1.创建BossGroup线程组,处理网络连接事件
     * 2.创建workerGroup线程组 处理网络读写事件
     * 3.创建服务端启动助手,serverBootStrap
     * 4.设置服务端通道实现方式为NIO
     * 5.设置服务端options
     * 6.创建通道初始化对象
     * 8.向pipeline中添加自定义业务初处理逻辑handler
     * 9.启动服务端并绑定端口,将异步改为同步
     * 10.关闭通道和连接池
     */

    //监听的端口号
    private final int port;
    public NettyService(int port) {
        this.port = port;
    }

    public void createNettyService() {
        //创建两个线程组 boosGroup、workerGroup,这两个都是无限循环,boos是负责进行连接请求的接收accept事件,而worker则是负责业务处理
        //只是处理连接请求accept,含有的子线程有多少个呢,NioEventLoop个数,默认是cpu的核数*2
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        //真正的和客户端进行业务处理,含有的子线程有多少个呢,NioEventLoop个数,默认是cpu的核数*2
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            //创建服务端的启动对象,设置参数
            ServerBootstrap bootstrap = new ServerBootstrap();
            //设置两个线程组boosGroup和workerGroup
            bootstrap.group(bossGroup, workerGroup)
                    //设置服务端通道实现类型
                    .channel(NioServerSocketChannel.class) //使用nioserversocketchannel作为通道实现
                    //设置线程队列得到连接个数
                    .option(ChannelOption.SO_BACKLOG, 128) //设置线程队列等待连接个数
                    //设置保持活动连接状态
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    //使用匿名内部类的形式初始化通道对象
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        //这里会进行客户端业务处理
                        //通过一个特殊的ChannelInboundHandler 来初始化注册到EventLoop的Channel
                        @Override
                        protected void initChannel(SocketChannel sc) throws Exception {
                            //给pipeline管道设置处理器
                            ChannelPipeline pipeline = sc.pipeline();
                            //添加一个基于行的解码器
                            pipeline.addLast(new LineBasedFrameDecoder(2048));
                            pipeline.addLast(new StringDecoder());
                            pipeline.addLast(new StringEncoder());
                            //这里加入了自定义的handler
                            pipeline.addLast(new NettyServiceHandler());
                        }
                    });//给workerGroup的EventLoop对应的管道设置处理器
            //绑定端口号,启动服务端
            ChannelFuture channelFuture = bootstrap.bind(port).sync();
            System.out.println("netty服务端已经准备就绪,端口" + port);
            //对关闭通道进行监听
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

自定义handler

package server;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.CharsetUtil;
import io.netty.util.concurrent.GlobalEventExecutor;

/**
 * 自定义处理Handler
 */
public class NettyServiceHandler extends SimpleChannelInboundHandler<String> {
    // 创建一个ChannelGroup,其是一个线程安全的集合,其中存放着与当前服务器相连接的所有Active状态的Channel
    // GlobalEventExecutor是一个单例、单线程的EventExecutor,是为了保证对当前group中的所有Channel的处理
    // 线程是同一个线程
    //private static ChannelGroup group = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    // 只要有客户端Channel与服务端连接成功就会执行这个方法
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 获取到当前与服务器连接成功的channel
        Channel channel = ctx.channel();
        System.out.println(channel.remoteAddress() + "---上线");
        //group.writeAndFlush(channel.remoteAddress() + "---上线\n");
        // 将当前channel添加到group中
        //group.add(channel);

//        NettyService.remoteAddressMap.put(channel.remoteAddress().toString(), ctx);
    }

    // 只要有客户端Channel断开与服务端的连接就会执行这个方法
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        // 获取到当前要断开连接的Channel
        Channel channel = ctx.channel();
        System.out.println(channel.remoteAddress() + "------下线");
        //group.writeAndFlush(channel.remoteAddress() + "下线,当前在线人数:" + group.size() + "\n");

        // group中存放的都是Active状态的Channel,一旦某Channel的状态不再是Active,
        // group会自动将其从集合中踢出,所以,下面的语句不用写
        // remove()方法的应用场景是,将一个Active状态的channel移出group时使用
        // group.remove(channel);
    }

    /**
     * channelRead,这个方法当有数据读写的时候,会触发,可以读取客户端的消息
     * 只要有客户端Channel给当前的服务端发送了消息,那么就会触发该方法的执行
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        Channel channel = ctx.channel();
        System.out.println("netty客户端" + channel.remoteAddress() + "发送过来的消息:" + msg);
        channel.writeAndFlush("自己发的消息:" + msg + "\n");
        
//        String[] split = msg.split(":");
//        if (split.length >= 2) { //指定客户端时发送给指定客户
//            System.out.println("发送给客户:" + split[0]);
//            if (NettyService.userIdMap.get(split[0]) != null) {
//                NettyService.userIdMap.get(split[0]).writeAndFlush(channel.remoteAddress() + ":" + split[1] + "\n");
//            } else {
//                NettyService.userIdMap.put(split[0], ctx);  //第一次发送消息时注册客户端的channel
//            }
//        } else { //否则发送给所有的客户端
//            System.out.println("广播");
//            // 遍历channelGroup,从而区分“我”和“别人”发出的消息,如果消息是自己发出的就显示“我”
//            group.forEach(ch -> { // JDK8 提供的lambda表达式
//                if (ch != channel) {
//                    ch.writeAndFlush(channel.remoteAddress() + ":" + msg + "\n");
//                } else {
//                    channel.writeAndFlush("自己发的消息:" + msg + "\n");
//                }
//            });
//        }
    }
    /**
     * channelReadComplete,数据读取完毕之后,需要做的业务操作,回消息
     */
//    public void channelReadComplete(ChannelHandlerContext channelHandlerContext) throws Exception {
//        //消息出站
//        System.out.println("Netty服务端读取消息完毕");
//        channelHandlerContext.writeAndFlush(Unpooled.copiedBuffer("--over", CharsetUtil.UTF_8));
//    }

    /**
     * exceptionCaught,发生异常的handler
     */
    public void exceptionCaught(ChannelHandlerContext channelHandlerContext, Throwable throwable) throws Exception {
        throwable.printStackTrace();
        channelHandlerContext.close();
    }
}

3. 新建客户端

package client;

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.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

public class NettyClient {
    /**
     * 1.创建线程组
     * 2.设置线程组启动助手 bootstrap
     * 3.设置客户端通道为NIO
     * 4.创建通道初始化对象
     * 5.向pipeline中添加自定义业务处理的handler
     * 6.启动客户端,等待链接服务端,同时将异步改为同步
     * 8.关闭通道和连接池
     */
    private final String host;
    private final int port;
    public NettyClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public void createNettyClient() {
        //客户端跟服务端有些许区别,第一个就是不需要两个loopgroup了,不需要使用boosgroup了,所以只需要新建一个loopgroup来处理业务就行了
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            //第二点就是不是使用serverBootstrap,而是使用bootstrap
            Bootstrap bootstrap = new Bootstrap();
            //设置线程组
            bootstrap.group(group)
                    .channel(NioSocketChannel.class) //设置客户端的通道实现类型
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new LineBasedFrameDecoder(2048));
                            pipeline.addLast(new StringDecoder());
                            pipeline.addLast(new StringEncoder());
                            pipeline.addLast(new NettyClientHandler()); //加入自己的处理器
                        }
                    });
            //启动客户端
            ChannelFuture future = bootstrap.connect(host, port).sync();
            System.out.println("netty客户端1准备就绪");
            // 获取键盘输入
            InputStreamReader is = new InputStreamReader(System.in, StandardCharsets.UTF_8);
            BufferedReader br = new BufferedReader(is);
            // 将输入的内容写入到Channel
            while (true) {
                //br.readLine()中执行fill()方法获取输入数据,获取不到时会发生阻塞,直到获取到数据为止
                future.channel().writeAndFlush(br.readLine() + "\r\n");
            }

            //监听通道关闭的状态事件
            //channel.closeFuture().sync();
        } catch (InterruptedException | IOException e) {
            e.printStackTrace();
        } finally {
            //关闭通道和连接池
            group.shutdownGracefully();
        }
    }
}

自定义handler

package client;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

/**
 * 自定义处理Handler
 */
public class NettyClientHandler extends SimpleChannelInboundHandler<String> {
    /**
     * channelActive,当通道就绪,就会触发该方法
     */
//    public void channelActive(ChannelHandlerContext channelHandlerContext) throws Exception {
//        //发送消息到服务端
//        channelHandlerContext.writeAndFlush(Unpooled.copiedBuffer("netty客户端1启动.", CharsetUtil.UTF_8));
//    }

//    public void channelInactive(ChannelHandlerContext channelHandlerContext) throws Exception {
//
//    }
    /**
     * channelRead,当通道有读取事件时,会触发
     */
    public void channelRead0(ChannelHandlerContext channelHandlerContext, String msg) throws Exception {//接收服务端发送过来的消息
        System.out.println("收到服务端消息:" + channelHandlerContext.channel().remoteAddress() + "的消息:" + msg);
    }

    /**
     * exceptionCaught,当有异常的就会触发
     */
    public void exceptionCaught(ChannelHandlerContext channelHandlerContext, Throwable throwable) throws Exception {
        channelHandlerContext.close();
    }
}

4. 测试

启动服务器

package test;

import server.NettyService;

public class Service {
    public static void main(String[] args) {
        new NettyService(6666).createNettyService();
    }
}

启动客户端

package test;

import client.NettyClient;

public class Client1 {
    public static void main(String[] args) {
        new NettyClient("127.0.0.1",6666).createNettyClient();
    }
}

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/693446.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Avoid adding reactive properties to a Vue instance or its root $da

避免在运行时向Vue实例或其根$data添加反应性属性-在数据选项中预先声明它。 在页面中声明对象&#xff0c;直接修改即可。 data(){return{addressInfo:{}}}

阿里云服务器地域可用区怎么选?

阿里云服务器地域和可用区怎么选择&#xff1f;地域是指云服务器所在物理数据中心的位置&#xff0c;地域选择就近选择&#xff0c;访客距离地域所在城市越近网络延迟越低&#xff0c;速度就越快&#xff1b;可用区是指同一个地域下&#xff0c;网络和电力相互独立的区域&#…

如何在Mac上安装 Stable Diffusion 来创作

​ 看着别人玩&#xff0c;是不是特想自己搭建一个&#xff0c;那么现在教程来了。 玩这种需要算力的东西&#xff0c;电脑配置肯定是越高越好了。我的电脑配置如下&#xff1a;​ 接下来就开始安装了。 第一步&#xff1a;安装homebrew 打开terminal终端&#xff08;comma…

使用maven中的profile动态打包不同环境的配置文件

maven中的profile可以在打包时动态选择不同的配置文件进行打入&#xff0c;在项目具有开发、测试、生产环境时可以更方便优雅的进行不同环境的打包运行 示例图&#xff1a; 1-配置profile 第一步需要为每个环境配置一个profile标签&#xff0c;在pom文件中进行配置。我这里只…

如何做好工程英语翻译

近年来&#xff0c;随着全球经济的持续发展&#xff0c;涉外工程业务日益增多&#xff0c;工程英语翻译的需求也越来越大。那么&#xff0c;工程英语翻译难吗&#xff0c;如何做好工程英语翻译&#xff0c;服务好的北京翻译公司哪里有&#xff1f; 据了解&#xff0c;工程英语语…

macOS Ventura 13.4.1With OpenCore 0.9.3 and winPE双引导黑苹果镜像

镜像特点 完全由黑果魏叔官方制作&#xff0c;针对各种机型进行默认配置&#xff0c;让黑苹果安装不再困难。系统镜像设置为双引导分区&#xff0c;全面去除clover引导分区&#xff08;如有需要&#xff0c;可以自行直接替换opencore分区文件为clover引导文件&#xff09;备注…

知识蒸馏学习记录(二)

上一篇博文中我们介绍了知识蒸馏的一些基础知识&#xff0c;这里我们来学习其到底是如何完成知识蒸馏过程的。 知识蒸馏为何可以让学生网络模型小却性能强&#xff1f; 详细很多同学与我有相同的疑问&#xff0c;尽管它依靠不同的蒸馏温度T可以学得一些hard target标注无法包…

《计算机系统与网络安全》 第四章 密码学基础

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

8--Gradle进阶 - Gradle任务的入门、任务行为

8--Gradle进阶 - Gradle任务的入门、任务行为 Gradle Task Gradle 项目工程的管理 实质上是 Task 对象的集合。一个 Task 表示一个逻辑上较为独立的执行过程&#xff0c;比如编译Java 源代码&#xff0c;拷贝文件&#xff0c; 打包Jar 文件&#xff0c;甚至可以是执行一个系统命…

flutter Exception: Gradle task assembleDebug failed with exit code 1

Exception: Gradle task assembleDebug failed with exit code 1 解决方案&#xff1a; 出现这个问题&#xff0c;可能是依赖的插件无法下载 找到项目的android->build.gradle, 将 google() mavenCentral()改成 maven{url https://maven.aliyun.com/repository/google }m…

XML的运用(XML解析)

一、XML文件的三种配置位置机读取方式 Java中配置XML文件的三种配置位置机读取方式&#xff1a;常用的三种 1、同包下 在演式之前我们把前期演示的部分给准备好&#xff1a; 我们以properties为后缀的文本为例&#xff1a; unamemybatis_ssm upassxiaoli urljdbc:mysql://lo…

短视频矩阵-短视频seo源码开发搭建

开发场景&#xff1a;抖音seo&#xff0c;短视频seo&#xff0c;抖音矩阵&#xff0c;短视频矩阵源码开源 一、 短视频矩阵源码需要掌握以下技术&#xff1a; 1. 视频编码技术 短视频矩阵系统利用视频编码技术&#xff0c;将视频文件进行压缩和解压缩&#xff0c;实现了高质…

cut一些常用的用法

目录 介绍语法示例切割提取指定列数据切割提取指定字符数据切割提取指定字节数据切割提取指定单词数据切割提取bash进程的PID号 小结 介绍 cut 译为“剪切, 切割” , 是一个强大文本处理工具&#xff0c;它可以将文本按列进行划分的文本处理。cut命令逐行读入文本&#xff0c;…

掌握apply和call,解密JavaScript的this指向

文章目录 一、介绍apply和call方法1.1 简述apply和call方法的作用1.2 apply和call方法的共同点与不同点 二、深入理解apply方法2.1 apply方法的语法和参数介绍2.2 apply方法的使用示例2.3 apply方法的应用场景 三、深入理解call方法3.1 call方法的语法和参数介绍3.2 call方法的…

MySQL:库的操作和表的操作(内含MySQL数据类型讲解)

进入数据库的数据目录 cd var/lib/mysql 库的操作 创建数据库 创建数据库的本质是创建目录。 创建数据库时有两个编码集&#xff1a;1.数据库编码集&#xff08;存储时使用&#xff09;2.进行字段比对读取时使用的编码方式&#xff09; 语法&#xff1a; CREATE DATABASE …

SpringBoot+Prometheus采集Metrics指标数据

简介 本文介绍在springboot3.x里配置启用系统指标监控功能&#xff0c;来监视系统各项Metrics指标&#xff0c;从而实时了解应用的运行健康状态&#xff0c;通过promtheus服务提供数据收集与指标可视化方案&#xff1b; Metrics指标 metrics指标表示应用程序代码运行中提供的…

【JavaWeb】前端之CSS基础认知

目录 前言 1、CSS基本语法规范 2、向HTML中引入CSS的方式 2.1、内部样式 2.2、外部样式 2.3、内联样式 3、CSS选择器 3.1、基础选择器 3.1.1、标签选择器 3.1.2、类选择器 3.1.3、id选择器 3.1.4、通配符选择器 3.2、复合选择器 3.2.1、后代选择器 4、CSS常用元素…

leetcode514. 自由之路(java)

自由之路 leetcode514. 自由之路题目描述解题思路代码演示 动态规划专题 leetcode514. 自由之路 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/problems/freedom-trail 题目描述 电子游戏“辐射4”中&#xff0c;任务 “通向自由…

深入浅出设计模式 - 工厂模式

博主介绍&#xff1a; ✌博主从事应用安全和大数据领域&#xff0c;有8年研发经验&#xff0c;5年面试官经验&#xff0c;Java技术专家✌ Java知识图谱点击链接&#xff1a;体系化学习Java&#xff08;Java面试专题&#xff09; ​ &#x1f495;&#x1f495; 感兴趣的同学可…

目标检测舰船数据集整合

一、光学数据集 1、 DIOR 数据集(已下载yolo版本)&#xff08;论文中提到过&#xff09; “DIOR”是一个用于光学遥感图像目标检测的大规模基准数据集。数据集包含23463个图像和192472个实例&#xff0c;涵盖20个对象类。这20个对象类是飞机、机场、棒球场、篮球场、桥梁、烟囱…