其中主要想探讨的是一个监听连接的AcceptorReactor类,一个监听数据到达的SessionReactor类,一个服务器断主控类ServerManager,一个控制数据发送、接收、存储用户信息的Session类。

 

在服务器运行的时候,只有3个线程在跑,一个是main主线程,一个是监听连接的线程,一个是监听客户端数据到达的线程。当有客户端数据达时,会另开辟线程处理,处理结束后销毁该线程。

 

在使用的时候,需要自己写类继承ServerManager实现自己server的功能,需要写类继承Session实现自己的数据处理,在server.properties中配置服务器端口号、客户端数据编码、需要加载的Session类(也就是自己写的继承自Session的类)、发送接收数据时的数据分隔符。

 

其中的Reactor模式是参考网上的,具体网址已经忘记了。

 

AcceptorReactor类:

/**
 * 
 */
package zys.net.tcp;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.Iterator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import zys.ThreadRunnable;

/**
 * @author Administrator
 */
final class AcceptorReactor extends ThreadRunnable {

  private ServerManager serverManager;

  private Class<Session> sessionClass;

  private ServerSocketChannel serverSocketChannel;

  private Selector selector;

  public AcceptorReactor() {
    super();
  }

  /**
   * 
   */
  public void run() {
    try {
      serverSocketChannel = ServerSocketChannel.open();
      try {
        ServerSocket sSocket = serverSocketChannel.socket();
        try {
          sSocket.bind(new InetSocketAddress(serverManager.getPort()));
          serverSocketChannel.configureBlocking(false);
          selector = Selector.open();
          try {
            SelectionKey sk = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            sk.attach(new Acceptor());
            serverManager.logInfo("Listener Reactor started.");
            querySelector();
          } finally {
            selector.close();
          }
        } finally {
          sSocket.close();
        }
      } finally {
        serverSocketChannel.close();
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  /**
   * @param aSelector
   * @throws IOException
   */
  private void querySelector() throws IOException {
    ExecutorService pool = Executors.newFixedThreadPool(50);
    try {
      while (!Thread.interrupted()) {
        int n = selector.select();
        if (n != 0) {
          Iterator it = selector.selectedKeys().iterator();
          while (it.hasNext()) {
            SelectionKey key = (SelectionKey) (it.next());
            pool.execute((Runnable) key.attachment());
            it.remove();
          }
        }
      }
    } finally {
      pool.shutdown();
      try {
        if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
          pool.shutdownNow();
        }
      } catch (InterruptedException e) {
        pool.shutdownNow();
      }
    }
  }

  class Acceptor implements Runnable { // inner
    public void run() {
      try {
        SocketChannel c = serverSocketChannel.accept();
        if (c != null) {
          c.socket().setSoLinger(true, 0);
          serverManager.logInfo("One Session conncted.");
          Session session = sessionClass.newInstance();
          session.setManager(serverManager);
          session.setSocketChannel(c);
          session.setConnTime(new Date());
          session.setConnIP(c.socket().getInetAddress().getHostAddress());
          session.setConnStatus(Session.CONN_STATUS_CONNECT);
          serverManager.registerSession(session);
        }
      } catch (Exception ex) {
        // log
      }
    }
  }

  public ServerManager getServerManager() {
    return serverManager;
  }

  public void setServerManager(ServerManager aServerManager) {
    serverManager = aServerManager;
  }

  public Class<Session> getSessionClass() {
    return sessionClass;
  }

  public void setSessionClass(Class<Session> aSessionClass) {
    sessionClass = aSessionClass;
  }
}

 

 

其中,在run方法中注册连接监听,在querySelector中捕获连接请求,在Acceptor的run中实现对监听的处理。

 

SessionReactor类:

/**
 * 
 */
package zys.net.tcp;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import zys.ThreadRunnable;

/**
 * @author Administrator
 */
final class SessionReactor extends ThreadRunnable {
  private ServerManager serverManager;

  private Selector selector;

  private ArrayList<Session> preparedSessions;

  /**
   * 
   */
  public SessionReactor() {
    super();
    preparedSessions = new ArrayList<Session>();
  }

  /**
   * @param aSession
   * @throws IOException
   */
  public void registerSession(Session aSession) throws IOException {
    synchronized (preparedSessions) {
      preparedSessions.add(aSession);
    }
    selector.wakeup();
  }

  public void clearPreparedSessions() {
    synchronized (preparedSessions) {
      preparedSessions.clear();
    }
    selector.wakeup();
  }

  public void stop() throws Exception {
    super.stop();
    selector.wakeup();
  }

  /*
   * (non-Javadoc)
   * 
   * @see java.lang.Runnable#run()
   */
  public void run() {
    try {
      selector = Selector.open();
      try {
        serverManager.logInfo("Session Reactor started.");
        querySelector();
      } finally {
        selector.close();
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  private void querySelector() throws Exception {
    ExecutorService pool = Executors.newFixedThreadPool(200);
    try {
      int preparedCount = 0;
      while (!Thread.interrupted()) {
        synchronized (preparedSessions) {
          preparedCount = preparedSessions.size();
          if (preparedCount > 0) {
            Iterator<Session> sessionIt = preparedSessions.iterator();
            while (sessionIt.hasNext()) {
              Session session = sessionIt.next();
              SocketChannel channel = session.getSocketChannel();
              channel.configureBlocking(false);
              SelectionKey skReader = channel.register(selector, SelectionKey.OP_READ);
              skReader.attach(new Reader(session));
              serverManager.logInfo("One Session registered.");
            }
            preparedSessions.clear();
          }
        }

        int n = selector.select();
        if (n != 0) {
          Iterator it = selector.selectedKeys().iterator();
          while (it.hasNext()) {
            // dispatch((SelectionKey) (it.next()));
            SelectionKey key = (SelectionKey) (it.next());
            pool.execute((Runnable) key.attachment());
            it.remove();
          }
        }
      }

      Iterator<SelectionKey> it = selector.keys().iterator();
      while (it.hasNext()) {
        SelectionKey key = (SelectionKey) (it.next());
        Reader r = (Reader) (key.attachment());
        key.cancel();
        r.getSession().distroy();
      }
    } finally {
      pool.shutdown();
      try {
        if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
          pool.shutdownNow();
        }
      } catch (InterruptedException e) {
        pool.shutdownNow();
      }
    }
  }

  class Reader implements Runnable { // inner
    private Session session;

    public Reader(Session aSession) {
      session = aSession;
    }

    public void run() {
      if(!session.isActive()){
        return;
      }
      SocketChannel channel = session.getSocketChannel();
      int count;
      ByteBuffer buffer = null;
      try {
        synchronized(channel){
          while(true){
            buffer = ByteBuffer.allocate(10);
            count = channel.read(buffer);
            if (count > 0) {
              buffer = ByteBuffer.allocate(Integer.valueOf(new String(buffer.array(), 0, count, serverManager.getCharSet())));
              count = channel.read(buffer);
              String sMsg = new String(buffer.array(), 0, count, serverManager.getCharSet());
              serverManager.logDebugReceive(sMsg);
              session.onReceive(sMsg);
            }else{
              if(count == -1){
                if (session.isActive()) {
                  session.distroy();
                }
              }
              break;
            }
          }
        }
      } catch (Exception e) {
        e.printStackTrace();
        serverManager.logError(this.getClass(), "Reader.run" , e.getMessage());
        if (session.isActive()) {
          session.distroy();
        }
      }
    }

    public Session getSession() {
      return session;
    }
  }

  public void setServerManager(ServerManager aServerManager) {
    serverManager = aServerManager;
  }
}

 

 

其中,在registerSession中准备需要注册接收数据的Session对象,在querySelector中的synchronized (preparedSessions) {吧准备好的Session对象注册成接收数据监听,之后处理接收到数据的请求,Reader是处理接收到的数据的类。

 

所有代码已经上传,包zys.net.tcp中是核心类,包server中的是测试类,功能是客户端连接后每隔一秒向客户端发送字符串变量COST_PARAMS_STR的值,以实现客户端数据的事实刷新,发送格式是10位的代表数据长度的数字(比如0000000013),5位代表数据类别的数字(比如20000,代表每秒发的同步数据),紧接着是实际数据。
可以用telnet xxx.xxx.xxx.xxx 9999 测试。

 

所有核心代码以及测试代码已经上传,utf-8编码的。

 

比较担心的是当用户交互比较频繁的时候,服务器开辟线程过多,是否会引起服务器效率低下,不过经过测试在100个客户端的情况下,数据交互正常,丝毫看不出来延迟。比较失败的是zys.ThreadRunnable类,在某些地方引用了。