Netty搭建简单的IM通信

废话不多说(netty权威指南还没看完不敢多说)。直接开整!

先介绍基本的几个概念

一LINUX网络I/O模型(所有都可以看成对一个文件的操作,共有5种)

1阻塞I/O模型:直到数据包发送到达并被进程缓冲区复制或错误才返回,一直处于阻塞等待状态

2非阻塞I/O模型:recvfrom从应用层到内核的时候,如果缓冲区没有数据,直接返回EWOULDBLOCK错误,模型回轮询此状态来判断是不是有数据到来

3I/O复用模型:Linux提供select/poll,进程通过将一个 或者多个fd传递给select或者poll系统调用,扫描fd是否就绪

4信号驱动I/O模型,开启套接口信号驱动I/O功能,并通过系统调用sigaction执行一个信号处理函数(调用后返回,进程继续工作,她是非阻塞的)。当数据准备就绪时,就为该进程生成一个SIGIO信号,通过信号回调通知应用程序调用recvfrom来读取数据,并通知主循环函数处理数据

5异步I/O:告诉内核某个操作,并让内核在整个操作完成后(将数据从内核复制到缓冲区)通知我们。这个模型与信号模型的主要区别是:一个是通知我们何时开始一个操作,一个是通知我们何时已经完成

JAVA的NIO的核心类库多路复用器Selector就是基于epoll的多路复用技术实现

JDK1.4时,新增java.nio包,提供了很多异步开发的api和类库

JDK1.7的NIO2.0发布(2011年7月),更完善的API,实现AIO(基于文件的异步I/O操作)功能,完成JSR-51定义的通道功能

这些都是基础皮毛,这里讲下可能出现的业务场景

1.做通信的实时服务

2.做软硬件结合的自动化控制服务

3.所有需要长连接的服务场景

建议开始撸码之前先了解下Netty基本东西(作为开发的话),这里不多说好吧!

.先建立NettyServer(服务端,C/S架构的思路),

1.找到自己写的一个废弃的Springoot项目(也可以自建花不了几分钟,这里废物利用),先maven构建下

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

2.在yml文件添加netty配置(本地测试)

netty:
  port: 7000
  url: 127.0.0.1

3.在启动主类继承CommandLineRunner(命令模式启动,在启动boot后执行@override的run方法)

主类添加NettyServer 类和Socket(套结字通信)服务地址类,添加代码如下:

@Value("${netty.port}")
private  int port;

@Value("${netty.url}")
private String url;

@Autowired
private NettyServer server;

/**
 * 用户运行bean的回调
 * @param args 
 * @throws Exception on error
 */
@Override
public void run(String... args) throws Exception {
    InetSocketAddress address =new InetSocketAddress(url,port);
    log.info("服务启动,地址是"+url);
    server.start(address);
}

4.对Netty的初始化配置(这里注解解释的比较简单 )

**
 * @author zhouxl
 * netty服务器
 */
@Component
@Slf4j
public class NettyServer {
    public void start(InetSocketAddress address){
        //两个独立的Reactor线程池。一个用于接收客户端的TCP连接,另一个用于处理I/O相关的读写操作,或者执行系统Task、定时任务Task等。
        EventLoopGroup bossGroup =new NioEventLoopGroup(1);
        EventLoopGroup worksGroup =new NioEventLoopGroup();
        try {
            //一个对服务端做配置和启动的类
            ServerBootstrap  bootstrap =new ServerBootstrap()//类似构造器
                   .group(bossGroup,worksGroup)//组的概念
                   .channel(NioServerSocketChannel.class)//NIO类的SocketServer通道
                   .localAddress(address)//服务地址
                   .childHandler(new ServerChannelInitializer())//拦截器实例化
                   .option(ChannelOption.SO_BACKLOG,128) //父渠道日志配置
                   .childOption(ChannelOption.SO_KEEPALIVE,true);//子渠道配置
            //绑定端口,开始接收进来的连接
            ChannelFuture future =bootstrap.bind(address).sync();
            log.info("服务已启动,端口为"+address.getPort());
            future.channel().closeFuture().sync();
        }catch (Exception e){
              e.printStackTrace();
              bossGroup.shutdownGracefully();
              worksGroup.shutdownGracefully();
        }
    }
}

5.配置渠道实例化

/**
 * @author zhouxl
 * 出站和入站的编码器和解码器
 * 继承ChannelInitializer<SocketChannel>
 */
public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel channel) throws Exception {
         channel.pipeline().addLast("decoder",new StringDecoder(CharsetUtil.UTF_8));
         channel.pipeline().addLast("encoder",new StringEncoder(CharsetUtil.UTF_8));
         channel.pipeline().addLast(new ServerHandlers());

    }
}

6.配置拦截器适配器

/**
 * @author zhouxl
 * 作为服务端拦截器,处理群聊天应用(多客户端通讯)
 */
@Slf4j
public class ServerHandlers extends ChannelInboundHandlerAdapter {
    /**
     * 创建一个静态组
     */
    private static ChannelGroup channels =new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    @Override
    public void handlerAdded(ChannelHandlerContext ctx){
        //加入ChannelGroup
        channels.add(ctx.channel());
        log.info(ctx.channel().id()+"的设备加入群聊"+"Online:"+channels.size());
    }

    @Override
    public  void handlerRemoved(ChannelHandlerContext context){
        log.info(context.channel().id()+"的设备退出群聊"+"Online:"+channels.size());
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //打印消息然后群发
        log.info(msg.toString());
        for (Channel channel:channels){
            channel.writeAndFlush(msg.toString());
        }
    }

    /**
     * 设备异常关闭
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx,Throwable cause)throws Exception{
      log.info(ctx.channel().id()+"的设备出错"+"Online:"+channels.size());
      ctx.close();
    }
}

配置完成,启动!(8019是boot端口地址,7000是Netty服务器端口)

三.建立客户端(这里面客户端可能不是springboot,有可能还不是软件项目,可以是支持通讯的多数客户端)

1.新建Client类,定义一些基本参数

static  final String Host = System.getProperty("host","127.0.0.1");//需要连接的地址
static  final int PORT = Integer.valueOf(System.getProperty("port","7000"));//端口号
static  final int SIZE = Integer.valueOf(System.getProperty("size","256"));//文件大小
static  final String  name = System.getProperty("name","9527"); //客户端名称

//mian函数启动类

public static void main(String[] args) throws  Exception{
    sendMessage("去吧,皮卡丘!");
}
/**
 * 群里发送信息
 * @param content
 * @throws InterruptedException
 */
public static void sendMessage(String content) throws InterruptedException{
    //配置客户端
    EventLoopGroup group = new NioEventLoopGroup();//NIO组概念
    try {
        Bootstrap b =new Bootstrap();//架构器
        b.group(group)
                .channel(NioSocketChannel.class)//渠道类型
                .option(ChannelOption.TCP_NODELAY,true)//模式
                .handler(new ChannelInitializer<SocketChannel>(){
                    @Override
                    public  void initChannel(SocketChannel ch) throws Exception{
                        ChannelPipeline p =ch.pipeline();
                        p.addLast("decoder",new StringDecoder());
                        p.addLast("encoder",new StringEncoder());
                        p.addLast(new ClientHandler());
                    }//拦截器设置
                });
        ChannelFuture future =b.connect(Host,PORT).sync();//渠道连接
        Scanner sca =new Scanner(System.in); //扫描黑窗口
        while(true){
            //输入内容
            String str =sca.nextLine();
            if(str.equals("exit")){
                break;
            }
            //将名字和信息内容一起发过去
           future.channel().writeAndFlush(name+"-:"+str);

        }
        future.channel().writeAndFlush(content);
        future.channel().closeFuture().sync();
    }finally {
        group.shutdownGracefully();
    }
}

2.ClientHander(客户端拦截器)

**
 * @author zhouxl
 * 作为服务端拦截器,处理一些简单的逻辑
 */
@Slf4j
public class ClientHandler extends ChannelInboundHandlerAdapter {
    @Override
    public  void channelActive(ChannelHandlerContext cx){
        log.info("客户端拦截器生效");
    }

    /**
     * 读取信息
     * @param ctx  渠道连接对象
     * @param msg  信息
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx,Object msg)throws Exception{

            log.info("客户端读写拦截开始生效-----------");
            log.info("客户端端读写远程地址是-----------"+ctx.channel().remoteAddress()+"信息是:"+msg.toString());

    }

    /**
     * 发生异常关闭连接
     * @param ctx 渠道连接对象
     * @param cause 异常
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx,Throwable cause) throws Exception{
        cause.printStackTrace();
        ctx.close();
    }

}

3.配置渠道实例化

/**
 * @author zhouxl
 * 出站和入站的编码器和解码器
 * 继承ChannelInitializer<SocketChannel>
 */
public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel channel) throws Exception {
        ChannelPipeline p =channel.pipeline();
         p.addLast("decoder",new StringDecoder(CharsetUtil.UTF_8));
         p.addLast("encoder",new StringEncoder(CharsetUtil.UTF_8));
         p.addLast(new ClientHandler());

    }
}

OK了,启动!

服务端在客户端启动时日志如下:

发送个消息试试,在客户端的黑窗口输入点字符:

服务端如下:

再加一个客户端2,发送信息"去吧,皮卡丘!"

客户端2如下:

服务端如下:

服务端1如下:

至此基本功能实现,也可以采用jar包模式直接启动(java -jar jar包名),黑窗口聊天,BEST聊天!

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值