Netty,Tcp,socket的java框架,netty学习

最新更新,报文发送,机器终端(车)与服务端

先学习一下基本内容,以下是基于基本内容

互相转换:byte(字节,字节是数字单位,他的数组是十进制内容),bit(二进制内容,不用操心这部分),十六进制0x(0-9-a-f),String(字符的数组,引用类型)

框架与语言:socket(tcp),java,netty

java代码

终端发给服务端,16进制字符串 转换 10进制的字节数组(数字数组)。通过outStream流发送。

服务端下发给终端,与上句原理一样。

String sourceStrs="AB AA";//AA是170,AB是171//有多个用空格隔开

//因为bytes[]不能追加,所以用字节流写入,写入后在转换
ByteArrayOutputStream output = new ByteArrayOutputStream();

String[] sourceStrArr = sourceStrs.split(" ");
for (int i = 0; i < sourceStrArr.length; i++) {
	String sourceStr = sourceStrArr[i];
	String byteNum = Tools.trans16t10(sourceStr);//将16进制字符串转换 得到字节
	output.write(Integer.parseInt(byteNum));//将单个字节追加
}
byte[] sendBytes = output.toByteArray();//将字节流转换为字节数组
//写一个字节数组过去
outputStream.write(sendBytes);

服务端解析终端,二进制转换为byte[]数组,数组转为16进制。就能解析出16进制内容

我用的netty,他的事件部分,decode执行的部分

//创建一个字节数组
byte[] bufs2=new byte[in.readableBytes()];
//将接收的字节存放到字节数组中
in.readBytes(bufs2);//in是ByteBuf in重写的内容,终端传来的数据
//将字节数组,他转成16进制的内容,这样就能和协议匹配了
String jiqiStr = TuLiTcpTools.bytes2hex(bufs2);

方法补充:

trans16t10

 public static String trans16t10(String str){

        String myStr[] = { "a", "b", "c", "d", "e", "f" };
        int result = 0;
        int n = 1;
        for (int i = str.length() - 1; i >= 0; i--) {
            String param = str.substring(i, i + 1);
            for (int j = 0; j < myStr.length; j++) {
                if (param.equalsIgnoreCase(myStr[j])) {
                    param = "1" + String.valueOf(j);
                }
            }
            result += Integer.parseInt(param) * n;
            n *= 16;
        }
//        System.out.println(result);
//        System.out.println(Integer.parseInt(str, 16));
        return String.valueOf(result);
    }
bytes2hex,10进制转16进制
public static String bytes2hex(byte[] bytes) {
    StringBuilder sb = new StringBuilder();
    String tmp;
    sb.append("[");
    for (byte b : bytes) {
        // 将每个字节与0xFF进行与运算,然后转化为10进制,然后借助于Integer再转化为16进制
        tmp = Integer.toHexString(0xFF & b);
        if (tmp.length() == 1) {
            tmp = "0" + tmp;//只有一位的前面补个0
        }
        sb.append(tmp).append(" ");//每个字节用空格断开
    }
    sb.delete(sb.length() - 1, sb.length());//删除最后一个字节后面对于的空格
    sb.append("]");
    return sb.toString();
}

netty程序

一个netty程序,分3个内容,前2个必须要,第3个依据业务可选

1.netty服务端的server,包含netty的配置和启动

2.handler处理端,对客户端(终端)发来的数据进行处理

3.编解码器,它其实是两部分,编码和解码,一般把他定义在handler之前

一个完整的netty服务端(JAVA版)

导入netty依赖

		
			io.netty
			netty-all
			4.1.36.Final
		

编写server

package nettyServer;

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.channel.socket.nio.NioSocketChannel;

public class nServer {
    public static void main(String[] args) throws InterruptedException {
        //创建bossGroup,接受连接请求,用evnetLoopGroup接受
        //创建workGroup,执行工作,业务处理
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup();
        //上面2个是线程组,loop循环,这2个都是无限循环

        //创建配置参数
        ServerBootstrap bootstrap = new ServerBootstrap();

        //使用链式编程,设置
        bootstrap.group(bossGroup,workGroup)//设置2个线程组
                .channel(NioServerSocketChannel.class)//指定服务通道为nio模型
                .option(ChannelOption.SO_BACKLOG,128)//设置线程得到的连接个数
                .childOption(ChannelOption.SO_KEEPALIVE,true)//设置保持长连接状态
                .childHandler(new ChannelInitializer() {
                    @Override//给pipline设置处理器
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        ChannelPipeline pipeline = socketChannel.pipeline();

                        pipeline.addLast("decoder",new nDecode());
                        pipeline.addLast(new nHandler());//增加处理器,handler

                    }
                });//设置work的EventLoop对应管道设置处理器
        System.out.println("服务器准备完成");

        //绑定端口,并且同步处理,future对象
        ChannelFuture channelFuture = bootstrap.bind(12306).sync();
        //当监听到后,处理完,在关闭,没有监听到则不会调用
        channelFuture.channel().closeFuture().sync();

    }
}

编写handler

注意:尽量用try catch,如果像我这样Exception,在springBoot中,异常状况下,客户端可能收不到数据,并且不会出现打印异常,让你错以为是卡死的情况。

package nettyServer;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

//自定义一个handler,需要继承netty定义好的handler适配器,否则无效
public class nHandler extends ChannelInboundHandlerAdapter{
    @Override
    //ChannelHandlerContext管道,通道,地址,他都能拿到
    //msg是客户端发送来的数据
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("server ctx="+ctx);
        //将msg 转成一个ByteBuf
        //ByteBuf 是 Netty 提供的,注意不要使用nio的byteBuffer
        System.out.println(msg);
        ByteBuf buf = (ByteBuf) msg;
        System.out.println("客户端发送的消息是"+buf.toString(CharsetUtil.UTF_8));

    }

    @Override
    //读完了客户端的消息后,执行的内容
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        //要写完,之后在flush,flush是发送(刷新,到通道)
        //对发送的数据进行编码
        ctx.channel().writeAndFlush(Unpooled.copiedBuffer("我收到了\r\n",CharsetUtil.UTF_8));
    }

    //处理异常,一般是关闭通道
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

编写解码器业务不需要可以删除

注:可以复制重写的方法,但是不能直接复制,我的代码内容,我引入了自己业务中的工具类。

package com.dt.tuli.springBoot_netty;

import com.dt.tuli.tools.TuLiTcpTools;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.util.CharsetUtil;
import nettyServer.nServerAccept;

import java.util.List;

public class nDecodeSpringBoot extends ByteToMessageDecoder {

    //ctx上下文,比如地址ip,端口等,从这拿
    //in,客户端发来的数据
    //rs经过解码器,最后保留的数据,输出的数据
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List rs)   {
        //创建一个字节数组
        byte[] bufs2=new byte[in.readableBytes()];
        //将接收的字节存放到字节数组中
        in.readBytes(bufs2);

        System.out.println("刚接受来的数据---------");//10进制
        for (int i = 0; i < bufs2.length; i++) {
            System.out.print(bufs2[i]);
        }
        System.out.println();
        System.out.println("----------------");
        //读取成字符串,打印下
//        byte bufs[]=in.toString(CharsetUtil.UTF_8).getBytes();
//        System.out.println("终端gps发过来的内容---------");
//        System.out.println(in.toString(CharsetUtil.UTF_8));
        //将他转成16进制的内容
        String jiqiStr = TuLiTcpTools.bytes2hex(bufs2);
        System.out.println("转成16进制的数据"+jiqiStr);

        try {

            //解析后存入rs中
            int size = TuLiTcpTools.getSize(jiqiStr);
            System.out.println("数据的长度是"+size);
            nServerAccept nServerAccept = TuLiTcpTools.parseData(jiqiStr, size);//封装成功

            rs.add(nServerAccept);
        }catch (Exception e){
            e.printStackTrace();
        }


        System.out.println("------------------------解码结束");
    }

/*    public static void main(String[] args) {

        ByteBuf buf= ByteBufAllocator.DEFAULT.buffer();
        StringBuilder sb=new StringBuilder();


        for(int i=0;i<5;i++){
            sb.append(123);
        }
        buf.writeBytes(sb.toString().getBytes());

        System.out.println(buf.readableBytes());
        System.out.println(buf.toString(CharsetUtil.UTF_8));//给我的字节二进制

        System.out.println(buf.capacity());
        byte bufs[] = new byte[buf.readableBytes()];


        bufs=buf.toString(CharsetUtil.UTF_8).getBytes();

        System.out.println(bufs);

        String jiqiStr = TuLiTcpTools.bytes2hex(bufs);
        System.out.println(jiqiStr);
    }*/
}

测试客户端编写

:删除了其中的业务敏感数据,所以与打印结果有所不同


import java.io.*;
import java.net.Socket;

public class SocketClient {

    public static void main(String[] args) throws InterruptedException {
        try {
            // 和服务器创建连接
            Socket socket = new Socket("localhost",12306);
            // 要发送给服务器的信息
            OutputStream os = socket.getOutputStream();
            PrintWriter pw = new PrintWriter(os);
//            byte[] bytes = new byte[97 98 99 10 01 01 10 21 03 49];
//            pw.write(0xAA);
//
//
//
//            pw.flush();
            os.write("xxxxx".getBytes());


//            找到原因了,我代码里面写了个循环
            // 从服务器接收的信息
            InputStream is = socket.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String info = null;
            while((info = br.readLine())!=null){
                System.out.println("我是客户端,服务器返回信息:"+info);
            }
            socket.shutdownOutput();
            br.close();
            is.close();
            os.close();
            pw.close();
            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

结果

客户端

服务端

Netty,Tcp,socket的java框架,netty学习_第1张图片


netty普通server转springBoot+netty

在springBoot中集成netty的目的是,可以通过springBoot的接口,向netty下发指令

上一个案例,是利用netty本身的server,而集成到springBoot中,需要新开一个线程运行netty服务,并且在启动springBoot的同时,启动netty。

新开一个线程,需要修改netty的server服务

启动springBoot的同时,启动netty,需要新写一个springBoot的config类

修改netty的server  (nServerSpringBoot)

package com.dt.tuli.springBoot_netty;

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 nettyServer.nDecode;


public class nServer4SpringBoot {
    public static void bind(int port) throws InterruptedException {

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                //创建bossGroup,接受连接请求,用evnetLoopGroup接受
                //创建workGroup,执行工作,业务处理
                EventLoopGroup bossGroup = new NioEventLoopGroup();
                EventLoopGroup workGroup = new NioEventLoopGroup();
                //上面2个是线程组,loop循环,这2个都是无限循环
                try {
                    //创建配置参数
                    ServerBootstrap bootstrap = new ServerBootstrap();

                    //使用链式编程,设置
                    bootstrap.group(bossGroup, workGroup)//设置2个线程组
                            .channel(NioServerSocketChannel.class)//指定服务通道为nio模型
                            .option(ChannelOption.SO_BACKLOG, 128)//设置线程得到的连接个数
                            .childOption(ChannelOption.SO_KEEPALIVE, true)//设置保持长连接状态
                            .childHandler(new ChannelInitializer() {
                                @Override//给pipline设置处理器
                                protected void initChannel(SocketChannel socketChannel) throws Exception {
                                    ChannelPipeline pipeline = socketChannel.pipeline();
                                    pipeline.addLast(new nDecode());
                                    pipeline.addLast(new nHandler4SpringBoot());//增加处理器,handler
                                }
                            });//设置work的EventLoop对应管道设置处理器
                    System.out.println("服务器准备完成");

                    //绑定端口,并且同步处理,future对象
                    ChannelFuture channelFuture = bootstrap.bind(port).sync();
                    //当监听到后,处理完,在关闭,没有监听到则不会调用
                    channelFuture.channel().closeFuture().sync();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    bossGroup.shutdownGracefully();
                }

            }
        });

        thread.start();
    }
}

新建springBoot配置类

package com.dt.tuli.springBoot_netty;

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

@Component
public class nConfig4SpringBoot implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        nServer4SpringBoot.bind(12306);

    }

}

此时启动springBoot,则会自动启动netty


springBoot,服务端如何主动下发指令,给机器

1.对每台机器,建立映射,存储套接字

2.服务端通过套接字发送给机器

我们业务,车端会通过tcp协议,不断的往服务端的某个端口发送数据-登录数据,此时服务端可以根据厂家协议,解析登录数据,获取到登录数据的信息,比如,车端发送的手机卡号,设备编号。

此时就可以存储设备编号,和socket连接,在netty中存储的是channel通道。

下次要主动下发的时候,从map中取出对应的编号的socket连接,进行发送。

建立存储套接字map

package com.dt.tuli.springBoot_netty;

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

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

//用来存储客户端和服务端建立的管道
public enum TCPCache {

    INSTANCE;

    private Map clientInfoContext = new ConcurrentHashMap<>();
    private Map serverInfoContext = new ConcurrentHashMap<>();

    public Map getClientInfoContext() {
        return clientInfoContext;
    }

    public Map getServerInfoContext() {
        return serverInfoContext;
    }

}

将socket套接字存入,套接字Map中(在hadnler的某个事件中,为了测试我放在了

channelReadComplete方法部分。实际业务是放在了channelRead部分,解码成功后校验完毕,进行存储。

Netty,Tcp,socket的java框架,netty学习_第2张图片

其中test是唯一编号

TCPCache.INSTANCE.getClientInfoContext().computeIfAbsent("test",no -> ctx);//no是别名

创建springBoot类

@PostMapping("/send")
    @ResponseBody
    //服务端主动推送测试
    public R TestSendMessage() {

        Map clientInfoContext = TCPCache.INSTANCE.getClientInfoContext();
        System.out.println("进来接口了");
        System.out.println(clientInfoContext.keySet());
        if(clientInfoContext.get("test")==null){
            return failed("终端,还没有和服务端建立tcp连接");
        }
//        Unpooled.copiedBuffer()
        try {
            ChannelHandlerContext ctx = clientInfoContext.get("test");
            ctx.channel().writeAndFlush(Unpooled.copiedBuffer("123\n".getBytes())).sync();
//            ctx.writeAndFlush("123\n");
        } catch (Exception e) {
            e.printStackTrace();
        }



        return ok("推送完成");
    }

测试中: 我的客户端测试是用\n区分是不是下一段的。所以通过请求写数据要\n。(实际开发,我是以包长度进行获取的)另外启动服务端后,要启动客户端才可以主动发送数据,因为客户端在启动时候,我的代码中向服务端发送了消息,建立了连接。收到数据后,服务端会存储这个连接,之后springBoot才能主动推送数据。

结果:

Netty,Tcp,socket的java框架,netty学习_第3张图片

 

客户端:

Netty,Tcp,socket的java框架,netty学习_第4张图片

 

 


杂谈,如果消息不想要的话,或者不符合你的规则就丢掉,比如,我发送的开头必须有aa aa,结果他发了个a1 aa,这就不符合规则,你想要丢掉,就在decode解码的时候,进行return;这样消息就不会发送给处理者,而是直接到结束者阶段。

修改decode代码

jiqiStr是我发过来的数据,而我截取了他的开头,看看符不符合规则

String substring = jiqiStr.substring(0, 8);//随便取的前面的数,看看开头有没有aa,没有则说明不是这个协议的报头,直接丢弃
if (!substring.contains("aa aa")){
    //如果没有,则直接丢弃,不会走对数据的处理方法,而直接走处理完数据后的方法
    return;
}//下面的语句都不会被执行

........

rs.add(nServerAccept);//List  rs ,这句话不会被执行,这句话不被执行的话,处理端就不会被调用

测试,含aa aa的正确规则,返回结果

Netty,Tcp,socket的java框架,netty学习_第5张图片

含a1 aa的错误规则,返回结果

Netty,Tcp,socket的java框架,netty学习_第6张图片

服务端的handler代码

Netty,Tcp,socket的java框架,netty学习_第7张图片


由于业务需要,一个服务,要同时与多辆车连接,并且能够通过接口向车下发指令。车上报数据,给服务端,服务端入时序数据库。

由于一个车辆需要和服务连接,多个车辆将消耗多个线程,而线程又是由cpu产生,线程之间的传递是通过网络,这里的硬件要求就是,高cpu核数线程数,以及宽带网速高和稳定

netty比较复杂,他是封装了nio,然后在封装了netty,然后在改进了roactor。所以框架比较复杂。学起来需要时间。建议不要速成,速成后,你没法改成你自己想要的业务。

044_尚硅谷_Netty入门-服务端1_哔哩哔哩_bilibili

解码器,解协议:

Netty自定义编-解码器解决TCP通讯粘包拆包的问题 - william_zhao - 博客园

netty是什么?


netty是一个nio框架,解决了socket的单线程效率低,采用了nio的优势,多线程,但是又屏蔽了nio的复杂性。

但是nio肯定是略低于bio的速度的。bio会一直阻塞,来了就收发,nio会缓冲,如果没有任务,可以先去做其他的事,然后再切回来。

ps:科普,bio是传统框架,同步进行会阻塞,效率不高,nio不会阻塞。

NIO全称 java non-blocking IO。

NIO三大核心部分:Channel(通道),Buffer(缓冲区),Selector(选择器)

深入了解:BIO和NIO的区别_你喜欢炸酱面么的博客-CSDN博客_bio nio

netty的架构原理


采用nio的模式,原来bio一个socket对应一个线程,变成了多个socket对应一个线程

Netty,Tcp,socket的java框架,netty学习_第8张图片

select

 原理是,用户提交读写后,会交给1个线程进行注册,所有的用户都交给这1个线程注册,注册后,这个线程就会对注册的事件,进行监控。一旦有消息发送来了,他就监控到了,然后发到缓冲区,这时候,会回调出一个新线程来处理,处理完,新线程又回去做其他的事情。

Netty,Tcp,socket的java框架,netty学习_第9张图片

buffer缓冲区

数据只能从channel中读到buffer中,或者把数据从buffer写入channel中

Netty,Tcp,socket的java框架,netty学习_第10张图片

线程模型

选择让哪个线程进行解码,这将很影响性能

线程模型1: 事件驱动,轮询查询

轮询就是不断的判断是否存在,存在则处理

事件驱动模型,就是把任务发到队列,另1个线程拉取队列内容,分发到不同的子线程中去执行任务,如下:

Netty,Tcp,socket的java框架,netty学习_第11张图片

 reactor模型

netty模型

Netty,Tcp,socket的java框架,netty学习_第12张图片

模块组件

这个比较重要,设计到代码

模块组件

【Bootstrap、ServerBootstrap】

Bootstrap 意思是引导,一个 Netty 应用通常由一个 Bootstrap 开始,主要作用是配置整个 Netty 程序,串联各个组件,Netty 中 Bootstrap 类是客户端程序的启动引导类,ServerBootstrap 是服务端启动引导类。

【Future、ChannelFuture】

正如前面介绍,在 Netty 中所有的 IO 操作都是异步的,不能立刻得知消息是否被正确处理。但是可以过一会等它执行完成或者直接注册一个监听,具体的实现就是通过 Future 和 ChannelFutures,他们可以注册一个监听,当操作执行成功或失败时监听会自动触发注册的监听事件。

【Channel】

Netty 网络通信的组件,能够用于执行网络 I/O 操作。Channel 为用户提供:

1)当前网络连接的通道的状态(例如是否打开?是否已连接?)

2)网络连接的配置参数 (例如接收缓冲区大小)

3)提供异步的网络 I/O 操作(如建立连接,读写,绑定端口),异步调用意味着任何 I/O 调用都将立即返回,并且不保证在调用结束时所请求的 I/O 操作已完成。

4)调用立即返回一个 ChannelFuture 实例,通过注册监听器到 ChannelFuture 上,可以 I/O 操作成功、失败或取消时回调通知调用方。

5)支持关联 I/O 操作与对应的处理程序。不同协议、不同的阻塞类型的连接都有不同的 Channel 类型与之对应。

下面是一些常用的 Channel 类型:

NioSocketChannel,异步的客户端 TCP Socket 连接。
NioServerSocketChannel,异步的服务器端 TCP Socket 连接。
NioDatagramChannel,异步的 UDP 连接。
NioSctpChannel,异步的客户端 Sctp 连接。
NioSctpServerChannel,异步的 Sctp 服务器端连接,这些通道涵盖了 UDP 和 TCP 网络 IO 以及文件 IO。

【Selector】

Netty 基于 Selector 对象实现 I/O 多路复用,通过 Selector 一个线程可以监听多个连接的 Channel 事件。当向一个 Selector 中注册 Channel 后,Selector 内部的机制就可以自动不断地查询(Select) 这些注册的 Channel 是否有已就绪的 I/O 事件(例如可读,可写,网络连接完成等),这样程序就可以很简单地使用一个线程高效地管理多个 Channel 。

【NioEventLoop】

NioEventLoop 中维护了一个线程和任务队列,支持异步提交执行任务,线程启动时会调用 NioEventLoop 的 run 方法,执行 I/O 任务和非 I/O 任务:I/O 任务,即 selectionKey 中 ready 的事件,如 accept、connect、read、write 等,由 processSelectedKeys 方法触发。非 IO 任务,添加到 taskQueue 中的任务,如 register0、bind0 等任务,由 runAllTasks 方法触发。两种任务的执行时间比由变量 ioRatio 控制,默认为 50,则表示允许非 IO 任务执行的时间与 IO 任务的执行时间相等。

【NioEventLoopGroup】

NioEventLoopGroup,主要管理 eventLoop 的生命周期,可以理解为一个线程池,内部维护了一组线程,每个线程(NioEventLoop)负责处理多个 Channel 上的事件,而一个 Channel 只对应于一个线程。

【ChannelHandler】

ChannelHandler 是一个接口,处理 I/O 事件或拦截 I/O 操作,并将其转发到其 ChannelPipeline(业务处理链)中的下一个处理程序。ChannelHandler 本身并没有提供很多方法,因为这个接口有许多的方法需要实现,方便使用期间,可以继承它的子类:
ChannelInboundHandler 用于处理入站 I/O 事件。
ChannelOutboundHandler 用于处理出站 I/O 操作。
或者使用以下适配器类:
ChannelInboundHandlerAdapter 用于处理入站 I/O 事件。
ChannelOutboundHandlerAdapter 用于处理出站 I/O 操作。
ChannelDuplexHandler 用于处理入站和出站事件。

【ChannelHandlerContext】

保存 Channel 相关的所有上下文信息,同时关联一个 ChannelHandler 对象。

【ChannelPipline】

保存 ChannelHandler 的 List,用于处理或拦截 Channel 的入站事件和出站操作。ChannelPipeline 实现了一种高级形式的拦截过滤器模式,使用户可以完全控制事件的处理方式,以及 Channel 中各个的 ChannelHandler 如何相互交互。

下图引用 Netty 的 Javadoc 4.1 中 ChannelPipeline 的说明,描述了 ChannelPipeline 中 ChannelHandler 通常如何处理 I/O 事件。I/O 事件由 ChannelInboundHandler 或 ChannelOutboundHandler 处理,并通过调用 ChannelHandlerContext 中定义的事件传播方法。

Netty,Tcp,socket的java框架,netty学习_第13张图片

例如:ChannelHandlerContext.fireChannelRead(Object)和 ChannelOutboundInvoker.write(Object)转发到其最近的处理程序。

入站事件由自下而上方向的入站处理程序处理,如图左侧所示。入站 Handler 处理程序通常处理由图底部的 I/O 线程生成的入站数据。通常通过实际输入操作(例如 SocketChannel.read(ByteBuffer))从远程读取入站数据。出站事件由上下方向处理,如图右侧所示。出站 Handler 处理程序通常会生成或转换出站传输,例如 write 请求。I/O 线程通常执行实际的输出操作,例如 SocketChannel.write(ByteBuffer)。在 Netty 中每个 Channel 都有且仅有一个 ChannelPipeline 与之对应,它们的组成关系如下:

Netty,Tcp,socket的java框架,netty学习_第14张图片

一个 Channel 包含了一个 ChannelPipeline,而 ChannelPipeline 中又维护了一个由 ChannelHandlerContext 组成的双向链表,并且每个 ChannelHandlerContext 中又关联着一个 ChannelHandler。

入站事件和出站事件在一个双向链表中,入站事件会从链表 head 往后传递到最后一个入站的 handler,出站事件会从链表 tail 往前传递到最前一个出站的 handler,两种类型的 handler 互不干扰。

没有时间了,没想到netty学习还是比较复杂的,由于工作期限的要求,我现在开始使用步骤,大家感兴趣,可以看下方的阅读

原理参考:

Netty:原理架构解析_区块链之美的博客-CSDN博客_netty原理详解

Netty核心原理_myself study log的博客-CSDN博客_netty原理

netty的使用


    我采用的是java编写

    依赖导入


    io.netty
    netty-all
    4.1.36.Final

    netty服务端创建

    服务端要创建的有

    监听类,负责读取消息

   连接类,负责处理消息

   启动类,负责启动

   公共代码,负责保存,客户端于服务端的连接关系

  监听类:

        

 链接类:

  启动类:

   公共代码类:

参考:

一个简单的Netty demo_颜翎的博客-CSDN博客_netty的demo

下面这个文章,可以完成,服务端,向客户端发送命令。

Netty--TCP--实例_IT利刃出鞘的博客-CSDN博客_netty tcp实例

springBoot和netty案例:

Spring Boot + Netty + WebSocket 实现消息推送 (qq.com)

踩坑:

netty客户端能收到netty服务端的,socket客户端能收到socket服务端的,netty服务端能收到socket客户端的,socket客户端【收不到】netty的,一直卡着

原因是因为,netty不知道你消息发送完了,所以会一直卡着,这句话的意思是告诉他,我发送完了。

Netty,Tcp,socket的java框架,netty学习_第15张图片

socket(客户端)与netty(服务端)交互。在客户端发送完消息后加上

socket.shutdownOutput();

但是这不是最终解法,最终的问题是netty身上,如果是这样,那每次客户端都需要重新和netty建立连接。开销大,而且会丢包。

最终的解法是设定netty,让netty回复客户端。底层tcp不知道,你到底有没有发完,你关闭后,可能他以为是发完了。这种情况需要使用消息解析器去判断

消息解析器,每次发完一段话,给他一个结束标志,他就知道,是不是这段消息发送完了。

最佳方案,居然是 在服务端的回复后面加"\n" 

Netty,Tcp,socket的java框架,netty学习_第16张图片

你可能感兴趣的:(java,java,tcp/ip,学习)