其中主要想探讨的是一个监听连接的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类,在某些地方引用了。