统一对象消息编程详解——一个基于netty的WebSocket服务器

 (注:文章内容为早期内容,作为设计参考,目前代码已经修改,认证阶段在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>() {
            }.getType();
            LinkedTreeMap contentmap;
            try {
                Gson gson = new Gson();
                contentmap = gson.fromJson(jstr, type);
            } catch (JsonSyntaxException e) {
                contentmap = null;
            }
            if (contentmap == null)
                return null;
            String msgid = (String) contentmap.get("msgid");
            result = new TLMsg().setMsgId(msgid).addArgs(contentmap);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;

    }

   函数首先将内容进行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消息、管理消息等。通过模块化的灵活组合,我们可以搭建不同的应用。

 

你可能感兴趣的:(统一对象消息编程详解,websocket,netty,消息编程,app后台)