Java服务端NIO多线程编程库系列(一)

        Java作为一种跨平台的开发语言,被广泛地应用,对比C++来讲,不需要关心对象的释放,缓冲区的管理,使编程的细节处理上要简单了很多,但是在高负载、多线程、多任务的协作处理时,对象的频繁new,线程的频繁创建、销毁,仍有可能导致程序的异常崩溃;每一次网络开发的调试都是一个几乎要崩溃的过程,各种细节的处理会导致开发过程缓慢、周期变长,本文通过对NIO和线程池的调度封装,使服务端的高并发、网络信息传送简化成了命令的传送,让开发的整个过程规范化,使程序员在相应开发时只需注意业务的处理,将所有的网络端、多线程设定和管理简化成了配置文件。

        下面我先给出一段使用封装后的库进行网络开发的实例。

//这个参数类是创建服务所需要的一些配置参数,这些参数可以通过命令行或配件文件进行传递
//这里我们只看一下它的结构,而不用关心具体的内容
public class SceneParameter
{
    public int m_iReconnectCount = Integer.MAX_VALUE;

    public long m_lReconnectIntervalMSEL = 5 * 1000;

    /**
     * CipherKeyService监听地址
     */
    public InetSocketAddress m_remoteAddressCipherKeyService = null;

    /**
     * CompensationService监听地址
     */
    public InetSocketAddress m_remoteAddressCompensationService = null;

    /**
     * MulticastSourceService监听地址
     */
    public InetSocketAddress m_remoteAddressMulticastSourceService = null;

    /**
     * PublicNetworkService监听地址
     */
    public InetSocketAddress m_remoteAddressPublicNetworkService = null;

    /**
     * CompensationService远程连接地址的long型表示
     *      高32位保存了端口号,
     *      低32位保存了IP地址;
     */
    public long m_lCompensationServiceAddress = 0;

    public MySceneLog m_mySenceLog;
}

        我们为每一个服务端所需要的配置创建一个参数类,参数类中的内容可以是由命令行或者配置文件来赋值,这个参数类做为创建服务时传递的参数。

        

public class AssignSceneAddressService
{
    /**
     * 版本号
     */
    private static String s_strVersion = "V1.0.3.2020.02.23.20.11";

    static class AppParameter
    {
        public String m_strConfigFile = "AssignSceneAddressService.config";
    }

    public static void main(String[] args)
    {
        try
        {
            //Tcp监听服务,用于终端与CompensationServer的连接
            //CompensationServer主动与组播服务器建立连接
            SceneParameter sp = new SceneParameter();
            TcpServerParameter tsp = new TcpServerParameter(null, null);
            tsp.m_nioTcpParameter = new NioTcpParameter();
            //应该将服务端的配置变成配置文件

            AppParameter ap = new AppParameter();
            AssignSceneAddressServiceConfig asasc = AssignSceneAddressService.parseCmdLine( ap, args, "=");
            sp.setCompensationServiceRemoteAddress( asasc.m_remoteCompensationServiceConfig.getInetSocketAddress() );
            sp.m_remoteAddressCipherKeyService = asasc.m_remoteCipherKeyServiceConfig.getInetSocketAddress();
            sp.m_remoteAddressMulticastSourceService = asasc.m_remoteMulticastSourceServiceConfig.getInetSocketAddress();
            sp.m_remoteAddressPublicNetworkService = asasc.m_publicNetworkServiceConfig.getInetSocketAddress();
            sp.m_iReconnectCount = asasc.m_nioTcpConnectorParmeterConfig.m_iReconnectCount;
            sp.m_lReconnectIntervalMSEL = asasc.m_nioTcpConnectorParmeterConfig.m_lReconnectIntervalMSEL;

            tsp.m_strBindAddress = asasc.m_remoteAssignSceneAddressServiceConfig.m_strBindAddress;
            tsp.m_iListenPort = asasc.m_remoteAssignSceneAddressServiceConfig.m_iPort;
            tsp.m_iMaxSelectorCount = asasc.m_iMaxSelectorCount;
            asasc.m_nioTcpParameterConfig.copyTo( tsp.m_nioTcpParameter );

            //这里可以根据参数,决定选用哪一种Log
            sp.m_mySenceLog = new MySceneLog();

            LogLevel.setLogOutLevel( asasc.m_logOutConfig.m_lLogOutLevel );
            //LogLevel.getLogFile().setJsonLogFileConfig( asasc.m_logFileConfig );
            //LogLevel.getLogFile().startThread();
            LogLevel.getLogFileEx().setJsonLogFileConfig( asasc.m_logFileConfig );
            LogLevel.getLogFileEx().startThread();

            LogLevel.outputToLogFile( Convenient.s_dfFullDateTime.format(new Date(System.currentTimeMillis()))
                    + " SMKE - AssignSceneAddressService is running! " + AssignSceneAddressService.s_strVersion
                    + "; \r\n\tListen address is " + tsp.getAddress()
                    + "; \r\n\tPublic network service address is " + sp.getPublicNetworkServiceRemoteAddress()
                    + "; \r\n\tMulticast source service address is " + sp.getMulticastSourceServiceRemoteAddress()
                    + "; \r\n\tCompensation service address is " + sp.getCompensationServiceRemoteAddress()
                    + "; \r\n\tCipherKey service address is " + sp.getCipherKeyServiceRemoteAddress() );
            
            //创建服务的处理线程
            new SceneCampsite( sp, tsp, asasc ).startService();
        }
        catch ( Exception e )
        {
            e.printStackTrace();
        }
    }

    public static AssignSceneAddressServiceConfig parseCmdLine( AppParameter ap, String[] args, String strSeparator )
    {
        CmdLineParameter clp = new CmdLineParameter();
        clp.parseCmdLine( args, true, strSeparator );

        if( args.length <= 0 )
        {
            //如果没有参数
            AssignSceneAddressService.outHelpInfo();
        }
        else
        {
            String str = clp.getSetValue("-File");
            if (str != null)
            {
                //设置要打开的本地的文件名
                ap.m_strConfigFile = str;
            }
        }

        AssignSceneAddressServiceConfig asasc = new AssignSceneAddressServiceConfig();
        JsonConfigBase.Read( new File( ap.m_strConfigFile ), asasc,null );
        return asasc;
    }

    private static void outHelpInfo()
    {
        System.out.println("可以输入以下的参数的任意组合(大小写敏感):\r\n"
                + "\t-File=n\t要加载的Config文件的名称,默认为AssignSceneAddressService.Config\r\n" );
    }
}

        从上面的代码可以看到,main()函数很简单,只是解晰命令行和读取配置文件,将配置文件中的内容赋值给需要的参数类,而服务端具体的创建,则由服务的入口线程根据参数完成,所有的配置文件的读取和解晰都采用Json方式进行配置,关于配置文件的标准化读取后面再介绍。

        下面我们再看一下具体的服务的入口线程的代码,看一下是如何定义多线程和实现业务处理的。

   /**
     * 开启服务
     */
    public void startService()
    {
        MyNioTcpLog myLog = this.m_sceneParameter.m_mySenceLog;
        try
        {
            //创建Selector封装对象。注意:这个对象不需要关联线程启动,只要new出一个实例就可以了
            NioTcpReactorRunnable ntrr = new NioTcpReactorRunnable( myLog, 1 );

            //创建Selector池
            SelectorPool selectorPool = new SelectorPool( this.m_tcpServerParameter.m_iMaxSelectorCount, ntrr, myLog );

            //设置服务端运行的参数
            this.m_tcpServerParameter.m_selectPool = selectorPool;
            this.m_tcpServerParameter.m_myNioTcpLog = myLog;
            this.m_tcpServerParameter.m_nioTcpProcessor = new SceneCmdReactor(
                    null,
                     ntrr,
                     this.m_tcpServerParameter.m_nioTcpParameter,
                    this );

            //启动Tcp监听端口
            NioTcpListenerReactorRunnable nioTcpListenerReactor = new NioTcpListenerReactorRunnable( this.m_tcpServerParameter );
            new Thread( nioTcpListenerReactor ).start();

            //创建定时器对象
            this.m_timer = new TimerRunnable( 1000 );
            new Thread( this.m_timer ).start();

            //创建Nio方式下的Tcp连接的管理类,当连接建立时,会自动关联到NioTcpProcessorReadWrite类进行读写消息处理
            NioTcpConnectMgr ntrm = new NioTcpConnectMgr(
                    selectorPool,
                    this.m_timer,
                    this.m_tcpServerParameter.m_nioTcpProcessor,
                    this.m_tcpServerParameter.m_nioTcpParameter,
                    myLog );

            if( this.m_sceneParameter.m_remoteAddressCompensationService != null )
            {
                //创建和CompensationService之间的tcp连接,并关联到连接管理类
                new MyNioTcpConnector( ntrm, this, MyNioTcpConnector.C_COMPENSATION_SERVICE ).connect(
                        this.m_sceneParameter.m_remoteAddressCompensationService,
                        this.m_sceneParameter.m_iReconnectCount,
                        this.m_sceneParameter.m_lReconnectIntervalMSEL );
            }

            if( this.m_sceneParameter.m_remoteAddressCipherKeyService != null )
            {
                //创建和CipherKeyService之间的tcp连接,并关联到连接管理类
                new MyNioTcpConnector( ntrm, this, MyNioTcpConnector.C_CIPHER_KEY_SERVICE ).connect(
                        this.m_sceneParameter.m_remoteAddressCipherKeyService,
                        this.m_sceneParameter.m_iReconnectCount,
                        this.m_sceneParameter.m_lReconnectIntervalMSEL );
            }

            if( this.m_sceneParameter.m_remoteAddressMulticastSourceService != null )
            {
                //创建和MulticastSourceService之间的tcp连接,并关联到连接管理类
                new MyNioTcpConnector( ntrm, this, MyNioTcpConnector.C_MULTICAST_SOURCE_SERVICE ).connect(
                        this.m_sceneParameter.m_remoteAddressMulticastSourceService,
                        this.m_sceneParameter.m_iReconnectCount,
                        this.m_sceneParameter.m_lReconnectIntervalMSEL );
            }

            if( this.m_sceneParameter.m_remoteAddressPublicNetworkService != null )
            {
                //创建和PublicNetworkService之间的tcp连接,并关联到连接管理类
                new MyNioTcpConnector( ntrm, this, MyNioTcpConnector.C_PUBLIC_NETWORK_SERVICE ).connect(
                        this.m_sceneParameter.m_remoteAddressPublicNetworkService,
                        this.m_sceneParameter.m_iReconnectCount,
                        this.m_sceneParameter.m_lReconnectIntervalMSEL );
            }

            this.m_mysql = new MyMySql( this.m_assignSceneAddressServiceConfig.m_dbConfig );
            this.loadDataFromDB();

            //启动监听地址组播
            MCSendDataRunnable mcsdr = new MCSendDataRunnable();
            mcsdr.m_strMulticastIp = this.m_assignSceneAddressServiceConfig.m_multicastNotifyConfig.m_strBindAddress;
            mcsdr.m_iMulticastPort = this.m_assignSceneAddressServiceConfig.m_multicastNotifyConfig.m_iPort;
            MCAddress mcAddress = new MCAddress();
            mcAddress.set( this.m_tcpServerParameter.m_strBindAddress, this.m_tcpServerParameter.m_iListenPort );
            mcsdr.setSendData( mcAddress.getData() );
            new Thread( mcsdr ).start();
        }
        catch ( IOException e )
        {
            MyNioTcpLog.outMinException( e );
        }
    }

        通过上面的简单几行,就已经:

  1. 创建了1个NIO的Server用于监听;
  2. 创建了1个TCP的NIO连接管理类,负责主动和被动连接的创建,管理连接的销毁和复用,管理主动连接的断开后自动重连等操作;
  3. 创建了4个TCP主动连接,连接到其它的服务,和其它服务这间形成通讯;
  4. 创建了1个线程池,用来管理多个NIO的连接,根据连接个数进行调度,使新的连接根据调度算法分配到不同的线程中;
  5. 创建了1个组播探测,用来检测当前网络是否支持组播;
  6. 创建了1个动态日志管理类;
  7. 创建了1个多复用定时器类;
  8. 其中的SceneCmdReactor类就是命令处理类,所有的TCP命令都是都过这个类来处理的。

        这里先简单介绍一下NIO,NIO是非阻塞式IO通讯方式,IO缓冲区中只要有数据,就会被唤醒,要求对IO进行读取,当缓冲区中的数据读取完后,则会等待接收远端的数据,在等待期间,IO会被挂起。

        当有数据需要处理时,在SceneCmdReactor类中的表现形式就是命令处理方法被调用。TCP的数据是一个流,在数据处理时,必需要将流分解成一个个的数据报,才能较方便地处理,在这里,流被分解成了一个个的数据包,经过封装好的命令解析器,数据包被解析成了一个个的命令,然后才会调用SceneCmdReactor类的命令处理类。注意:在高并发的服务端设计中,单个命令处理的时间应该尽量的短,并且可控,其执行时间应该是一个常量,在处理时可以有循环,但循环的次数应该是明确的,并且次数不能太多,如果不可控,应该通过异步的方式来解决。

        下面是SceneCmdReactor的具体业务处理类。

public class SceneCmdReactor extends NioCmdReactor implements ITimeoutNotifyInterface
{
    private SceneCampsite m_campsite;

    private Equipment m_equipment = null;

    public SceneCmdReactor( SocketChannel socketChannel, NioTcpReactorRunnable ntrr, NioTcpParameter ntp, SceneCampsite campsite )
    {
        super( new CmdHead(), socketChannel, ntrr, ntp );
        this.m_campsite = campsite;
    }

    @Override
    public NioTcpProcessor createNew( NioTcpReactorRunnable ntrr, SocketChannel socketChannel, NioTcpParameter ntp, NioTcpConnector ntc )
    {
        SceneCmdReactor ccr = new SceneCmdReactor( socketChannel, ntrr, ntp, this.m_campsite );
        ccr.setReconnect( true, ntc );
        return ccr;
    }

    @Override
    public void onRelease()
    {
        super.onRelease();
        if( this.m_campsite != null )
        {
            if( this.m_equipment != null )
            {
                this.m_campsite.closeEquipment(this.m_equipment);
                this.m_equipment = null;
            }
            this.m_campsite = null;
        }
    }

    @Override
    public void onReclaimCmd( NioCmdHead cmd, int iReason )
    {
        if( cmd.isHeartbeat() && iReason == NioCmdProcessor.CMD_RECLAIM_REASON_SEND_COMPLETED )
        {
            LogLevel.outputToLogFile( "SceneCmdReactor.onReclaimCmd() --- Heartbeat packet send complete, active!" );
            this.m_campsite.activeTimeoutTracking();
        }
    }

    @Override
    public boolean onRecvOneCmd( NioCmdHead cmd )
    {
        if( LogLevel.isEnableLogOut( LogLevel.C_LOG_OUT_LEVEL_TCP_CMD_OUT ) )
        {
            cmd.println();
            LogLevel.outputToLogFile( "SceneCmdReactor.onRecvOneCmd() --- CmdId = " + cmd.m_iCmd );
        }
        switch ( cmd.m_iCmd )
        {
            case CmdDef.C_CMD_NOTIFY_BUY_TICKET:
            {
                //买票
                return this.m_campsite.processNotifyBuyTicket( cmd.m_lUserContext, (CmdTicket)cmd.m_objData, this );
            }
            case CmdDef.C_CMD_NOTIFY_RETURN_TICKET:
            {
                //退票
                return this.m_campsite.processNotifyReturnTicket( cmd.m_lUserContext, (CmdTicket)cmd.m_objData, this );
            }
            case CmdDef.C_CMD_NOTIFY_CHANGE_SCENE:
            {
                //现换场次
                this.m_campsite.processNotifyChangeScene( cmd.m_lUserContext, cmd.m_lFlag, (CmdTicket)cmd.m_objData, this );
                return true;
            }
            case CmdDef.C_CMD_REQUEST_MULTICAST_ADDRESS:
            {
                //请求多播地址
                this.m_campsite.processRequestMulticastAddress( cmd.m_lUserContext, cmd.m_lFlag, this );
                return true;
            }
            case CmdDef.C_CMD_NOTIFY_CHECK_IN:
            {
                //检票入场
                this.m_campsite.processNotifyCheckIn( cmd.m_lUserContext, (CmdTicket)cmd.m_objData, this );
                return true;
            }
            case CmdDef.C_CMD_APPEND_ONE_FILM:
            {
                //增加一部电影
                this.m_campsite.processAppendOneFilm( cmd.m_lUserContext, (CmdReturnFilmInfo)cmd.m_objData, true );
                return true;
            }
            case CmdDef.C_CMD_OPEN_ONE_MULTICAST_SOURCE_RETURN:
            {
                //开启一个组播源返回,告之了开启的组播源的IP和端口
                this.m_campsite.processOpenOneMulticastSourceReturn( cmd.m_lUserContext, cmd.m_lFlag, (CmdMergeSceneAddress)cmd.m_objData );
                return true;
            }
            case CmdDef.C_CMD_NOTIFY_STATUS:
                return true;
        }
        return false;
    }

    @Override
    public boolean onTimeoutNotify( TimeoutTracking tt, boolean bTimeout )
    {
        try
        {
            if (bTimeout)
            {
                //如果是超时
                LogLevel.outputToLogFile("SceneCmdReactor.onTimeoutNotify() --- Connect timeout with public networ! Close Channel!");
                this.closeChannel();
                return false;
            }
            LogLevel.outputToLogFile("SceneCmdReactor.onTimeoutNotify() --- Ready send heartbeat packet is over! wait send....");
            if (this.sendHeartbeat())
            {
                LogLevel.outputToLogFile("SceneCmdReactor.onTimeoutNotify() --- Send heartbeat packet is over! wait send....");
                return true;
            }
        }
        catch( Exception e )
        {
            LogLevel.outputToLogFile("SceneCmdReactor.onTimeoutNotify() --- Send heartbeat packet is failed! Close Channel!");
            this.closeChannel();
        }
        return false;
    }
}

        SceneCmdReactor类并不复杂,它派生自NioCmdReactor类,这个类是NIO的TCP命令处理的反应器,在应用中只需要重载以下几个方法即可。

        createNew()方法,这个方法的内容复制一下就可以了,是标准的写法,基本不需要改变,但必需被派生类重载。

        onRelease()方法,这个方法是连接被断开后都会调用的方法,可以作一些善后处理,也可以不重载。

        onReclaimCmd()方法,这个方法是在一个命令被发送到远端后,如果要监控该命令是否发送成功,可以重载该方法进行处理。这里要说明一下,因为NIO是非阻塞的方式,因此在发送数据时,是首先将要发送的内容保存在缓冲区中,然后依次发送出去,从程序的流程来看,发送命令一被执行,就会立即返回,而发送的结果则是在一段时间之后才能知道。

        onRecvOneCmd()方法,这个方法是标准的命令处理方法响应体,每一个应用都需要重载该方法,在该方法中处理规定的命令。

        下面我们看一下命令的定义类。

public class CmdHead extends NioCmdHead
{
    public CmdHead()
    {
    }

    public CmdHead( int iCmd, long lUserContext )
    {
        super( iCmd, lUserContext );
    }

    public CmdHead( int iCmd, long lUserContext, NioCmdObjectInterface obj )
    {
        super( iCmd, lUserContext, obj );
    }

    public CmdHead( int iCmd, long lUserContext, long lFlag )
    {
        super( iCmd, lUserContext, lFlag );
    }

    public CmdHead( int iCmd, long lUserContext, long lFlag, NioCmdObjectInterface obj )
    {
        super( iCmd, lUserContext, lFlag );
        this.m_objData = obj;
    }

    @Override
    protected boolean createObject()
    {
        switch ( this.m_iCmd )
        {
            case CmdDef.C_CMD_NOTIFY_BUY_TICKET:
                this.m_objData = new CmdTicket();
                return true;
            case CmdDef.C_CMD_NOTIFY_RETURN_TICKET:
                this.m_objData = new CmdTicket();
                return true;
            case CmdDef.C_CMD_NOTIFY_CHANGE_SCENE:
                this.m_objData = new CmdTicket();
                return true;
            case CmdDef.C_CMD_NOTIFY_CHECK_IN:
                this.m_objData = new CmdTicket();
                return true;
            case CmdDef.C_CMD_APPEND_ONE_FILM:
                this.m_objData = new CmdReturnFilmInfo();
                return true;
            case CmdDef.C_CMD_OPEN_ONE_MULTICAST_SOURCE_RETURN:
                this.m_objData = new CmdMergeSceneAddress();
            case CmdDef.C_CMD_REQUEST_MULTICAST_ADDRESS:
            case CmdDef.C_CMD_NOTIFY_STATUS:
                return true;
        }
        return false;
    }
}

        命令处理类实际上只需要重载createObject()方法,然后根据不同的命令ID,创建不同的命令对象就可以了。我们再看一个命令对象的定义。

public class CmdTicket implements NioCmdObjectInterface
{
    public long m_lTicketId;
    public long m_lSceneId;
    public long m_lMac = 0;

    public CmdTicket()
    {

    }

    public CmdTicket( long lTicketId, long lSceneId )
    {
        this.m_lTicketId = lTicketId;
        this.m_lSceneId = lSceneId;
    }

    public CmdTicket( long lTicketId, long lSceneId, long lMac )
    {
        this.m_lTicketId = lTicketId;
        this.m_lSceneId = lSceneId;
        this.m_lMac = lMac;
    }

    @Override
    public boolean parse(ByteBuffer byteBuffer)
    {
        try
        {
            this.m_lTicketId = byteBuffer.getLong();
            this.m_lSceneId = byteBuffer.getLong();
            this.m_lMac = byteBuffer.getLong();
            return true;
        }
        catch( Exception e )
        {
            e.printStackTrace();
            return false;
        }
    }

    @Override
    public boolean write(ByteBuffer byteBuffer)
    {
        byteBuffer.putLong( this.m_lTicketId );
        byteBuffer.putLong( this.m_lSceneId );
        byteBuffer.putLong( this.m_lMac );
        return true;
     }

    @Override
    public void release()
    {
    }

    @Override
    public void println( NioCmdHead cmd )
    {
        try
        {
            switch (cmd.m_iCmd) {
                case CmdDef.C_CMD_NOTIFY_BUY_TICKET:
                {
                    StringBuilder sb;
                    if ((sb = LogLevel.getLog(LogLevel.C_LOG_OUT_LEVEL_NIO_CMD_OUT)) != null)
                    {
                        sb.append("\r\n\tBuy Ticket Cmd --- cmdId =").append(cmd.m_iCmd)
                                .append(", FilmId = ").append(cmd.m_lUserContext)
                                .append(", TicketId = ").append(this.m_lTicketId)
                                .append(", SceneId = ").append(this.m_lSceneId)
                                .append(", Opening time = ").append(Convenient.s_dfFullDateTime.format(new Date(this.m_lMac)));
                    }
                    return;
                }
                case CmdDef.C_CMD_NOTIFY_RETURN_TICKET:
                {
                    StringBuilder sb;
                    if ((sb = LogLevel.getLog(LogLevel.C_LOG_OUT_LEVEL_NIO_CMD_OUT)) != null)
                    {
                        sb.append("\r\n\tReturn Ticket Cmd --- cmdId =").append(cmd.m_iCmd)
                                .append(", FilmId = ").append(cmd.m_lUserContext)
                                .append(", TicketId = ").append(this.m_lTicketId)
                                .append(", SceneId = ").append(this.m_lSceneId);
                    }
                    return;
                }
                case CmdDef.C_CMD_NOTIFY_CHANGE_SCENE:
                {
                    StringBuilder sb;
                    if ((sb = LogLevel.getLog(LogLevel.C_LOG_OUT_LEVEL_NIO_CMD_OUT)) != null)
                    {
                        sb.append("\r\n\tChange Scene Cmd --- cmdId =").append(cmd.m_iCmd)
                                .append(", FilmId = ").append(cmd.m_lUserContext)
                                .append(", NewSceneId = ").append(cmd.m_lFlag)
                                .append(", TicketId = ").append(this.m_lTicketId)
                                .append(", SceneId = ").append(this.m_lSceneId)
                                .append(", New scene opening time = ").append(Convenient.s_dfFullDateTime.format(new Date(this.m_lMac)));
                    }
                    return;
                }
                case CmdDef.C_CMD_NOTIFY_CHECK_IN:
                {
                    StringBuilder sb;
                    if ((sb = LogLevel.getLog(LogLevel.C_LOG_OUT_LEVEL_NIO_CMD_OUT)) != null)
                    {
                        sb.append("\r\n\tCheck in Cmd --- cmdId =").append(cmd.m_iCmd)
                                .append(", FilmId = ").append(cmd.m_lUserContext)
                                .append(", TicketId = ").append(this.m_lTicketId)
                                .append(", SceneId = ").append(this.m_lSceneId)
                                .append(", Mac = ").append(Convert.long2HexUpperCaseMacString(this.m_lMac));
                    }
                    return;
                }
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}

        不要被这么长的代码所吓到,println方法实际上是为了日志调试方便将内容输出成文本的实现。真正必需要派生的只有2个方法,一个是parse()方法,该方法用来将传送的数据解析成对象;一个是write()方法,该方法实现命令对象的序列化,将对象封装成数据包,传送到远方。这2个方法实现很简单,在write()方法中,只需要将要传送的内容依次写入缓冲区即可,在parse()方法中,只要按照write()方法中的顺序依次从缓冲区中读出数据赋值给相应的变量即可,注意写入和读出时的数据的长度必需是一样的。

        到这里,对Java中的NIO的封装的简单介绍就结束了,在后续的文章中我会从所有用到的基础类开始讲起,SelectPool线程池、Mgr管理类、Reactor反应器、纯TCP处理、基于命令行的TCP处理、配置文件的标准化、动态日志的创建和管理、消费者模型的定义、文件的NIO读写、多播等的封装模型也会依次详细介绍。

你可能感兴趣的:(java,网络)