石墨文档:https://shimo.im/docs/tHwJJcvKl2AIiCZD/

(二期)18、开源t-io项目解读

【课程18】BIO、...AIO.xmind0.4MB

【课程18】t-io简介.xmind0.2MB

【课程18】两个官方例子.xmind0.3MB

【课程18】同步异...阻塞.xmind0.3MB

【课程18预习】百万...t-io.xmind0.3MB

t-io是什么

官网相关

官网:https://t-io.org/

宣传:不仅仅是百万级网络通信框架,让天下没有难开发的网络通信

git地址:https://gitee.com/tywo45/t-io

t-io手册:https://t-io.org/doc/index.html

t-io.pdf3MB

t-io与websocket

t-io:是一个网络框架,从这一点来说是有点像 netty 的,但 t-io 为常见和网络相关的业务(如 IM、消息推送、RPC、监控)提供了近乎于现成的解决方案,即丰富的编程 API,极大减少业务层的编程难度。

websocket: WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。

预备知识AIO、NIO、Socket
同步、异步、阻塞、非阻塞

先来个例子理解一下概念,以银行取款为例:

  • 同步 : 自己亲自出马持银行卡到银行取钱(使用同步IO时,Java自己处理IO读写);
  • 异步 : 委托一小弟拿银行卡到银行取钱,然后给你(使用异步IO时,Java将IO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS(银行卡和密码),OS需要支持异步IO操作API);

  • 阻塞 : ATM排队取款,你只能等待(使用阻塞IO时,Java调用会一直阻塞到读写完成才返回);
  • 非阻塞 : 柜台取款,取个号,然后坐在椅子上做其它事,等号广播会通知你办理,没到号你就不能去,你可以不断问大堂经理排到了没有,大堂经理如果说还没到你就不能去(使用非阻塞IO时,如果不能读写Java调用会马上返回,当IO事件分发器会通知可读写时再继续进行读写,不断循环直到读写完成)

IO的方式通常分为几种,同步阻塞的BIO、同步非阻塞的NIO、异步非阻塞的AIO。

同步阻塞BIO
  • 一个连接一个线程

在JDK1.4出来之前,我们建立网络连接的时候采用BIO模式,需要先在服务端启动一个ServerSocket,然后在客户端启动Socket来对服务端进行通信,默认情况下服务端需要对每个请求建立一堆线程等待请求,而客户端发送请求后,先咨询服务端是否有线程相应,如果没有则会一直等待或者遭到拒绝请求,如果有的话,客户端会线程会等待请求结束后才继续执行。

优化:弄一个线程池来管理线程。即伪异步阻塞IO

同步非阻塞NIO
  • 一个请求一个线程

NIO本身是基于事件驱动思想来完成的,其主要想解决的是BIO的大并发问题:每个客户端请求必须使用一个线程单独来处理。问题在于系统本身对线程总数有一定限制,容易瘫痪。

NIO,当socket有流可读或可写入socket时,操作系统会相应的通知引用程序进行处理,应用再将流读取到缓冲区或写入操作系统。  也就是说,这个时候,已经不是一个连接就要对应一个处理线程了,而是有效的请求,对应一个线程,当连接没有数据时,是没有工作线程来处理的。

BIO与NIO一个比较重要的不同,是我们使用BIO的时候往往会引入多线程,每个连接一个单独的线程;而NIO则是使用单线程或者只使用少量的多线程,每个连接共用一个线程。

** NIO的最重要的地方是当一个连接创建后,不需要对应一个线程,这个连接会被注册到多路复用器上面,所以所有的连接只需要一个线程就可以搞定,当这个线程中的多路复用器进行轮询的时候,发现连接上有请求的话,才开启一个线程进行处理,也就是一个请求一个线程模式。

异步非阻塞AIO

对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。  即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。

总结

Java对BIO、NIO、AIO的支持:

  • Java BIO : 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
  • Java NIO : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
  • Java AIO(NIO.2) : 异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理

BIO、NIO、AIO适用场景分析:

  • BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
  • NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
  • AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
t-io框架
使用场景

常用关键类
  • ChannelContext(通道上下文)
  • 每一个 tcp 连接的建立都会产生一个 ChannelContext 对象
  • (1)ServerChannelContext
  • ChannelContext 的子类,当用 tio 作 tcp 服务器时,业务层接触的是这个类的实例。
  • (2)ClientChannelContext
  • ChannelContext 的子类,当用 tio 作 tcp 客户端时,业务层接触的是这个类的实例

  • GroupContext(服务配置与维护)
  • GroupContext 就是用来配置线程池、确定监听端口,维护客户端各种数据等的
  • ClientGroupContext
  • ServerGroupContext

我们在写 TCP Server 时,都会先选好一个端口以监听客户端连接,再创建 N 组线程池来执行相关的任 务,譬如发送消息、解码数据包、处理数据包等任务,还要维护客户端连接的各种数据,为了和业务互动, 还要把这些客户端连接和各种业务数据绑定起来,譬如把某个客户端绑定到一个群组,绑定到一个 userid, 绑定到一个 token 等。GroupContext 就是用来配置线程池、确定监听端口,维护客户端各种数据等的。

GroupContext 是个抽象类,如果你是用 tio 作 tcp 客户端,那么你需要创建 ClientGroupContext,如 果你是用 tio 作 tcp 服务器,那么你需要创建 ServerGroupContext

  • AioHandler(消息处理接口)
  • 处理消息的核心接口,它有两个子接口
  • ClientAioHandler
  • ServerAioHandler
  • AioListener(通道监听者)
  • 处理事件监听的核心接口,它有两个子接口,
  • ClientAioListener
  • ServerAioListener
  • Packet(应用层数据包)
  • TCP 层过来的数据,都会被 tio 要求解码成 Packet 对象,应用都需要继承这个类,从而实现自己的业务 数据包。

用于应用层与传输层的数据传递

传输层在往应用层传递数据时,并不保证每次传递的数据是一个完整的应用层数据包(以 http 协议为 例,就是并不保证应用层收到的数据刚好可以组成一个 http 包),这就是我们经常提到的半包粘包。传输层只负责传递 byte[]数据,应用层需要自己对 byte[]数据进行解码,以 http 协议为例,就是把 byte[] 解码成 http 协议格式的字符串。

  • AioServer(tio 服务端入口类)

  • AioClient(tio 客户端入口类)

  • ObjWithLock(自带读写锁的对象)
  • 是一个自带了一把(读写)锁的普通对象(一般是集合对象),每当要对 这个对象进行同步安全操作(并发下对集合进行遍历或对集合对象进行元素修改删除增加)时,就得用这个 锁。

t-io是基于tcp层协议的一个网络框架,所以在应用层与tcp传输层之间设计到一个数据的编码与解码问题,t-io让我们能自定义数据协议,所以需要我们自己手动去编码解码过程。

入门例子HelloWord
  • git项目地址

https://gitee.com/tywo45/tio-showcase

git里面有个几个例子,首先我们看helloword的例子:

业务逻辑

本例子演示的是一个典型的TCP长连接应用,大体业务简介如下。

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

具体类说明:

server端
  • 导入核心包
<dependency>
   <groupId>org.t-io</groupId>
   <artifactId>tio-core</artifactId>
</dependency>
  • HelloServerStarter

构造ServerGroupContext,main方法启动服务

  • HelloServerAioHandler

实现ServerAioHandler接口,重写decode,encode,handler方法。

common
  • HelloPacket

继承Packet类。自定义数据包的内容

  • Const

常量类,配置ip,端口等信息

client端
  • HelloClientStarter

构造clientGroupContext,连接节点,使用信息发送消息

  • HelloClientAioHandler

实现ClientAioHandler接口,重写decode,encode,handler方法。

流程图
  • idea请安装PlantUML intergration插件

客户端与服务端沟通时序图.puml0.8KB

Server端初始化时序图.puml0.6KB

(初始化服务器)

(客户端与服务端通讯流程)

入门例子showcase
  • git项目地址

https://gitee.com/tywo45/tio-showcase

上面讲的是helloword的例子,比较简单,接下来的是showcase的例子,结合实际场景的一个例子。

业务逻辑

tio的框架初始化使用过程是一样的。不过因为showcase中涉及到的交互越来越多,因此不能像helloword例子中的HelloPacket只有body这么简单了,我们至少要加上一个type参数(消息类型),这样的话服务器获取到数据包之后再更加type来选择消息处理类,从而拓展系统可用性。

(客户端与服务器沟通流程)

客户端与服务端沟通时序图.puml1.4KB

客户端发起登录操作

  • org.tio.examples.showcase.client.ShowcaseClientStarter#processCommand
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:登录请求参数封装类,继承BaseBody
  • ShowcasePacket:数据包,继承Packet,和ByteBuffer相互转换
  • clientChannelContext:连接上下文通道

服务端接受请求操作

  • org.tio.examples.showcase.server.ShowcaseServerAioHandler#handler
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:存有当前所有消息处理类的map。数据包中包含消息类型,会根据消息类型获取对应的消息处理类,而这个消息处理类会调用handler()方法处理数据。
  • AbsShowcaseBsHandler:消息处理抽象类,继承这个类的处理类会对一种消息类型进行处理,并且一般专门处理一种消息封装类(继承BaseBody的封装类)。

消息类型对应消息处理类的初始化

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());
}

如果接收到的消息类型是P2P_REQ,那么处理类就是P2PReqHandler:

  • org.tio.examples.showcase.server.handler.P2PReqHandler#handler
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);
springboot集成t-io

项目运行:https://github.com/fanpan26/SpringBootLayIM.git

由于layim是付费产品,所以在网络上找了一个。(仅供学习哈)

需要放到项目目录下面

static/js/layui/lay/modules/layim.js

layim.js33.3KB

项目结构

项目集成:

因为这里需要和浏览器之间进行通讯,所以需要用到websocket机制。tio有集成websocket的框架,所以直接导入即可。

<dependency>
    <groupId>org.t-io</groupId>
    <artifactId>tio-websocket-server</artifactId>
    <version>0.0.5-tio-websocket</version>
</dependency>

代码结构

Guava - EventBus(事件总线)

项目使用Guava的EventBus替代了Spring的ApplicationEvent事件机制。

Guava的EventBus使用介绍如下:

事件定义

EventBus为我们提供了register方法来订阅事件,不需要实现任何的额外接口或者base类,只需要在订阅方法上标注上@Subscribe和保证只有一个输入参数的方法就可以搞定。

new Object() {

    @Subscribe
    public void lister(Integer integer) {
        System.out.printf("%d from int%n", integer);
    }
}

事件发布

对于事件源,则可以通过post方法发布事件。 正在这里对于Guava对于事件的发布,是依据上例中订阅方法的方法参数类型决定的,换而言之就是post传入的类型和其基类类型可以收到此事件。

//定义事件
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);

项目的而运用

主要处理事件:包含了申请通知,添加好友成功通知

关键类:

  • com.fyp.layim.common.event.bus.EventUtil:封装了事件的监听注册,以及发布动作
  • com.fyp.layim.common.event.bus.body.EventBody:发布的内容封装类,包含消息类型和消息内容字段
  • com.fyp.layim.common.event.bus.handler.AbsEventHandler:事件处理抽象类,具体处理器需要继承这个重写handler()方法
  • com.fyp.layim.common.event.bus.handler.AddFriendEventHandler:添加好友成功通知处理类。
  • com.fyp.layim.common.event.bus.LayimEventType:消息类型常量

调用:

  • com.fyp.layim.web.biz.UserController#handleFriendApply:好友同意好友请求之后发布事件

逻辑:

事件的处理其实是给申请人发起好友同意通知

  • com.fyp.layim.im.common.util.PushUtil#pushAddFriendMessage
/**
 * 添加好友成功之后向对方推送消息
 * */
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);
    }
}

最后是通过Aio.send发送消息。

  • com.fyp.layim.im.common.util.PushUtil#push
/**
 * 服务端主动推送消息
 * */
private static void push(ChannelContext channelContext,Object msg) {
    try {
        WsResponse response = BodyConvert.getInstance().convertToTextResponse(msg);
        Aio.send(channelContext, response);
    }catch (IOException ex){

    }
}
功能结构
  • 登录功能  
  • 单聊功能
  • 群聊功能
  • 其他自定义消息提醒功能
  • 等等。。。。

登录的目的是过滤非法请求,如果有一个非法用户请求websocket服务,直接返回403或者401即可。

单聊,群聊这个就不用解释了

其他自定义消息提醒,比如:时时加好友消息,广播消息,审核消息等。

项目设计-前端

前端的框架用到是layim,layim已经帮我们做好了界面和封装了接口,所以我们的工作只需要按照layim来写好接口提供结果就可以了。

首先看初始化:

  • templates/index.html

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' //聊天记录页面地址,若不开启,剔除该项即可

});

因为需要进行通讯,所以websocket的信息也需要初始化。都是在layim里面一起初始化的。

socket.config({
    log:true,
    token:'/layim/token',
    server:'ws://127.0.0.1:8888'
});
  • templates/layim/msgbox.html
项目设计-后端
基本相关类

控制器:

com.fyp.layim.web.api.GroupController:群组相关

com.fyp.layim.web.biz.AccountController:账户相关,登录注册

com.fyp.layim.web.biz.UploadController:上传文件、图片

com.fyp.layim.web.biz.UserController:用户基础数据

拦截器:

com.fyp.layim.web.filter.TokenVerifyInterceptor:校验token,与userid转换

com.fyp.layim.web.filter.LayimAspect:给所有请求设置当前用户的值,放session当中

全局异常处理:

com.fyp.layim.web.filter.GlobalExceptionHandler:异常处理

网络编程主要代码

com.fyp.layim.im.common.*:定义消息类型,消息处理类的公共包。

com.fyp.layim.im.packet.*:这是数据包,因为协议是websocket,所以返回的不是packet,最后要返回的其实是符合websocket定义的org.tio.websocket.common.WsResponse。而这些数据是放在body中。

com.fyp.layim.im.server.*:t-io的服务配置相关,包括t-io服务的启动,启动之前需要ServerAioHandler、ServerAioListener、ServerGroupContext等参数。

所以总体逻辑是这样的:

步骤一、配置了springboot的模块自动加载机制。

  • META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration= com.fyp.layim.im.server.LayimServerAutoConfig

LayimServerAutoConfig配置将会被自动装载。

  • com.fyp.layim.im.server.LayimServerAutoConfig:自动装载的配置类
  • com.fyp.layim.im.server.config.LayimServerConfig:服务的ip、端口、心跳时间等基本配置
  • com.fyp.layim.im.server.LayimWebsocketStarter:初始化配置,启动t-io服务,其中配置初始化和启动都是委托给com.fyp.layim.im.server.LayimServerStarter完成。
  • com.fyp.layim.im.server.LayimServerStarter:根据配置初始化serverGroupContext、初始化消息处理器
//初始化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();
}

接下来看下与serverGroupContext相关的消息处理咧、事件监听类

  • com.fyp.layim.im.server.handler.LayimServerAioHandler:继承ServerAioHandler,完成消息的解码、编码、消息处理过程
  • com.fyp.layim.im.server.listener.LayimServerAioListener:继承ServerAioListener,完成事件监听

绑定用户信息

  • com.fyp.layim.im.common.processor.ClientCheckOnlineMsgProcessor#process
SetWithLock<ChannelContext> checkChannelContexts =
      Aio.getChannelContextsByUserid(channelContext.getGroupContext(),body.getId());

绑定用户上线信息。

  • com.fyp.layim.im.server.handler.LayimMsgHandler#handleHandshakeUserInfo
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;
}

Aio.java的api:

实现  IWsMsgHandler接口:用于websocket握手,响应,关闭通道等过程的业务处理。

握手逻辑首先是在:com.fyp.layim.im.server.handler.LayimServerAioHandler中。

wsMsgHandler.handshake 方法,这里一般直接返回默认的 httpReponse即可,代表(框架层)握手成功。但是我们可以在接口中自定义一些业务逻辑,比如用户判断之类的逻辑,然后决定是否同意握手流程。

(转)开源项目t-io的更多相关文章

  1. 瓣呀,一个基于豆瓣api仿网易云音乐的开源项目

    整体采用material design 风格,本人是网易云音乐的粉丝,所以界面模仿了网页云音乐,另外,项目中尽量使用了5.0之后的新控件. 项目整体采用mvp+rxjava+retrofit 框架,使 ...

  2. 《云阅》一个仿网易云音乐UI,使用Gank.Io及豆瓣Api开发的开源项目

    CloudReader 一款基于网易云音乐UI,使用GankIo及豆瓣api开发的符合Google Material Desgin阅读类的开源项目.项目采取的是Retrofit + RxJava + ...

  3. 微软CMS项目 Orchard 所用到的开源项目

    研发了Orchard一年左右了,时常遇到瓶颈,总觉得力不从心,其实并不是基础不够,关键还是概念性的东西太多,一会儿这个概念名词,一会那个,关于Orchard的技术文档也的确很少,每次看起来总是焦头烂额 ...

  4. [转] Android优秀开源项目

    Android经典的开源项目其实非常多,但是国内的博客总是拿着N年前的一篇复制来复制去,实在是不利于新手学习.今天爬爬把自己熟悉的一些开源项目整理起来,希望能对Android开发同学们有所帮助.另外, ...

  5. .Net 开源项目资源大全

    伯乐在线已在 GitHub 上发起「DotNet 资源大全中文版」的整理.欢迎扩散.欢迎加入. https://github.com/jobbole/awesome-dotnet-cn (注:下面用 ...

  6. 分享我的开源项目-springmore

    之前有在博客园分享过springmore,不知道是什么原因,被管理员移除首页 在此郑重声明,这是我个人的开源项目,东西不多,也不存在打广告,也没有什么利益可图 完全是出于分享的目的,望博客园管理员予以 ...

  7. [转]Android开源项目第二篇——工具库篇

    本文为那些不错的Android开源项目第二篇--开发工具库篇,主要介绍常用的开发库,包括依赖注入框架.图片缓存.网络相关.数据库ORM建模.Android公共库.Android 高版本向低版本兼容.多 ...

  8. 2015-2016最火的Android开源项目--github开源项目集锦(不看你就out了)

    标签: Android开发开源项目最火Android项目github 2015-2016最火的Android开源项目 本文整理与集结了近期github上使用最广泛最火热与最流行的开源项目,想要充电与提 ...

  9. swift开源项目精选

    Swift 开源项目精选-v1.0 2016-03-07 22:11 542人阅读 评论(0) 收藏 举报  分类: iOS(55)   Swift(4)    目录(?)[+]   转自 http: ...

  10. 开源项目大全 >> ...

    http://www.isenhao.com/xueke/jisuanji/kaiyuan.php   监控系统-Nagios 网络流量监测图形分析工具-Cacti 分布式系统监视-zabbix 系统 ...

随机推荐

  1. github上删除一个项目或者reposity

    1,点击github的头像,选择如下操作. 2.选择要删除的reposity 3.选择settings 4.复制reposity名字,然后下滑鼠标到底部,选择delete this reposity ...

  2. spring boot 知识点

    spring boot 好处 1. 简化配置,spring boot 提供了默认配置 例如 日志 默认logback日志  info级别 2. 简化部署,内嵌容器,tomcat,jetty,直接部署j ...

  3. 更换tomcat运行日志目录

    1.在tomcat安装文件夹的bin目录下,修改catalina.sh,改变catalina.out的输出目录 CATALINA_OUT="$CATALINA_BASE"/logs ...

  4. mysql按天,按周,按月,按季度,按年统计数据

    /*查询2小时前的数据*/select * from tableName WHERE create_time HOUR) SELECT count(id) FROM rd_track_info WHE ...

  5. g++编译

    命令: otool -L xx.lib 查看mac os 动态库依赖的包 ldd xx.so 查看linux动态库依赖的包 c++打包动态库java调用,mac上没问题到linux上就是不行,g++命 ...

  6. Gamma函数深入理解

    Gamma函数 当n为正整数时,n的阶乘定义如下:n! = n * (n - 1) * (n - 2) * … * 2 * 1. 当n不是整数时,n!为多少?我们先给出答案. 容易证明,Γ(x + 1 ...

  7. 前端规范--eslint standard

    https://github.com/standard/standard/blob/master/docs/RULES-zhcn.md

  8. formdata 和 Payload 区别

    FormData和Payload是浏览器传输给接口的两种格式,这两种方式浏览器是通过Content-Type来进行区分的(了解Content-Type),如果是 application/x-www-f ...

  9. Spring SpringBoot和SpringCloud的关系

    Spring SpringBoot和SpringCloud的关系 Spring Cloud 是完全基于 Spring Boot 而开发,Spring Cloud 利用 Spring Boot 特性整合 ...

  10. GoldenGate实时投递数据到大数据平台(5) - Kafka

    Oracle GoldenGate是Oracle公司的实时数据复制软件,支持关系型数据库和多种大数据平台.从GoldenGate 12.2开始,GoldenGate支持直接投递数据到Kafka等平台, ...