t-io比netty开发快速

t-io对于快速开发物联网,或者长连接请求,可以方便快速开发,他的底层基于netty的编写的,更适合开发。但是面试需要了解Netty的底层原理与协议。

    分为server和client工程,server和client共用common工程
    服务端和客户端的消息协议比较简单,消息头为4个字节,用以表示消息体的长度,消息体为一个字符串的byte[]
    服务端先启动,监听6789端口
    客户端连接到服务端后,会主动向服务器发送一条消息
    服务器收到消息后会回应一条消息
    之后,框架层会自动从客户端发心跳到服务器,服务器也会检测心跳有没有超时(这些事都是框架做的,业务层只需要配一个心跳超时参数即可)
    框架层会在断链后自动重连(这些事都是框架做的,业务层只需要配一个重连配置对象即可)

需要导入pom.xml

<dependency>
    <groupId>org.t-io</groupId>
    <artifactId>tio-core</artifactId>
    <version>3.0.0.v20180520-RELEASE</version>
</dependency>

定义Packet



    package org.tio.examples.helloworld.common;

    import org.tio.core.intf.Packet;

    /**
    * @author tanyaowu
    */
    public class HelloPacket extends Packet {
        private static final long serialVersionUID = -172060606924066412L;
        public static final int HEADER_LENGHT = 4;//消息头的长度
        public static final String CHARSET = "utf-8";
        private byte[] body;

        /**
        * @return the body
        */
        public byte[] getBody() {
            return body;
        }

        /**
        * @param body the body to set
        */
        public void setBody(byte[] body) {
            this.body = body;
        }
    }

定义服务器端和客户端都用得到的常量


    package org.tio.examples.helloworld.common;

    /**
    *
    * @author tanyaowu
    * 2017年3月30日 下午7:05:54
    */
    public interface Const {
        /**
        * 服务器地址
        */
        public static final String SERVER = "127.0.0.1";
        
        /**
        * 监听端口
        */
        public static final int PORT = 6789;

        /**
        * 心跳超时时间
        */
        public static final int TIMEOUT = 5000;
    }

服务端代码

实现org.tio.server.intf.ServerAioHandler

package org.tio.examples.helloworld.server;

import java.nio.ByteBuffer;

import org.tio.core.Aio;
import org.tio.core.ChannelContext;
import org.tio.core.GroupContext;
import org.tio.core.exception.AioDecodeException;
import org.tio.core.intf.Packet;
import org.tio.examples.helloworld.common.HelloPacket;
import org.tio.server.intf.ServerAioHandler;

/**
* @author tanyaowu
*/
public class HelloServerAioHandler implements ServerAioHandler {

/**
    * 解码:把接收到的ByteBuffer,解码成应用可以识别的业务消息包
    * 总的消息结构:消息头 + 消息体
    * 消息头结构:    4个字节,存储消息体的长度
    * 消息体结构:   对象的json串的byte[]
    */
@Override
public HelloPacket decode(ByteBuffer buffer, int limit, int position, int readableLength, ChannelContext channelContext) throws AioDecodeException {
    //提醒:buffer的开始位置并不一定是0,应用需要从buffer.position()开始读取数据
    //收到的数据组不了业务包,则返回null以告诉框架数据不够
    if (readableLength < HelloPacket.HEADER_LENGHT) {
        return null;
    }

    //读取消息体的长度
    int bodyLength = buffer.getInt();

    //数据不正确,则抛出AioDecodeException异常
    if (bodyLength < 0) {
        throw new AioDecodeException("bodyLength [" + bodyLength + "] is not right, remote:" + channelContext.getClientNode());
    }

    //计算本次需要的数据长度
    int neededLength = HelloPacket.HEADER_LENGHT + bodyLength;
    //收到的数据是否足够组包
    int isDataEnough = readableLength - neededLength;
    // 不够消息体长度(剩下的buffe组不了消息体)
    if (isDataEnough < 0) {
        return null;
    } else //组包成功
    {
        HelloPacket imPacket = new HelloPacket();
        if (bodyLength > 0) {
            byte[] dst = new byte[bodyLength];
            buffer.get(dst);
            imPacket.setBody(dst);
        }
        return imPacket;
    }
}

/**
    * 编码:把业务消息包编码为可以发送的ByteBuffer
    * 总的消息结构:消息头 + 消息体
    * 消息头结构:    4个字节,存储消息体的长度
    * 消息体结构:   对象的json串的byte[]
    */
@Override
public ByteBuffer encode(Packet packet, GroupContext groupContext, ChannelContext channelContext) {
    HelloPacket helloPacket = (HelloPacket) packet;
    byte[] body = helloPacket.getBody();
    int bodyLen = 0;
    if (body != null) {
        bodyLen = body.length;
    }

    //bytebuffer的总长度是 = 消息头的长度 + 消息体的长度
    int allLen = HelloPacket.HEADER_LENGHT + bodyLen;
    //创建一个新的bytebuffer
    ByteBuffer buffer = ByteBuffer.allocate(allLen);
    //设置字节序
    buffer.order(groupContext.getByteOrder());

    //写入消息头----消息头的内容就是消息体的长度
    buffer.putInt(bodyLen);

    //写入消息体
    if (body != null) {
        buffer.put(body);
    }
    return buffer;
}


/**
    * 处理消息
    */
@Override
public void handler(Packet packet, ChannelContext channelContext) throws Exception {
    HelloPacket helloPacket = (HelloPacket) packet;
    byte[] body = helloPacket.getBody();
    if (body != null) {
        String str = new String(body, HelloPacket.CHARSET);
        System.out.println("收到消息:" + str);

        HelloPacket resppacket = new HelloPacket();
        resppacket.setBody(("收到了你的消息,你的消息是:" + str).getBytes(HelloPacket.CHARSET));
        Aio.send(channelContext, resppacket);
    }
    return;
}
}
                            
    

启动类

package org.tio.examples.helloworld.server;

import java.io.IOException;

import org.tio.examples.helloworld.common.Const;
import org.tio.server.AioServer;
import org.tio.server.ServerGroupContext;
import org.tio.server.intf.ServerAioHandler;
import org.tio.server.intf.ServerAioListener;

/**
    *
    * @author tanyaowu
    * 2017年4月4日 下午12:22:58
    */
public class HelloServerStarter {
    //handler, 包括编码、解码、消息处理
    public static ServerAioHandler aioHandler = new HelloServerAioHandler();

    //事件监听器,可以为null,但建议自己实现该接口,可以参考showcase了解些接口
    public static ServerAioListener aioListener = null;

    //一组连接共用的上下文对象
    public static ServerGroupContext serverGroupContext = new ServerGroupContext("hello-tio-server", aioHandler, aioListener);

    //aioServer对象
    public static AioServer aioServer = new AioServer(serverGroupContext);

    //有时候需要绑定ip,不需要则null
    public static String serverIp = null;

    //监听的端口
    public static int serverPort = Const.PORT;

    /**
        * 启动程序入口
        */
    public static void main(String[] args) throws IOException {
        serverGroupContext.setHeartbeatTimeout(org.tio.examples.helloworld.common.Const.TIMEOUT);

        aioServer.start(serverIp, serverPort);
    }
}

客户端代码

实现org.tio.client.intf.ClientAioHandler

package org.tio.examples.helloworld.client;

import java.nio.ByteBuffer;

import org.tio.client.intf.ClientAioHandler;
import org.tio.core.ChannelContext;
import org.tio.core.GroupContext;
import org.tio.core.exception.AioDecodeException;
import org.tio.core.intf.Packet;
import org.tio.examples.helloworld.common.HelloPacket;

/**
    * 
    * @author tanyaowu
    */
public class HelloClientAioHandler implements ClientAioHandler {
    private static HelloPacket heartbeatPacket = new HelloPacket();


    /**
        * 解码:把接收到的ByteBuffer,解码成应用可以识别的业务消息包
        * 总的消息结构:消息头 + 消息体
        * 消息头结构:    4个字节,存储消息体的长度
        * 消息体结构:   对象的json串的byte[]
        */
    @Override
    public HelloPacket decode(ByteBuffer buffer, int limit, int position, int readableLength, ChannelContext channelContext) throws AioDecodeException {
        //收到的数据组不了业务包,则返回null以告诉框架数据不够
        if (readableLength < HelloPacket.HEADER_LENGHT) {
            return null;
        }

        //读取消息体的长度
        int bodyLength = buffer.getInt();

        //数据不正确,则抛出AioDecodeException异常
        if (bodyLength < 0) {
            throw new AioDecodeException("bodyLength [" + bodyLength + "] is not right, remote:" + channelContext.getClientNode());
        }

        //计算本次需要的数据长度
        int neededLength = HelloPacket.HEADER_LENGHT + bodyLength;
        //收到的数据是否足够组包
        int isDataEnough = readableLength - neededLength;
        // 不够消息体长度(剩下的buffe组不了消息体)
        if (isDataEnough < 0) {
            return null;
        } else //组包成功
        {
            HelloPacket imPacket = new HelloPacket();
            if (bodyLength > 0) {
                byte[] dst = new byte[bodyLength];
                buffer.get(dst);
                imPacket.setBody(dst);
            }
            return imPacket;
        }
    }

    /**
        * 编码:把业务消息包编码为可以发送的ByteBuffer
        * 总的消息结构:消息头 + 消息体
        * 消息头结构:    4个字节,存储消息体的长度
        * 消息体结构:   对象的json串的byte[]
        */
    @Override
    public ByteBuffer encode(Packet packet, GroupContext groupContext, ChannelContext channelContext) {
        HelloPacket helloPacket = (HelloPacket) packet;
        byte[] body = helloPacket.getBody();
        int bodyLen = 0;
        if (body != null) {
            bodyLen = body.length;
        }

        //bytebuffer的总长度是 = 消息头的长度 + 消息体的长度
        int allLen = HelloPacket.HEADER_LENGHT + bodyLen;
        //创建一个新的bytebuffer
        ByteBuffer buffer = ByteBuffer.allocate(allLen);
        //设置字节序
        buffer.order(groupContext.getByteOrder());

        //写入消息头----消息头的内容就是消息体的长度
        buffer.putInt(bodyLen);

        //写入消息体
        if (body != null) {
            buffer.put(body);
        }
        return buffer;
    }
    
    /**
        * 处理消息
        */
    @Override
    public void handler(Packet packet, ChannelContext channelContext) throws Exception {
        HelloPacket helloPacket = (HelloPacket) packet;
        byte[] body = helloPacket.getBody();
        if (body != null) {
            String str = new String(body, HelloPacket.CHARSET);
            System.out.println("收到消息:" + str);
        }

        return;
    }

    /**
        * 此方法如果返回null,框架层面则不会发心跳;如果返回非null,框架层面会定时发本方法返回的消息包
        */
    @Override
    public HelloPacket heartbeatPacket() {
        return heartbeatPacket;
    }
}
                            

启动类

package org.tio.examples.helloworld.client;

import org.tio.client.AioClient;
import org.tio.client.ClientChannelContext;
import org.tio.client.ClientGroupContext;
import org.tio.client.ReconnConf;
import org.tio.client.intf.ClientAioHandler;
import org.tio.client.intf.ClientAioListener;
import org.tio.core.Aio;
import org.tio.core.Node;
import org.tio.examples.helloworld.common.Const;
import org.tio.examples.helloworld.common.HelloPacket;

/**
    *
    * @author tanyaowu
    *
    */
public class HelloClientStarter {
    //服务器节点
    public static Node serverNode = new Node(Const.SERVER, Const.PORT);

    //handler, 包括编码、解码、消息处理
    public static ClientAioHandler aioClientHandler = new HelloClientAioHandler();

    //事件监听器,可以为null,但建议自己实现该接口,可以参考showcase了解些接口
    public static ClientAioListener aioListener = null;

    //断链后自动连接的,不想自动连接请设为null
    private static ReconnConf reconnConf = new ReconnConf(5000L);

    //一组连接共用的上下文对象
    public static ClientGroupContext clientGroupContext = new ClientGroupContext(aioClientHandler, aioListener, reconnConf);

    public static AioClient aioClient = null;
    public static ClientChannelContext clientChannelContext = null;

    /**
        * 启动程序入口
        */
    public static void main(String[] args) throws Exception {
        clientGroupContext.setHeartbeatTimeout(Const.TIMEOUT);
        aioClient = new AioClient(clientGroupContext);
        clientChannelContext = aioClient.connect(serverNode);
        //连上后,发条消息玩玩
        send();
    }

    private static void send() throws Exception {
        HelloPacket packet = new HelloPacket();
        packet.setBody("hello world".getBytes(HelloPacket.CHARSET));
        Aio.send(clientChannelContext, packet);
    }
}

运行hello tio

运行服务器:org.tio.examples.helloworld.server.HelloServerStarter.main(String[]) 控制台应该会打印如下日志: )

2018-03-18 19:36:25,608 WARN  org.tio.server.AioServer[109]: hello-tio-server started, listen on 0.0.0.0:6789

运行客户端:org.tio.examples.helloworld.client.HelloClientStarter.main(String[]) 控制台应该会打印如下日志:

2018-03-18 19:36:27 INFO  o.t.c.ConnectionCompletionHandler[100]: connected to 127.0.0.1:6789
收到消息:收到了你的消息,你的消息是:hello world
2018-03-18 19:36:28 INFO  org.tio.client.AioClient[370]: [1]: curr:1, closed:0, received:(1p)(55b), handled:1, sent:(1p)(15b)
2018-03-18 19:36:30 INFO  org.tio.client.AioClient[370]: [1]: curr:1, closed:0, received:(1p)(55b), handled:1, sent:(1p)(15b)
2018-03-18 19:36:31 INFO  org.tio.client.AioClient[364]: 0:0:0:0:0:0:0:0:9084发送心跳包
2018-03-18 19:36:31 INFO  org.tio.client.AioClient[370]: [1]: curr:1, closed:0, received:(1p)(55b), handled:1, sent:(1p)(15b)
2018-03-18 19:36:32 INFO  org.tio.client.AioClient[370]: [1]: curr:1, closed:0, received:(1p)(55b), handled:1, sent:(2p)(19b)
2018-03-18 19:36:33 INFO  org.tio.client.AioClient[364]: 0:0:0:0:0:0:0:0:9084发送心跳包
同时,服务器端的控制台会出现类似下面的日志
2018-03-18 19:36:25,608 WARN  org.tio.server.AioServer[109]: hello-tio-server started, listen on 0.0.0.0:6789
收到消息:hello world
2018-03-18 19:38:25,654 INFO  o.t.server.ServerGroupContext[219]: 
hello-tio-server
    ├ 当前时间:1521373105587
    ├ 连接统计
    │ 	 ├ 共接受过连接数  :1
    │ 	 ├ 当前连接数            :1
    │ 	 ├ 异IP连接数           :1
    │ 	 └ 关闭过的连接数  :0
    ├ 消息统计
    │ 	 ├ 已处理消息  :44
    │ 	 ├ 已接收消息(packet/byte):44/187
    │ 	 ├ 已发送消息(packet/byte):1/55b
    │ 	 ├ 平均每次TCP包接收的字节数  :4.25
    │ 	 └ 平均每次TCP包接收的业务包  :1.0
    └ IP统计时段 
        └ []
    ├ 节点统计
    │ 	 ├ clientNodes :1
    │ 	 ├ 所有连接               :1
    │ 	 ├ 活动连接               :1
    │ 	 ├ 关闭次数               :0
    │ 	 ├ 绑定user数         :0
    │ 	 ├ 绑定token数       :0
    │ 	 └ 等待同步消息响应 :0
    ├ 群组
    │ 	 ├ channelmap  :0
    │ 	 └ groupmap:0
    └ 拉黑IP 
        └ []
2018-03-18 19:38:25,655 INFO  o.t.server.ServerGroupContext[273]: hello-tio-server, 检查心跳, 共1个连接, 

《参考:https://t-io.org/blog/index.html?p=%2Fblog%2Ftio%2Fhello%2Fhello.html》

猜你喜欢

转载自blog.csdn.net/qq_32447301/article/details/82965034