SpringBoot 快速集成TCP服务端和客户端

4 篇文章 0 订阅

依赖:

        <dependency>
            <groupId>org.t-io</groupId>
            <artifactId>tio-core</artifactId>
            <version>3.5.0.v20190822-RELEASE</version>
        </dependency>

TCP协议:消息头占4个字节,代表了消息体的长度。

总共四个类:

消息载体:HelloPacket

客户端消息处理器:HelloClientAioHandler

服务端消息处理器:HelloServerAioHandler

接口控制器:DemoController

import lombok.Data;
import lombok.EqualsAndHashCode;
import org.tio.core.intf.Packet;

/**
 * @author 消息载体
 */
@EqualsAndHashCode(callSuper = true)
@Data
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;
}
import java.nio.ByteBuffer;

import org.tio.client.intf.ClientAioHandler;
import org.tio.core.ChannelContext;
import org.tio.core.TioConfig;
import org.tio.core.exception.AioDecodeException;
import org.tio.core.intf.Packet;

/**
 * @author 客户端消息处理器
 */
public class HelloClientAioHandler implements ClientAioHandler {
    private static HelloPacket heartbeatPacket = new HelloPacket();

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

    /**
     * 解码:【把接收到的ByteBuffer,解码成应用可以识别的业务消息包】
     * 总的消息结构:消息头 + 消息体
     * 消息头结构:    4个字节,存储消息体的长度
     * 消息体结构:   对象的json串的byte[]
     */
    @Override
    public HelloPacket decode(ByteBuffer buffer, int limit, int position, int readableLength, ChannelContext channelContext) throws AioDecodeException {
        /**
         * 可读长度至少要比消息长度体要长
         */
        if (readableLength < HelloPacket.HEADER_LENGHT) {
            return null;
        }
        /**
         * 读取消息长度体的值,即消息体的长度【buffer.getInt():读取四个字节并根据字节顺序将他们组合成一个int值,buffer.getLong()则是读取八个字节,buffer.getShort()则是读取两个字节】
         */
        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, TioConfig tioConfig, ChannelContext channelContext) {
        HelloPacket helloPacket = (HelloPacket) packet;
        byte[] body = helloPacket.getBody();
        int bodyLen = 0;
        if (body != null) {
            bodyLen = body.length;
        }
        /**
         * 总长度是 = 消息头的长度 + 消息体的长度
         */
        int allLen = HelloPacket.HEADER_LENGHT + bodyLen;
        //创建一个新的bytebuffer
        ByteBuffer buffer = ByteBuffer.allocate(allLen);
        //设置字节序
        buffer.order(tioConfig.getByteOrder());
        /**
         * 写入消息头----消息头的内容就是消息体的长度【putInt:写入长度值,且占四个字节;putLong:写入长度值,且占八个字节...】
         */
        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;
    }
}
import java.nio.ByteBuffer;

import org.tio.core.ChannelContext;
import org.tio.core.TioConfig;
import org.tio.core.exception.AioDecodeException;
import org.tio.core.intf.Packet;
import org.tio.server.intf.ServerAioHandler;

/**
 * @author 服务端消息处理器
 */
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;
        }
        //读取消息体的长度【buffer.getInt():读取四个字节并根据字节顺序将他们组合成一个int值,buffer.getLong()则是读取八个字节,buffer.getShort()则是读取两个字节】
        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, TioConfig tioConfig, 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(tioConfig.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;
    }
}
import com.example.demo.helloworld.*;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.tio.client.ClientChannelContext;
import org.tio.client.ClientTioConfig;
import org.tio.client.ReconnConf;
import org.tio.client.TioClient;
import org.tio.core.Node;
import org.tio.core.Tio;
import org.tio.server.ServerTioConfig;
import org.tio.server.TioServer;

import java.io.IOException;
import java.io.UnsupportedEncodingException;

@RestController
@RequestMapping("/demo")
@Validated
public class DemoController {

    private ClientChannelContext clientChannelContext;
    private ServerTioConfig serverTioConfig;

    /**
     * 客户端向服务端发送消息
     *
     * @param msg
     * @return
     * @throws UnsupportedEncodingException
     */
    @PostMapping("sendToServer")
    public String sendToServer(String msg) throws UnsupportedEncodingException {
        byte[] bytes = msg.getBytes(HelloPacket.CHARSET);

        HelloPacket packet = new HelloPacket();
        packet.setBody(bytes);
        if (clientChannelContext == null) {
            return "TCP客户端未启动";
        }
        Tio.send(clientChannelContext, packet);

        return "success";
    }

    /**
     * 服务端向客户端发送消息
     *
     * @param msg
     * @return
     * @throws UnsupportedEncodingException
     */
    @PostMapping("sendToClient")
    public String sendToClient(String msg) throws UnsupportedEncodingException {
        byte[] bytes = msg.getBytes(HelloPacket.CHARSET);

        HelloPacket packet = new HelloPacket();
        packet.setBody(bytes);
        if (serverTioConfig == null) {
            return "TCP服务端未启动";
        }
        Tio.sendToAll(serverTioConfig, packet);

        return "success";
    }

    /**
     * 发起TCP连接
     *
     * @throws Exception
     */
    @PostMapping("clientStarter")
    public void clientStarter() throws Exception {
        //我这里直接省略了消息监听器,读者可实现ServerAioListener接口,然后在此注入即可
        ClientTioConfig clientTioConfig = new ClientTioConfig(new HelloClientAioHandler(), null, new ReconnConf(5000L));
        clientTioConfig.setHeartbeatTimeout(5000);
        TioClient tioClient = new TioClient(clientTioConfig);
        clientChannelContext = tioClient.connect(new Node("127.0.0.1", 6789));
        //连上后,发条消息玩玩
        HelloPacket packet = new HelloPacket();
        packet.setBody("hello world".getBytes(HelloPacket.CHARSET));
        Tio.send(clientChannelContext, packet);
    }

    /**
     * 启动TCP服务
     *
     * @throws IOException
     */
    @PostMapping("serverStarter")
    public void serverStarter() throws IOException {
        //我这里直接省略了消息监听器,读者可实现ServerAioListener接口,然后在此注入即可
        serverTioConfig = new ServerTioConfig("hello-tio-server", new HelloServerAioHandler(), null);
        //设置心跳周期
        serverTioConfig.setHeartbeatTimeout(5000);
        TioServer tioServer = new TioServer(serverTioConfig);
        //启动服务器
        tioServer.start(null, 6789);
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

文子阳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值