(注:文章内容为早期内容,作为设计参考,目前代码已经修改,认证阶段在websocket连接建立之前的http阶段,认证信息加在http头中,认证通过后才建立连接,减轻服务器压力——2021年3月21日)
netty是一个NIO的网络服务器应用框架,现在我们基于这个框架用消息编程的模式开发一款websocket服务器。该服务器有以下特征:
1、基于netty框架
2、基于tcp层的ssl加密传输
3、用户认证,包括名字/密码认证及token认证
4、传输数据编码为json。
一、代码简介
1、实现的类
cn.tianlong.tlobjcet.network.websocket 包下的TLWebSocketServer
2、代码概要
TLWebSocketServer 继承了 抽象类 TLBaseServer 。TLBaseServer是一个对netty包装的服务器,主要部分如下:
protected void run( int port , IObject serviceHander) throws Exception {
if(port==0)
port=this.port;
if(serviceHander==null )
{
if( params!=null && params.get("clientMsgHandler")!=null)
this.clientMsgHandler = (IObject) getModule(params.get("clientMsgHandler"));
else
this.clientMsgHandler = (IObject) getModule("clientMsgHandler");
}
else
this.clientMsgHandler =serviceHander;
bossGroup = new NioEventLoopGroup();
workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(getChannelInitializer(this) )
.option(ChannelOption.SO_BACKLOG, so_BackLog)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture future = bootstrap.bind(new InetSocketAddress(port));
future.sync();
putLog(" 服务器启动,网址是 : " + "http://localhost:" + port,LogLevel.INFO);
runFlag=true;
afterServerRun();
future.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
runFlag=false;
}
}
我们看到这是一个netty服务器端的通用启动过程,其中通道handler 通过 getChannelInitializer(this) 方法配置,getChannelInitializer 方法是一个抽象方法:
protected abstract ChannelHandler getChannelInitializer(TLBaseServer server) ;
这样通过实现该方法,配置不同的通道handler就可以搭建不同的服务器。
现在我们看在TLWebSocketServer中的实现:
@Override
protected ChannelHandler getChannelInitializer(TLBaseServer server) {
return new myChannelInitializer(server);
}
protected class myChannelInitializer extends ChannelInitializer {
protected TLBaseServer server;
public myChannelInitializer(TLBaseServer server) {
this.server = server;
}
@Override
protected void initChannel(Channel ch) throws Exception {
//SSLEngine 此类允许使用ssl安全套接层协议进行安全通信
SSLEngine engine = sslContext.createSSLEngine();
engine.setUseClientMode(false);
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("IdleState", new IdleStateHandler(10, 0, 0, TimeUnit.SECONDS));
pipeline.addLast("IdleState-trigger", idleStateTrigger);
//websocket协议本身是基于http协议的,所以这边也要使用http解编码器
pipeline.addLast(new SslHandler(engine));
pipeline.addLast(new HttpServerCodec());
//以块的方式来写的处理器
pipeline.addLast(new ChunkedWriteHandler());
pipeline.addLast(new HttpObjectAggregator(8192));
pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
pipeline.addLast(new WebSocketServerHandler(server));
}
}
getChannelInitializer 方法创建一个通道初始化类myChannelInitializer(继承netty的ChannelInitializer类),在该类中对需要的handler进行配置。
其中WebSocketServerHandler 为我们自定义的对于websocket的处理类,下面我们看其主要部分:
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 添加
clientIp = ctx.channel().remoteAddress().toString();
((TLWebSocketServer) server).addnoAuthchannels(ctx.channel());
server.putLog("客户端与服务端连接开启:" + clientIp, LogLevel.INFO, "channelActive");
}
/**
* channel 通道 Inactive 不活跃的 当客户端主动断开服务端的链接后,这个通道就是不活跃的。也就是说客户端与服务端关闭了通信通道并且不可以传输数据
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
// 移除
((TLWebSocketServer) server).removeChannel(name);
server.putLog("客户端与服务端连接关闭:" + ctx.channel().remoteAddress().toString(), LogLevel.INFO, "channelInactive");
}
/**
* 接收客户端发送的消息 channel 通道 Read 读 简而言之就是从通道中读取数据,也就是服务端接收客户端发来的数据。但是这个数据在不进行解码时它是ByteBuf类型的
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) {
if (msg instanceof WebSocketFrame) {
handlerWebSocketFrame(ctx, (WebSocketFrame) msg);
}
}
/**
* channel 通道 Read 读取 Complete 完成 在通道读取完成后会在这个方法里通知,对应可以做刷新操作 ctx.flush()
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
}
private void handlerWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
// 本例程仅支持文本消息,不支持二进制消息
if (!(frame instanceof TextWebSocketFrame)) {
ctx.close();
return;
}
// 返回应答消息 {"datas":{"name":"dongq117"},"msgid":"dbmodle"}
String request = ((TextWebSocketFrame) frame).text();
if (request.equals("ping")) {
server.putLog("服务器收到: " + ctx.channel().remoteAddress().toString() + " " + request, LogLevel.INFO, "ping");
return;
}
if (request.equals("echo")) {
server.putLog("服务器收到: " + ctx.channel().remoteAddress().toString() + " " + request, LogLevel.INFO, "echo");
TextWebSocketFrame tws = new TextWebSocketFrame("echo");
ctx.channel().writeAndFlush(tws);
return;
}
server.putLog("服务器收到: " + ctx.channel().remoteAddress().toString() + " " + request, LogLevel.INFO, "channelRead");
TLMsg clientMsg = TLMsgUtils.jsonWithMsgidToMsg(request);
if (clientMsg == null) {
ctx.close();
return;
}
((TLWebSocketServer) server).handleClientMsg(clientMsg, name, ctx.channel());
}
对于websocket连接,本身没有认证环节,也就是任何一个客户端都可以连接到服务器而开启一个连接,认证需要我们自己定义。为区别连接是否认证,我们在服务器TLWebSocketServer中定义了两个变量池,用于来保存已认证通过的连接和没有认证的连接,对于没有认证的连接,在一定时间内如果没有收到认证消息则关闭连接。
protected Map channels = new ConcurrentHashMap();
protected Map noAuthchannels = new ConcurrentHashMap();
channels 用于保存已经认证通过的连接,noAuthchannels 用于保存还没有进行认证的连接。
现在我们看WebSocketServerHandler中对于连接的处理顺序:
1、在channelActive(ChannelHandlerContext ctx),连接建立后,服务器将连接(通道)加入未认证池。
((TLWebSocketServer) server).addnoAuthchannels(ctx.channel());
server中的代码:
protected void addnoAuthchannels(Channel channel) {
noAuthchannels.put(channel, System.currentTimeMillis());
}
未连接池中保存连接及连接加入的时间。
2、在channelRead0(ChannelHandlerContext ctx, Object msg) 读通道内容,也就是处理取客户端发来的内容。
开始如果是客户端发来的ping心跳包,则直接返回响应包。用来维持链路,保持客户端的连接。
然后对于不是心跳包的内容进行消息转换,将客户端内容转换为消息对象TLMsg。
TLMsg clientMsg = TLMsgUtils.jsonWithMsgidToMsg(request);
我们看转换方式:
public static TLMsg jsonWithMsgidToMsg(String jstr) {
TLMsg result = null;
try {
Type type = new TypeToken
函数首先将内容进行json解码,转换成一个map变量,然后取出map变量中的msgid项,在创建一个基于msgid的消息对象:
result = new TLMsg().setMsgId(msgid).addArgs(contentmap);
这是server就可以对这个消息clientMsg进行处理了:
((TLWebSocketServer) server).handleClientMsg(clientMsg, name, ctx.channel());
我们看TLWebSocketServer中如何处理消息:
public void handleClientMsg(TLMsg clientMsg, String chanaelName, Channel clientChannel) {
Channel channel = channels.get(chanaelName);
if (channel == null) {
TLMsg authMsg = createMsg().setMsgId("login").setArgs(clientMsg.getArgs()).setParam("channel", chanaelName)
.setParam("severName",name).setParam("ip", clientChannel.remoteAddress().toString());
TLMsg returnMsg = clientMsgHandler.getMsg(this, authMsg);
noAuthchannels.remove(clientChannel);
if (returnMsg != null) {
channels.put(chanaelName, clientChannel);
String jsonString = gson.toJson(returnMsg.getArgs());
TextWebSocketFrame tws = new TextWebSocketFrame(jsonString);
clientChannel.writeAndFlush(tws);
return;
} else {
HashMap serverData = new HashMap<>();
serverData.put("code", 9999);
serverData.put("message", "auth failure");
String jsonString = gson.toJson(serverData);
TextWebSocketFrame tws = new TextWebSocketFrame(jsonString);
clientChannel.writeAndFlush(tws);
clientChannel.close();
return;
}
}
TLMsg returnMsg = clientMsgHandler.getMsg(this, clientMsg);
if (returnMsg != null) {
HashMap serverData = returnMsg.getArgs();
String jsonString = gson.toJson(serverData);
TextWebSocketFrame tws = new TextWebSocketFrame(jsonString);
channel.writeAndFlush(tws);
putLog("服务器发送:" + channel.remoteAddress().toString() +" " +jsonString, LogLevel.INFO, "handleClientMsg");
}
}
首先在认证连接(通道)池中查找是否存在该连接,如果没有存在说明该通道没有认证,因此发出认证过程,如果认证通过则将通道加入到认证池中,没有通过则关闭通道。
如果通道存在说明已经认证通过,则进行客户消息处理:
TLMsg returnMsg = clientMsgHandler.getMsg(this, clientMsg);
clientMsgHandler 为客户消息处理转发类,该类一般不直接处理消息,而是根据消息id转发至不同的模块处理。clientMsgHandler可在服务器配置中定义具体的类,在我们应用中,我们将clientMsgHandler配置为TLMsgRouter,我们看TLMsgRouter代码:
public class TLMsgRouter extends TLBaseModule {
public TLMsgRouter(){
super();
}
public TLMsgRouter(String name ){
super(name);
}
public TLMsgRouter(String name , TLObjectFactory modulefactory){
super(name,modulefactory);
}
@Override
protected void init() {
}
@Override
protected TLMsg checkMsgAction(Object fromWho, TLMsg msg) {
return null ;
}
}
TLMsgRouter 非常简单,仅仅是实例化了基本模块对象TLBaseModule,没有对消息处理的具体内容。这是为何?因为基本模块对象TLBaseModule已经拥有根据消息id来转发消息的功能了,TLMsgRouter利用其自身功能即可。对于消息的转发,通过配置文件定义,我们看一个案例配置:
user
TLMsgRouter 根据不同的msgid转发不同的消息至相应模块,如对于消息id为login,转发消息至模块"myuserManagerModule处理。在TLWebSocketServer对连接进行认证的时候,就是如此实现的。这里看到,通过不同的配置项,我们可以自定义任意的认证模块。在我们实现中,我们定义了TLUserManagerModule认证模块,通过配置该模块可以实现用户名/密码及token认证。
对于主动由服务器推送客户端 ,由putToClient方法实现:
protected TLMsg putToClient(Object fromWho, TLMsg msg) {
Object chanaelName = msg.getParam(NETTY_CHANAEL);
int result = 0;
Object content = msg.getParam(MSG_CONTENT);
String contentStr;
if (content instanceof String)
contentStr = (String) content;
else
contentStr = gson.toJson(content);
if (chanaelName == null)
result = putContentToClient(contentStr);
else if (chanaelName instanceof String)
result = putContentToClient(contentStr, (String) chanaelName);
else if (chanaelName instanceof String[])
result = putContentToClient(contentStr, (String[]) chanaelName);
else if (chanaelName instanceof List)
result = putContentToClient(contentStr, (List) chanaelName);
putLog("服务器发送:" + chanaelName.toString() +" " +content, LogLevel.INFO, "handleClientMsg");
return createMsg().setParam(RESULT, result);
}
方法中,首先取得客户端对应的通道名,将内容推送出去。根据通道的类型,可以单推或组推。
在这里我们有个问题,对于应用层,我们是不知道具体用户的通道的,我们经常是以用户名为标识推送信息。对于server只是保有具体的连接(通道),没有用户信息。因此我们就不能直接通过server推送消息,这里通过TLUserManagerModule用户管理模块推送,在用户管理模块中,保存着用户与通道的对应(当用户认证的时候记录的)。我们看用户管理模块:
private TLMsg putToUser(Object fromWho, TLMsg msg) {
Object userid = msg.getParam("userid");
ArrayList channels = new ArrayList<>();
if (userid == null) {
TLMsg toServer = createMsg().setAction("putToClient").setParam(MSG_CONTENT, msg.getArgs());
return putMsg(severName, toServer);
}
if (userid instanceof String)
channels = getUserChannelAsList((String) userid);
else if (userid instanceof String[]) {
String[] array = (String[]) userid;
for (int i = 0; i < array.length; i++) {
String toUserid =array[i];
getUserChannel(toUserid , channels);
}
} else if (userid instanceof List) {
List array = (List) userid;
for (int i = 0; i < array.size(); i++) {
String toUserid =(String) array.get(i);
getUserChannel(toUserid , channels);
}
}
if (channels == null ||channels.isEmpty())
return null;
msg.removeParam("userid");
TLMsg toServer = createMsg().setAction("putToClient").setParam(MSG_CONTENT, msg.getArgs());
if(channels.size()==1)
toServer.setParam(NETTY_CHANAEL, channels.get(0));
else
toServer.setParam(NETTY_CHANAEL, channels);
return putMsg(severName, toServer);
}
当给某用户推送消息时,首先找出用户对应的通道,然后转发给server,由server根据通道推送,从而实现应用层给用户的推送。
现在我们基本了解websocket服务器处理客户消息的流程。客户消息由hander接收,解码后转给server,server再将消息通过clientMsgHandler客户消息转发模块把消息转发到响应的处理模块。
这里有个注意,对于开始提到的未认证连接放到未认证池中,那么这些未认证连接我们不能一直保持的 ,如何从池中清除呢?在server启动后,我们启动了一个线程来监听未连接池,对于里面的每个连接,如果超时了还没有认证,则从池中取消、关闭连接。代码通过如下实现:
protected void afterServerRun() {
putMsg(this, createMsg().setAction("checkNoAuthChanaels")
.setParam(SESSIONDEAMON, true)
.setParam(EXCEPTIONHANDLER, new MyUnchecckedExceptionhandler(this, createMsg().setAction("restart")))
.setWaitFlag(false));
}
protected void checkNoAuthChanaels(Object fromWho, TLMsg msg) {
int expire = 30;
if (params.get("expire") != null)
expire = Integer.parseInt(params.get("expire"));
Long expiremillis = Long.valueOf(expire * 1000);
while (true) {
try {
Thread.sleep(2000);
checkNoAuthChanaels(expiremillis);
} catch (InterruptedException e) {
putLog("发生异常,任务模块重启", LogLevel.WARN, "exception1");
putLog(e, LogLevel.ERROR, "exception2");
init();
}
}
}
protected void checkNoAuthChanaels(Long delay) {
putLog("开始检查空闲链接,空闲链接:" + noAuthchannels.size(), LogLevel.INFO, "checkNoAuthChanaels");
Long nowTime = System.currentTimeMillis();
for (Channel channel : noAuthchannels.keySet()) {
Long stime = noAuthchannels.get(channel);
Long ntime = nowTime - stime;
if (ntime > delay) {
noAuthchannels.remove(channel);
putLog("空闲链接关闭" + channel.remoteAddress().toString() + " idle:" + ntime, LogLevel.INFO, "idleClose");
channel.close();
}
}
}
afterServerRun() 是server运行后触发的方法。在该方法中,我们启动一个线程周期进行未认证池检查。
二、模块应用
各模块已经完成,现在我们搭建一个具体应用 。根据java要求,我们要定义一个包含main函数的启动类 。在cn.tianlong.java.tlobjdemo.http包下,webSocketServerStart为我们建的一个启动类,现在我们看代码:
public class webSocketServerStart extends TLBaseModule {
public webSocketServerStart(String main, TLObjectFactory myfactory) {
super(main, myfactory);
}
@Override
protected void init() {
}
@Override
protected TLMsg checkMsgAction(Object fromWho, TLMsg msg) {
return null;
}
public static void main(String[] args) {
String configdir = "/tlobjdemo/";
String path =webSocketServerStart.class.getResource(configdir).getPath();
System.setProperty("log4j.configurationFile", path+"log4j2.xml");
TLObjectFactory myfactory = TLObjectFactory.getInstance(configdir, "mywebSocketServer_factory_config.xml");
myfactory.boot();
webSocketServerStart serverstart =new webSocketServerStart("main",myfactory);
serverstart.run();
}
private void run() {
putMsg("mywebSocketServer",createMsg().setAction("run").setWaitFlag(false));
putMsg("msgScanner",new TLMsg().setAction("startScan"));
}
}
这是消息编程框架的标准启动类。首先定义配置文件的路径,实例化模块工厂。然后实例化启动类:
webSocketServerStart serverstart =new webSocketServerStart("main",myfactory);
在启动类的run方法中开始具体模块的启动:
putMsg("mywebSocketServer",createMsg().setAction("run").setWaitFlag(false));
这里通过给socketServer发送run消息进行server启动。其中消息的setWatiFlag方法指明该消息一个异步消息,既启动一个线程来运行消息。
对于模块的定义在工厂配置文件mywebSocketServer_factory_config.xml 中,我们看配置文件的主要项:
在配置文件中,我们定义了我们的服务器mywebSocketServer ,其具体模块为webSocketServer(TLWebSocketServer的别名),配置中定义了ip、端口 ssl认证文件 ,我们前面提到过的客户消息转发模块clientMsgHandler;
定义了客户消息转发模块具体指定的类“ msgRouter”;
定义了 用户管理模块myuserManagerModule对应的模块 ,及相关配置,其中认证方式为token;
关于客户token的产生由其他应用提供。
下面我们看运行:
"C:\Program Files\Java\jdk1.8.0_66\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2018.1.3\lib\idea_rt.jar=55935:C:\Program Files\JetBrains\IntelliJ IDEA 2018.1.3\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_66\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_66\jre\lib\rt.jar;D:\javaweb\web\WEB-INF\classes;D:\javaweb\web\WEB-INF\lib\kxml2-2.3.0.jar;D:\javaweb\web\WEB-INF\lib\okhttp-3.10.0.jar;D:\javaweb\web\WEB-INF\lib\okhttputils-2_6_2.jar;D:\javaweb\web\WEB-INF\lib\okio-1.14.1.jar;D:\javaweb\web\WEB-INF\lib\xmlpull_1_1_3_4c.jar;D:\javaweb\web\WEB-INF\lib\commons-lang3-3.5.jar;D:\javaweb\web\WEB-INF\lib\velocity-engine-core-2.0.jar;D:\javaweb\web\WEB-INF\lib\gson-2.8.5.jar;D:\javaweb\web\WEB-INF\lib\jjwt-0.9.0.jar;D:\javaweb\web\WEB-INF\lib\jedis-3.0.1.jar;D:\javaweb\web\WEB-INF\lib\servlet-api.jar;D:\javaweb\web\WEB-INF\lib\c3p0-0.9.5.2.jar;D:\javaweb\web\WEB-INF\lib\quartz-2.1.7.jar;D:\javaweb\web\WEB-INF\lib\ehcache-3.5.2.jar;D:\javaweb\web\WEB-INF\lib\xmlpull_1_0_5.jar;D:\javaweb\web\WEB-INF\lib\commons-io-2.4.jar;D:\javaweb\web\WEB-INF\lib\java-jwt-3.4.0.jar;D:\javaweb\web\WEB-INF\lib\cache-api-1.0.0.jar;D:\javaweb\web\WEB-INF\lib\shiro-all-1.3.2.jar;D:\javaweb\web\WEB-INF\lib\log4j-api-2.11.0.jar;D:\javaweb\web\WEB-INF\lib\slf4j-api-1.7.25.jar;D:\javaweb\web\WEB-INF\lib\log4j-core-2.11.0.jar;D:\javaweb\web\WEB-INF\lib\commons-codec-1.11.jar;D:\javaweb\web\WEB-INF\lib\jackson-core-2.8.9.jar;D:\javaweb\web\WEB-INF\lib\commons-dbutils-1.7.jar;D:\javaweb\web\WEB-INF\lib\commons-pool2-2.6.1.jar;D:\javaweb\web\WEB-INF\lib\thumbnailator-0.4.8.jar;D:\javaweb\web\WEB-INF\lib\jackson-databind-2.8.9.jar;D:\javaweb\web\WEB-INF\lib\netty-all-4.1.25.Final.jar;D:\javaweb\web\WEB-INF\lib\log4j-slf4j-impl-2.11.1.jar;D:\javaweb\web\WEB-INF\lib\commons-fileupload-1.3.3.jar;D:\javaweb\web\WEB-INF\lib\commons-collections-3.2.2.jar;D:\javaweb\web\WEB-INF\lib\jackson-annotations-2.8.0.jar;D:\javaweb\web\WEB-INF\lib\okhttputils-2.6.2-sources.jar;D:\javaweb\web\WEB-INF\lib\aliyun-java-sdk-core-4.1.0.jar;D:\javaweb\web\WEB-INF\lib\commons-configuration2-2.4.jar;D:\javaweb\web\WEB-INF\lib\mchange-commons-java-0.2.11.jar;D:\javaweb\web\WEB-INF\lib\aliyun-java-sdk-dysmsapi-1.0.0.jar;D:\javaweb\web\WEB-INF\lib\c3p0-oracle-thin-extras-0.9.5.2.jar;D:\javaweb\web\WEB-INF\lib\mysql-connector-java-5.1.39-bin.jar" cn.tianlong.java.tlobjdemo.http.webSocketServerStart
2019-10-12 16??16,864 (TLLog.java:179) INFO main:moduleFactory Tag:doMsgList Content:start msg: destion:null msgid:null action:getModule
2019-10-12 16??16,876 (TLLog.java:179) INFO main:moduleFactory Tag:doMsgList Content:start msg: destion:null msgid:null action:getModule
2019-10-12 16??16,878 (TLLog.java:179) INFO main:threadPool Tag:start Content: module: threadPool start configFile:/tlobjdemo/threadPool_config.xml
2019-10-12 16??16,879 (TLLog.java:179) INFO main:moduleFactory Tag:doMsgList Content:start msg: destion:null msgid:null action:getModule
2019-10-12 16??16,889 (TLLog.java:179) INFO main:monitorConfig Tag:default Content:监听配置文件模块启动
2019-10-12 16??16,890 (TLLog.java:179) INFO main:monitorConfig Tag:start Content: module: monitorConfig start configFile:/tlobjdemo/monitorConfig_config.xml
2019-10-12 16??16,891 (TLLog.java:179) INFO main:moduleFactory Tag:doMsgList Content:start msg: destion:null msgid:null action:getModule
2019-10-12 16??16,899 (TLLog.java:179) INFO main:myclientMsgHandler Tag:start Content: module: myclientMsgHandler start configFile:/tlobjdemo/myclientMsgHandler_config.xml
2019-10-12 16??16,900 (TLLog.java:179) INFO main:moduleFactory Tag:doMsgList Content:start msg: destion:null msgid:null action:getModule
2019-10-12 16??16,913 (TLLog.java:179) INFO main:c3p0Conn Tag:start Content: module: c3p0Conn start configFile:/tlobjdemo/c3p0Conn_config.xml
2019-10-12 16??16,913 (TLLog.java:179) INFO main:c3p0Conn Tag:getMsg Content:run action:init
2019-10-12 16??17,086 (TLLog.java:179) INFO Thread-1:monitorConfig Tag:getMsg Content:run action:checkModules
2019-10-12 16??17,340 (Slf4jMLog.java:212) INFO MLog clients using slf4j logging.
2019-10-12 16??18,314 (Slf4jMLog.java:212) INFO Initializing c3p0-0.9.5.2 [built 08-December-2015 22:06:04 -0800; debug? true; trace: 10]
2019-10-12 16??18,475 (TLLog.java:179) INFO main:dbserver1 Tag:start Content: module: dbserver1 start configFile:/tlobjdemo/database_config.xml
2019-10-12 16??18,476 (TLLog.java:179) INFO main:moduleFactory Tag:doMsgList Content:start msg: destion:null msgid:null action:getModule
2019-10-12 16??18,479 (TLLog.java:179) INFO main:c3p0Conn Tag:start Content: module: c3p0Conn start configFile:/tlobjdemo/c3p0Conn_config.xml
2019-10-12 16??18,480 (TLLog.java:179) INFO main:c3p0Conn Tag:getMsg Content:run action:init
2019-10-12 16??18,517 (TLLog.java:179) INFO main:dbserver2 Tag:start Content: module: dbserver2 start configFile:/tlobjdemo/database_config.xml
2019-10-12 16??18,517 (TLLog.java:179) INFO main:moduleFactory Tag:doMsgList Content:start msg: destion:null msgid:null action:getModule
2019-10-12 16??18,520 (TLLog.java:179) INFO main:serviceModle Tag:start Content: module: serviceModle start configFile:/tlobjdemo/serviceModle_config.xml
2019-10-12 16??18,960 (TLLog.java:179) INFO main:mywebSocketServer Tag:start Content: module: mywebSocketServer start configFile:/tlobjdemo/mywebSocketServer_config.xml
2019-10-12 16??18,965 (TLLog.java:179) INFO main:msgScanner Tag:start Content: module: msgScanner start configFile:/tlobjdemo/msgScanner_config.xml
2019-10-12 16??18,965 (TLLog.java:179) INFO main:msgScanner Tag:getMsg Content:run action:startScan
msgScanner start:
2019-10-12 16??18,969 (TLLog.java:179) INFO Thread-2:mywebSocketServer Tag:getMsg Content:run action:run
2019-10-12 16??19,717 (TLLog.java:179) INFO Thread-2:mywebSocketServer Tag:default Content: 服务器启动,网址是 : http://localhost:9081
2019-10-12 16??19,719 (TLLog.java:179) INFO Thread-4:mywebSocketServer Tag:getMsg Content:run action:checkNoAuthChanaels
2019-10-12 16??21,720 (TLLog.java:179) INFO Thread-4:mywebSocketServer Tag:checkNoAuthChanaels Content:开始检查空闲链接,空闲链接:0
2019-10-12 16??23,720 (TLLog.java:179) INFO Thread-4:mywebSocketServer Tag:checkNoAuthChanaels Content:开始检查空闲链接,空闲链接:0
2019-10-12 16??25,720 (TLLog.java:179) INFO Thread-4:mywebSocketServer Tag:checkNoAuthChanaels Content:开始检查空闲链接,空闲链接:0
2019-10-12 16??27,722 (TLLog.java:179) INFO Thread-4:mywebSocketServer Tag:checkNoAuthChanaels Content:开始检查空闲链接,空闲链接:0
2019-10-12 16??29,723 (TLLog.java:179) INFO Thread-4:mywebSocketServer Tag:checkNoAuthChanaels Content:开始检查空闲链接,空闲链接:0
服务器启动(本例中在模块工厂中还配置了启动其他模块,如数据库、监听模块等)。
在上面启动类中,我们注意到,启动一个server其实就是运行一个模块对象,那么我们由此可以想到我们可以同时运行几个server对象,仅仅这样的发出消息指令即可:
putMsg("mywebSocketServer",createMsg().setAction("run").setWaitFlag(false));
这些server由同一个工厂启动,因此它们可共享空间和数据。实际也就是一个程序可以同时启动、监听多个端口。不同的端口可以处理不同的消息。实际应用中我们可以用一个端口接受客户端消息,其他端口接受其他应用消息,如web消息、管理消息等。通过模块化的灵活组合,我们可以搭建不同的应用。