-
上一讲我们了解了请求分发的一点小思路,这一讲进行实战训练,项目搭建过程忽略,如果需要源码,去公众号回复"tio实战源码",后续会分享一些个人在项目方面的思考,在不同的业务下,项目的构建思路,闲言少叙
-
这个项目是因为有个朋友需要搞个socket项目,然后让我帮忙搞一下,本着偷懒的原则然后试着引入了t-io,相关资料自行百度(https://www.tiocloud.com/2/product/tio.html, https://www.oschina.net/p/t-io),t-io作者说如果网络编程很痛苦,那一定是没用 t-io,经过使用,确实比netty要感觉简单很多
-
首先看下项目结构
tio-demo-common 模块放一些公共的工具类以及bean
tio-demo-socket socket放socket相关的核心类
tio-demo-socket-biz biz放业务类
- 结构比较简单
-
- 消息包:
public class TioDemoPacket extends Packet {
public static final int HEADER_LENGTH = 4;
private byte[] body;
public byte[] getBody() {
return body;
}
public void setBody(byte[] body) {
this.body = body;
}
}
- 接收参数解码:
@Override
public TioDemoPacket decode(ByteBuffer buffer, int limit, int position, int readableLength, ChannelContext channelContext) throws AioDecodeException {
if (buffer.remaining() < TioDemoPacket.HEADER_LENGTH) {
return null;
} else {
TioDemoPacket imPacket = new TioDemoPacket();
if (buffer.remaining() < TioDemoPacket.HEADER_LENGTH) {
return null;
} else {
byte[] bytes = new byte[limit];
buffer.get(bytes);
imPacket.setBody(bytes);
return imPacket;
}
}
}
- 接收参数之后的解析
TioDemoPacket packingPacket = (TioDemoPacket) packet;
byte[] body = packingPacket.getBody();
if (body != null) {
String reqJson = new String(body);
if (logPrint) {
log.info("接收到服务器的请求信息:{},{},收到消息:{}", cc.getId(), cc.getToken(), reqJson);
}
if (!JSONUtil.isTypeJSON(reqJson)) {
log.error("参数不是json");
return;
}
JSONObject jb = JSONUtil.parseObj(reqJson);
String service = jb.getStr("service");
if (StrUtil.isEmpty(service)) {
log.error("接口参数缺失");
return;
}
if (!InterfaceNameEnum.interfaceNameIsExist(service)) {
log.error("接口未接入");
return;
}
RequestMessageHandler requestMessageHandler = requestMessageHandlerContainer.getMessageHandler(service);
// 获得 MessageHandler 处理器 的消息类
Class extends RequestMessage> messageClass = RequestMessageHandlerContainer.getMessageClass(requestMessageHandler);
// 解析消息
RequestMessage requestMessage = JacksonUtils.toBean(reqJson, messageClass);
//线程池执行
tioDemoTaskExecutor.submit(() -> {
//先判断是否是认证接口
if (InterfaceNameEnum.CHECK_KEY.getName().equals(service)) {
requestMessageHandler.execute(cc, requestMessage);
return;
}
String token = cc.getToken();
if (StrUtil.isEmpty(token)) {
log.error("调用的接口未经认证,不能调用:{}", service);
Tio.close(cc, token);
return;
}
requestMessageHandler.execute(cc, requestMessage);
});
return;
}
- 响应参数编码:
@Override
public ByteBuffer encode(Packet packet, TioConfig tioConfig, ChannelContext channelContext) {
TioDemoPacket serverPacket = (TioDemoPacket) packet;
byte[] body = serverPacket.getBody();
int bodyLen = 0;
if (body != null) {
bodyLen = body.length;
}
//bytebuffer的总长度是 = 消息头的长度 + 消息体的长度
int allLen = bodyLen;
//创建一个新的bytebuffer
ByteBuffer buffer = ByteBuffer.allocate(allLen);
//设置字节序
buffer.order(tioConfig.getByteOrder());
//写入消息头----消息头的内容就是消息体的长度
//写入消息体
if (body != null) {
buffer.put(body);
}
return buffer;
}
- listener 在监听到关闭以后清理token
@Override
public void onBeforeClose(ChannelContext cc, Throwable throwable, String remark, boolean isRemove) throws Exception {
log.info("server onBeforeClose");
log.info("关闭后清除认证信息");
Tio.unbindToken(cc);
}
@Override
public boolean onHeartbeatTimeout(ChannelContext cc,
Long interval,
int heartbeatTimeoutCount) {
Tio.unbindToken(cc);
return false;
}
- 进行认证:
package cn.tio.demo.biz.auth; import cn.tio.demo.socket.RequestMessageHandler; import cn.tio.demo.socket.SocketUtil; import com.tio.common.enums.InterfaceNameEnum; import com.tio.common.enums.ResultCodeEnum; import com.tio.common.socket.request.AuthRequestMessage; import com.tio.common.socket.response.AuthResponseMessage; import com.tio.common.util.JacksonUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.tio.core.ChannelContext; import org.tio.core.Tio; import javax.validation.ConstraintViolation; import java.util.Objects; import java.util.Set; @Slf4j @Component public class AuthRequestMessageHandler implements RequestMessageHandler{ @Value("${parking.parkId}") private String parkId; @Value("${parking.parkKey}") private String parkKey; @Value(("${parking.logPrint:false}")) private Boolean logPrint; @Override public String getService() { return InterfaceNameEnum.CHECK_KEY.getName(); } @Override public void execute(ChannelContext cc, AuthRequestMessage message) { if (logPrint) { log.info("客户端认证信息:{}", message); } AuthResponseMessage authResponseMessage = new AuthResponseMessage(); authResponseMessage.setService(InterfaceNameEnum.CHECK_KEY.getName()); Set > constraintViolationSet = validParam(message); if (Objects.isNull(constraintViolationSet) || !constraintViolationSet.isEmpty()) { authResponseMessage.setResultCode(ResultCodeEnum.FAIL_2.getCode()); authResponseMessage.setMessage("参数不能为空"); SocketUtil.sendMsg(cc, JacksonUtils.toJson(authResponseMessage)); return; } String reParkId = message.getParkId(); String reParkKey = message.getParkKey(); if (!reParkId.equals(parkId) || !reParkKey.equals(parkKey)) { authResponseMessage.setResultCode(ResultCodeEnum.FAIL_1.getCode()); authResponseMessage.setMessage("认证失败,无此车场"); SocketUtil.sendMsg(cc, JacksonUtils.toJson(authResponseMessage)); return; } //绑定用户 Tio.bindToken(cc, parkId); authResponseMessage.setResultCode(ResultCodeEnum.SUCCESS.getCode()); authResponseMessage.setMessage("认证成功"); SocketUtil.sendMsg(cc, JacksonUtils.toJson(authResponseMessage)); } }
- 心跳
package cn.tio.demo.biz.heartbeat; import cn.tio.demo.socket.RequestMessageHandler; import cn.tio.demo.socket.SocketUtil; import com.tio.common.enums.InterfaceNameEnum; import com.tio.common.enums.ResultCodeEnum; import com.tio.common.socket.request.HeartBeatRequestMessage; import com.tio.common.socket.response.HeartbeatResponseMessage; import com.tio.common.util.JacksonUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.tio.core.ChannelContext; import javax.validation.ConstraintViolation; import java.util.Objects; import java.util.Set; @Slf4j @Component public class HeartBeatRequestMessageHandler implements RequestMessageHandler{ @Value(("${parking.logPrint:false}")) private Boolean logPrint; @Override public String getService() { return InterfaceNameEnum.HEART_BEAT.getName(); } @Override public void execute(ChannelContext cc, HeartBeatRequestMessage message) { if (logPrint) { log.info("接收到的客户端心跳结果:{}", message); } HeartbeatResponseMessage responseMessage = new HeartbeatResponseMessage(); responseMessage.setService(InterfaceNameEnum.HEART_BEAT.getName()); Set > constraintViolationSet = validParam(message); if (Objects.isNull(constraintViolationSet) || !constraintViolationSet.isEmpty()) { responseMessage.setResultCode(ResultCodeEnum.FAIL_2.getCode()); responseMessage.setMessage("参数不能为空"); SocketUtil.sendMsg(cc, JacksonUtils.toJson(responseMessage)); return; } SocketUtil.connect = cc; responseMessage.setResultCode(ResultCodeEnum.SUCCESS.getCode()); responseMessage.setMessage("在线"); SocketUtil.sendMsg(cc, JacksonUtils.toJson(responseMessage)); } }
- 一个简单的骨架就搭建好了,试着跑一下:
- 关键代码已经贴出来了,还是不懂得可以留言,看到就会回复
- 这是我的微信公众号二维码,你的关注是我持续更新的动力,谢谢



