以前用C写的evnet,类似于libev的NIO Reactor框架,主要用于Android、IOS的游戏网络模块。随着它的完善,也开始用于服务端的服务编写。对于一些对资源要求苛刻的服务,用它来写,非常的轻量级,并且已经比较稳定了。
它主要的Event来自Redis的AELOOP,windows版本我补充了Select版本,另外补充了一个pipe通知机制,这个pipe用于支持signal与多线程的异步通知。
目前有不少基于它的服务用于生产环境,跑了一年都不用重启。
最近有些需求,需要大量用到JAVA的第三方库,用C重新写就很坑了。于是想着,能否把服务端需要的部分转成Java版本的。这样就能更好的利用JAVA的生态了。
于是研究了下JAVA的NIO,规划了一下,整体上需要做如下几个部分:
1. EventLoop。这个是最主要的,它是异步事件的发动装置。一般EventLoop会阻塞等待事件的到来,包括:
Socket的IO事件(Read、Write、Connect、Accept),
定时器,
异步信号(Signal、多线程任务完成通知)
2. Pipe。这个是为多线程以及信号(signal)需要的,用于唤醒EventLoop。
如果在某个线程中的任务完成了,需要通知到EventLoop,让它唤醒对异步任务完成的Observer来继续处理任务。
如果服务接收到了SIG_INT、SIG_HUP等,那么也需要通知到EventLoop里注册的Observer。
3. Signal。以前用C写写服务习惯了,非常喜欢Linux的信号机制,Kill -INT pid,就能比较好的优雅的关闭服务了。因此,
java版本的也要安排上。
4. 多线程。需要一个线程池,处理阻塞类的第三方类库。比如数据库操作、HTTPS的客户端,等。那么这些不能阻塞主
线程。因此,需要对这类的调用进行一个包装。等这类任务完成之后,发送信号给EventLoop,让EventLoop唤起对任务
结果感兴趣的Observer来继续处理。
5. HTTPD。对于TCP的类的游戏服务,不需要这个,但是绝大多数,都是需要一个HTTP的服务。所以,这个要不完全支
持, 仅仅支持GET/POST,不支持文件上传。
大概就是这样的一个规划。下面的实现的过程。
1. 对与EventLoop,它的核心功能有这么几个:
1). 事件的监听。它用了Java NIO 的Selector来完成监听。
事件有SocketChannel的读写、ServerChannel的Accept、Pipe的SourceChannel的读。这些Channel都有一个父类。就是
SelectableChannel。它可以被Selector来监听。
跟Linux一样的是它能设置一个超时时间。这个值要根据当前LOOP中的Timers的最短触发时间来设置。
2). 阻塞与唤醒。
阻塞:如果没有IO事件、Signal、Timer-Expired 或者异步信号的话,这个Loop就是阻塞等待的,从而不会浪费CPU的资 源。
JAVA版本用的Selector的select来进行阻塞。在linux上,它映射到epool,windows上就是select了。
唤醒:如果有新的事件,那么我们需要唤醒。对与Socket Channel(包括pipe的Source端)的事件,JVM会自动唤醒。
但是对于Signal、异步线程完成通知这两个,JVM不会给你做。这个时候,PIPE就起到了至关重要的作用,我们在接收到
siganl或者其他线程想唤醒EventLoop的时候,就在Pipe的Sink端,写几个字节。由于pipe的sourceChannel被加入到了
Selector中,那么它可读的时候,就会打断阻塞,Loop就能继续。那么Signal也好、异步任务也罢,它们对应的Observer 都能得到响应的执行了。
3). 对外的接口。
就是初始化,添加对事件的监听、移除对事件的监听、异步唤醒方法。加上一个LOOP方法。
这个LOOP方法的运行过程,就是对各种事件进行Reactor的一个过程。
2. 对于Pipe。
就是用的java的Pipe。它会将自己的读端注册到EventLoop中。写端提供给Signal、多线程模块来用于唤醒EventLoop.
3. 对于多线程。使用了ExecutorService,可以自定义各种参数。每当子线程执行完一个Work。就会将它挂到FinishedQueue里,
然后去唤醒Loop。Loop则会将完成的任务,派发到它的Observer里进行下一步处理。
4. 对于Signal。使用了Java的Signal包。当收到信号的时候,唤醒Loop去执行去唤醒它的SignalObserver。SIG_INT一般就是优 雅的退出了。
5. HTTPD。比较简单,它暴漏两个抽象方法,一个是线程中的阻塞处理。一个是主线程中对结果的Response。
最后进行编码实现。跑了一下还是比较OK的。