(转)开源项目t-io
石墨文档:https://shimo.im/docs/tHwJJcvKl2AIiCZD/
(二期)18、开源t-io项目解读
t-io:
websocket
- 同步
- 异步
- 阻塞
- 非阻塞
- 一个连接一个线程
- 一个请求一个线程
**
Java对BIO、NIO、AIO的支持:
BIO、NIO、AIO适用场景分析:
- ChannelContext(通道上下文)
- GroupContext(服务配置与维护)
- AioHandler(消息处理接口)
- AioListener(通道监听者)
- Packet(应用层数据包)
- ObjWithLock(自带读写锁的对象)
t-io是基于tcp层协议的一个网络框架,所以在应用层与tcp传输层之间设计到一个数据的编码与解码问题,t-io让我们能自定义数据协议,所以需要我们自己手动去编码解码过程。
https://gitee.com/tywo45/tio-showcase
- 导入核心包
<dependency>
<groupId>org.t-io</groupId>
<artifactId>tio-core</artifactId>
</dependency>
- HelloServerStarter
- HelloServerAioHandler
- HelloPacket
- Const
- HelloClientStarter
- HelloClientAioHandler
- idea请安装PlantUML intergration插件
(初始化服务器)
(客户端与服务端通讯流程)
https://gitee.com/tywo45/tio-showcase
LoginReqBody loginReqBody = new LoginReqBody();
loginReqBody.setLoginname(loginname);
loginReqBody.setPassword(password);
ShowcasePacket reqPacket = new ShowcasePacket();
#这里指定消息类型
reqPacket.setType(Type.LOGIN_REQ);
reqPacket.setBody(Json.toJson(loginReqBody).getBytes(ShowcasePacket.CHARSET));
Tio.send(clientChannelContext, reqPacket);
- LoginReqBody
- ShowcasePacket
- clientChannelContext
ShowcasePacket showcasePacket = (ShowcasePacket) packet;
#获取消息类型
Byte type = showcasePacket.getType();
#根据消息类型找到对应的消息处理类
AbsShowcaseBsHandler<?> showcaseBsHandler = handlerMap.get(type);
if (showcaseBsHandler == null) {
log.error("{}, 找不到处理类,type:{}", channelContext, type);
return;
}
#执行消息处理。消息处理类必须继承AbsShowcaseBsHandler
showcaseBsHandler.handler(showcasePacket, channelContext);
- handlerMap
- AbsShowcaseBsHandler
private static Map<Byte, AbsShowcaseBsHandler<?>> handlerMap = new HashMap<>();
static {
#把消息类型与消息处理类映射起来
handlerMap.put(Type.GROUP_MSG_REQ, new GroupMsgReqHandler());
handlerMap.put(Type.HEART_BEAT_REQ, new HeartbeatReqHandler());
handlerMap.put(Type.JOIN_GROUP_REQ, new JoinGroupReqHandler());
handlerMap.put(Type.LOGIN_REQ, new LoginReqHandler());
handlerMap.put(Type.P2P_REQ, new P2PReqHandler());
}
log.info("收到点对点请求消息:{}", Json.toJson(bsBody));
ShowcaseSessionContext showcaseSessionContext = (ShowcaseSessionContext) channelContext.getAttribute();
P2PRespBody p2pRespBody = new P2PRespBody();
p2pRespBody.setFromUserid(showcaseSessionContext.getUserid());
p2pRespBody.setText(bsBody.getText());
ShowcasePacket respPacket = new ShowcasePacket();
respPacket.setType(Type.P2P_RESP);
respPacket.setBody(Json.toJson(p2pRespBody).getBytes(ShowcasePacket.CHARSET));
Tio.sendToUser(channelContext.groupContext, bsBody.getToUserid(), respPacket);
项目集成:
<dependency>
<groupId>org.t-io</groupId>
<artifactId>tio-websocket-server</artifactId>
<version>0.0.5-tio-websocket</version>
</dependency>
代码结构
事件定义
new Object() {
@Subscribe
public void lister(Integer integer) {
System.out.printf("%d from int%n", integer);
}
}
事件发布
//定义事件
final EventBus eventBus = new EventBus();
//注册事件
eventBus.register(new Object() {
//使用@Subscribe说明订阅事件处理方法
@Subscribe
public void lister(Integer integer) {
System.out.printf("%s from int%n", integer);
}
@Subscribe
public void lister(Number integer) {
System.out.printf("%s from Number%n", integer);
}
@Subscribe
public void lister(Long integer) {
System.out.printf("%s from long%n", integer);
}
});
//发布事件
eventBus.post(1);
eventBus.post(1L);
项目的而运用
主要处理事件
关键类:
调用:
逻辑:
/**
* 添加好友成功之后向对方推送消息
* */
public static void pushAddFriendMessage(long applyid){
if(applyid==0){
return;
}
Apply apply = applyService.getApply(applyid);
ChannelContext channelContext = getChannelContext(""+apply.getUid());
//先判断是否在线,再去查询数据库,减少查询次数
if (channelContext != null && !channelContext.isClosed()) {
LayimToClientAddFriendMsgBody body = new LayimToClientAddFriendMsgBody();
User user = getUserService().getUser(apply.getToid());
if (user==null){return;}
//对方分组ID
body.setGroupid(apply.getGroup());
//当前用户的基本信息,用于调用layim.addList
body.setAvatar(user.getAvatar());
body.setId(user.getId());
body.setSign(user.getSign());
body.setType("friend");
body.setUsername(user.getUserName());
push(channelContext, body);
}
}
/**
* 服务端主动推送消息
* */
private static void push(ChannelContext channelContext,Object msg) {
try {
WsResponse response = BodyConvert.getInstance().convertToTextResponse(msg);
Aio.send(channelContext, response);
}catch (IOException ex){
}
}
- 登录功能
- 单聊功能
- 群聊功能
- 其他自定义消息提醒功能
- 等等。。。。
layim.config({
//初始化接口
init: {
url: '/layim/base'
}
//查看群员接口
,members: {
url: '/layim/members'
}
//上传图片接口
,uploadImage: {url: '/upload/file'}
//上传文件接口
,uploadFile: {url: '/upload/file'}
,isAudio: true //开启聊天工具栏音频
,isVideo: true //开启聊天工具栏视频
,initSkin: '5.jpg' //1-5 设置初始背景
,notice: true //是否开启桌面消息提醒,默认false
,msgbox: '/layim/msgbox'
,find: layui.cache.dir + 'css/modules/layim/html/find.html' //发现页面地址,若不开启,剔除该项即可
,chatLog: layui.cache.dir + 'css/modules/layim/html/chatLog.html' //聊天记录页面地址,若不开启,剔除该项即可
});
socket.config({
log:true,
token:'/layim/token',
server:'ws://127.0.0.1:8888'
});
org.springframework.boot.autoconfigure.EnableAutoConfiguration= com.fyp.layim.im.server.LayimServerAutoConfig
//初始化t-io的serverGroupContext
//还有消息处理器与消息类型的映射关系
public LayimServerStarter(LayimServerConfig wsServerConfig, IWsMsgHandler wsMsgHandler, TioUuid tioUuid, SynThreadPoolExecutor tioExecutor, ThreadPoolExecutor groupExecutor) throws Exception {
this.layimServerConfig = wsServerConfig;
this.wsMsgHandler = wsMsgHandler;
layimServerAioHandler = new LayimServerAioHandler(wsServerConfig, wsMsgHandler);
layimServerAioListener = new LayimServerAioListener();
serverGroupContext = new ServerGroupContext(layimServerAioHandler, layimServerAioListener, tioExecutor, groupExecutor);
//心跳时间,暂时设置为0
serverGroupContext.setHeartbeatTimeout(wsServerConfig.getHeartBeatTimeout());
serverGroupContext.setName("Tio Websocket Server for LayIM");
aioServer = new AioServer(serverGroupContext);
serverGroupContext.setTioUuid(tioUuid);
//initSsl(serverGroupContext);
//初始化消息处理器
LayimMsgProcessorManager.init();
}
SetWithLock<ChannelContext> checkChannelContexts =
Aio.getChannelContextsByUserid(channelContext.getGroupContext(),body.getId());
private HttpResponse handleHandshakeUserInfo(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) throws Exception {
UserService userService = getUserService();
//增加token验证方法
String path = httpRequest.getRequestLine().getPath();
String token = URLDecoder.decode(path.substring(1),"utf-8");
String userId = TokenVerify.IsValid(token);
if (userId == null) {
//没有token 未授权
httpResponse.setStatus(HttpResponseStatus.C401);
} else {
long uid = Long.parseLong(userId);
//解析token
LayimContextUserInfo userInfo = userService.getContextUserInfo(uid);
if (userInfo == null) {
//没有找到用户
httpResponse.setStatus(HttpResponseStatus.C404);
} else {
channelContext.setAttribute(userId, userInfo.getContextUser());
//绑定用户ID
Aio.bindUser(channelContext, userId);
//绑定用户群组
List<String> groupIds = userInfo.getGroupIds();
//绑定用户群信息
if (groupIds != null) {
groupIds.forEach(groupId -> Aio.bindGroup(channelContext, groupId));
}
//通知所有好友本人上线了
notify(channelContext,true);
}
}
return httpResponse;
}