netty创建聊天室服务端及单机并发测试结果

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sureSand/article/details/82980770

前言

netty应该是Java网络通信开发的杀手级框架,它提供异步,事件驱动的网络应用程序框架和工具。

至于netty的概念和优点很多,在此我就不献丑了,我纯粹实在官方文档的小例子基础上做一些测试以印证自己心中的想法。

首先搭建一个netty服务端,新建一个maven项目,引入依赖:

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

handler处理器

package cn.com.handler.server;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;

/**
 * @Auther: kc
 * @Date: 2018/10/8 20:28
 * @Description:
 */
public class SimpleChatServerHandler extends SimpleChannelInboundHandler<String> { // (1)

    public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    //服务端从客户端收到新的连接的时候,客户端的channel存入channerGroup列表中,并通知列表中的其他客户端
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {  // (2)
        Channel incoming = ctx.channel();

        // Broadcast a message to multiple Channels
        channels.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 加入\n");

        channels.add(ctx.channel());
    }
    //当从服务端收到客户端断开时,客户端自动从channelgroup中离开,并通知其它客户端
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {  // (3)
        Channel incoming = ctx.channel();

        // Broadcast a message to multiple Channels
        channels.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 离开\n");

        // A closed Channel is automatically removed from ChannelGroup,
        // so there is no need to do "channels.remove(ctx.channel());"
    }
    //每当服务端读到客户端写入信息时,将信息转发给其它客户端的channel
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String s) throws Exception { // (4)
        Channel incoming = ctx.channel();
        for (Channel channel : channels) {
            if (channel != incoming){
                channel.writeAndFlush("[" + incoming.remoteAddress() + "]" + s + "\n");
            } else {
                channel.writeAndFlush("[you]" + s + "\n");
            }
        }
    }
    //服务端监听到客户端活动
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception { // (5)
        Channel incoming = ctx.channel();
        System.out.println("SimpleChatClient:"+incoming.remoteAddress()+"在线");
    }
    //服务端监听到客户端不活动
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception { // (6)
        Channel incoming = ctx.channel();
        System.out.println("SimpleChatClient:"+incoming.remoteAddress()+"掉线");
    }
    //当处理方法出现throwable对象才会被调用,即当netty由于IO错误或者处理器在处理事件时抛出的异常
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (7)
        Channel incoming = ctx.channel();
        System.out.println("SimpleChatClient:"+incoming.remoteAddress()+"异常");
        // 当出现异常就关闭连接
        cause.printStackTrace();
        ctx.close();
    }
}

SimpleChatServerInitializer.java

用来增加多个的处理类到 ChannelPipeline 上,包括编码、解码、SimpleChatServerHandler 等。

package cn.com.handler.server;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

/**
 * @Auther: kc
 * @Date: 2018/10/8 20:35
 * @Description: 用来增加多个的处理类到ChannelPipeline,包括编码、解码、simpleChatServerHandler
 */
public class SimpleChatServerInitializer extends
        ChannelInitializer<SocketChannel> {

    @Override
    public void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();

        pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
        pipeline.addLast("decoder", new StringDecoder());
        pipeline.addLast("encoder", new StringEncoder());
        pipeline.addLast("handler", new SimpleChatServerHandler());

        System.out.println("SimpleChatClient:"+ch.remoteAddress() +"连接上");
    }
}

SimpleChatServer.java

编写一个 main() 方法来启动服务端:

package cn.com.handler.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * @Auther: kc
 * @Date: 2018/10/8 20:38
 * @Description:
 */
public class SimpleChatServer {
    private int port;

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

    public void run() throws Exception {
        //用来处理IO操作的多线程事件循环器,boss用来接收接进来的连接,worker用来处理已经接收的连接,一旦boss收到连接就会把连接信息注册到worker上
        EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap(); // ServerBootStrap是一个启动NIO服务的辅助启动类
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class) // 指定使用NioServerSocketChannel类来举例说明一个新的channel如何接收进来的连接
                    .childHandler(new SimpleChatServerInitializer())  //调用SimpleChatServerInitializer
                    .option(ChannelOption.SO_BACKLOG, 128)          // (5)
                    .childOption(ChannelOption.SO_KEEPALIVE, true); // (6)

            System.out.println("SimpleChatServer 启动了");

            // 绑定端口,开始接收进来的连接
            ChannelFuture f = b.bind(port).sync(); // (7)

            // 等待服务器  socket 关闭 。
            // 在这个例子中,这不会发生,但你可以优雅地关闭你的服务器。
            f.channel().closeFuture().sync();

        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();

            System.out.println("SimpleChatServer 关闭了");
        }
    }

    public static void main(String[] args) throws Exception {
        int port;
        if (args.length > 0) {
            port = Integer.parseInt(args[0]);
        } else {
            port = 8080;
        }
        new SimpleChatServer(port).run();

    }
}

启动main方法

Jmeter测试

我是按照官网的例子写的,在自己电脑(4核,8G内存/没测试之前内存用了4。5G,cpu占用15%左右)跑服务端,跑jmeter测试,没经过任何优化,首先我用1秒钟500次请求
在这里插入图片描述

可以发现请求的一瞬间cpu占用会到80%,而内存却没有多大占用。

然后我用100秒的时间去请求10000次,这里截不了图,因为会很卡,大概到6000个请求的时候,就会吃完我电脑剩下的内存,但是cpu应该还犹有余力。

总结

我并不知道我这样的测试是否合理,但是看来一台8G,4核的服务器host1w个请求肯定是可以得,我觉得应该还可以优化让netty接受更多的请求,继续学习吧!

猜你喜欢

转载自blog.csdn.net/sureSand/article/details/82980770