依赖:
<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);
}
}