??xml version="1.0" encoding="utf-8" standalone="yes"?>
]]>
有h说java nio在多U程环境下编E简直就是个恶梦,其实你如果能把握住java nio API的要?你就可以之N.
0. 一?channal 对应一个SelectionKey in the same selector.
e.g:
SelectionKey sk=sc.register(selector, SelectionKey.OP_READ, handler);
sk==sc.register(selector, SelectionKey.OP_WRITE, handler) true?
selector.select() 每次q回的对同一channal的sk是否相同?
1.channel.register(...) may block if invoked concurrently with another registration[another.register(...)] or selection operation[selector.select(...)] involving *****the same selector*****.
q个是registerҎjdk src上的原文,
e.g:
如果一个selection thread已经在selectҎ上等待ing,那么q个时候如果有另一条线E调用channal.registerҎ的话,那么它将被blocking.
2.selectionKey.cancel() : The key will be removed from all of the selector's key sets during *****the next selection operation[selector.select(...)]*****.
may block briefly if invoked concurrently with a cancellation[cancel()] or selection operation[select(...)] involving ***the same selector***.
q个也是cancelҎjdk src上的原文,
e.g:
你先一个selectionKey.cancel(),然后随即再channel.register to the same selector,
在cancel和register之间,如果没有U程(包括当前U程)q行select操作的话,
那么 throws java.nio.channels.CancelledKeyException.
所?nbsp;cancel-->select-->re-register.
3.if don't remove the current selectedKey from selector.selectedKeys()[Set] 导?selector.select(...) not block [may be cpu 100%,specially when client cut the current channel(connection)].
e.g:
Iterator<SelectionKey> it=selector.selectedKeys().iterator();
...for/while it.hasNext()...
it.remove();<------*****must do it. or Keys' Set.clear() finally;
if remove the current selectedKey from selector.selectedKeys()[Set] but don't sk.interestOps(sk.interestOps()& (~sk.readyOps()));导?selector.select(...) not block [select() not block several times, or excepted exception]
4.op_write should not be registered to the selector. [may be cpu100%]
5. if involving wakeup() before select() [wakeup called several times >=1],the next select() not block [not block just once].
管以前有些人分析了nio的wakeup性能及not block in linux的bug,但是java nio依然是高效的,那些c/c++的牛Zȝ看jre/bin目录下的nio.dll/nio.so?java nio是基于select模型(q个是c/c++中常用网l编E模型之一)?
Zjava nio的服务器:mina,girzzly[glassfish],jetty(Zgirzzly),tomcat6[可以配置Http11NioProtocol]...
其中从本人对girzzly,tomcat6的源码分析来?它们都还没有真正发挥出nio异步处理h的优?它们的读写还都是blocking的虽然用了selectorPool,此外tomcat6要剥dsocket通信q要p一定的功夫.?strong>mina却是?font class="" style="font-family: " color="#ff0000">?/font>W其?/strong>,q有bug?
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.Iterator; import java.util.LinkedList; import java.util.Set; import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.channels.ServerSocketChannel; import java.nio.channels.Selector; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; public class MyNioServer { private int BUFFERSIZE = 1024*10; private String CHARSET = "GBK"; private Selector sel; public MyNioServer(int port) throws IOException { ServerSocketChannel ssc = ServerSocketChannel.open(); ssc.configureBlocking(false); ssc.socket().bind( new InetSocketAddress(InetAddress.getLocalHost(), port)); sel = Selector.open(); ssc.register(sel, SelectionKey.OP_ACCEPT); } public void startup() { System.out.println("Server start..."); try { while (!Thread.interrupted()) { int keysCount = sel.select(); System.out.println("Catched " + keysCount + " SelectionKeys"); if (keysCount < 1) { continue; } Set<SelectionKey> set = sel.selectedKeys(); Iterator<SelectionKey> it = set.iterator(); while (it.hasNext()) { SelectionKey key = it.next(); if (key.isAcceptable()) { System.out.println("Key isAcceptable"); doAccept(key); } if (key.isValid() && key.isReadable()) { System.out.println("Key isReadable"); doRead(key); } if (key.isValid() && key.isWritable()) { System.out.println("Key isWritable"); doWrite(key); } } set.clear(); } System.err.println("Program is interrupted."); } catch (IOException e) { e.printStackTrace(); } System.out.println("Server stop..."); shutdown(); } public void shutdown(){ Set<SelectionKey> keys = sel.keys(); for(SelectionKey key:keys){ try { key.channel().close(); } catch (IOException e) { e.printStackTrace(); } } try { sel.close(); } catch (IOException e) { e.printStackTrace(); } } private void doAccept(SelectionKey key) { try { SocketChannel sc = ((ServerSocketChannel) key.channel()).accept(); sc.configureBlocking(false); SelectionKey newkey = sc.register(sel, SelectionKey.OP_READ); newkey.attach(new LinkedList<ByteBuffer>()); new Thread(new UserInteractive(newkey)).start(); } catch (IOException e) { e.printStackTrace(); System.err.println("Failed to accept new client."); } System.out.println("end doAccept"); } // TODO buffersize performance testing private void doRead(SelectionKey key) { try { SocketChannel sc = (SocketChannel) key.channel(); ByteBuffer bb = ByteBuffer.allocate(BUFFERSIZE); StringBuffer sb = new StringBuffer(); int count = 0; while ( (count = sc.read(bb)) > 0) { bb.flip(); sb.append(Charset.forName(CHARSET).decode(bb)); bb.flip(); } //if client disconnected, read return -1 if(count == -1){ System.out.println("client disconnected"); disconnect(key); } else { System.out.println("message received from client:" + sb.toString()); } } catch (IOException e) { disconnect(key); e.printStackTrace(); } System.out.println("end doRead"); } private void doWrite(SelectionKey key) { SocketChannel sc = (SocketChannel) key.channel(); LinkedList<ByteBuffer> outseq = (LinkedList<ByteBuffer>) key .attachment(); ByteBuffer bb = outseq.poll(); if(bb == null){ return; } try { while(bb.hasRemaining()){ sc.write(bb); } } catch (IOException e) { disconnect(key); e.printStackTrace(); } if (outseq.size() == 0) { System.out.println("after all buffers wrote, unregister OP_WRITE from interestOps"); key.interestOps(SelectionKey.OP_READ); } System.out.println("end doWrote"); } private void disconnect(SelectionKey key) { try { key.channel().close(); } catch (IOException e) { e.printStackTrace(); } } //TODO find out how to shutdown private class UserInteractive implements Runnable { SelectionKey key; public UserInteractive(SelectionKey key) { this.key = key; } public void run() { System.out.println("UserInteractive thread start..."); BufferedReader br = new BufferedReader(new InputStreamReader( System.in)); while (true) { try { String inputLine = br.readLine(); ByteBuffer bb = ByteBuffer.allocate(BUFFERSIZE); bb = ByteBuffer.wrap(inputLine.getBytes()); ((LinkedList<ByteBuffer>) key.attachment()).offer(bb); System.out .println("after input, register OP_WRITE to interestOps and wakeup selector"); key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE); key.selector().wakeup(); } catch (IOException e) { e.printStackTrace(); } } } } /** * @param args */ public static void main(String[] args) { try { MyNioServer server = new MyNioServer(10001); server.startup(); } catch (Exception e) { e.printStackTrace(); System.err.println("Exception caught, program exiting…"); } } } |
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.nio.channels.SelectionKey; import java.text.MessageFormat; import java.util.LinkedList; import java.util.Set; import java.util.Iterator; public class MyNioClient { private int BUFFERSIZE = 1024*10; private String CHARSET = "GBK"; private Selector sel; public MyNioClient(int port) throws IOException { SocketChannel sc = SocketChannel.open(); sc.configureBlocking(false); // this operation need to be executed before socket.connnect, for OP_CONNECT event sc.connect(new InetSocketAddress(InetAddress.getLocalHost(), port)); sel = Selector.open(); sc.register(sel, SelectionKey.OP_CONNECT |SelectionKey.OP_READ); } public void startup() { System.out.println("Client start..."); try { while (!Thread.interrupted()) { int keysCount = sel.select(); System.out.println("Catched " + keysCount + " SelectionKeys"); if (keysCount < 1) { continue; } Set<SelectionKey> selectedKeys = sel.selectedKeys(); Iterator<SelectionKey> it = selectedKeys.iterator(); while (it.hasNext()) { SelectionKey key = it.next(); //printKeyInfo(key); if (key.isConnectable()) { System.out.println("Key isConnectable"); doConnect(key); } else if (key.isValid() && key.isReadable()) { System.out.println("Key isReadable"); doRead(key); } else if (key.isValid() && key.isWritable()) { System.out.println("Key isWritable"); doWrite(key); } } selectedKeys.clear(); } System.err.println("Program is interrupted."); } catch (IOException e) { e.printStackTrace(); } System.out.println("Client stop..."); shutdown(); } public void shutdown(){ Set<SelectionKey> keys = sel.keys(); for(SelectionKey key:keys){ try { key.channel().close(); } catch (IOException e) { e.printStackTrace(); } } try { sel.close(); } catch (IOException e) { e.printStackTrace(); } } private void printKeyInfo(SelectionKey key) { String keyStr = MessageFormat .format( "IntOps:{0},ReadyOps:{1},isVal:{2},isAcc:{3},isCnn:{4},isRead:{5},isWrite:{6}", key.interestOps(), key.readyOps(), key.isValid(), key .isAcceptable(), key.isConnectable(), key .isReadable(), key.isWritable()); System.out.println(keyStr); } private void doConnect(SelectionKey key) { try { boolean flag = ((SocketChannel) key.channel()).finishConnect(); } catch (IOException e) { e.printStackTrace(); System.exit(1); } System.out.println("unregister OP_CONNECT from interestOps"); key.interestOps(SelectionKey.OP_READ); key.attach(new LinkedList<ByteBuffer>()); new Thread(new UserInteractive(key)).start(); } private void doRead(SelectionKey key) { try { SocketChannel sc = (SocketChannel) key.channel(); ByteBuffer bb = ByteBuffer.allocate(BUFFERSIZE); StringBuffer sb = new StringBuffer(); while (sc.read(bb) > 0) { bb.flip(); sb.append(Charset.forName(CHARSET).decode(bb)); bb.flip(); } System.out.println("message received from server:" + sb.toString()); } catch (IOException e) { e.printStackTrace(); disconnect(key); System.exit(1); } System.out.println("now end readMessage"); } private void doWrite(SelectionKey key) { SocketChannel sc = (SocketChannel) key.channel(); LinkedList<ByteBuffer> outseq = (LinkedList<ByteBuffer>) key .attachment(); ByteBuffer bb = outseq.poll(); if(bb == null){ return; } try { while(bb.hasRemaining()){ sc.write(bb); } } catch (IOException e) { disconnect(key); e.printStackTrace(); } if (outseq.size() == 0) { System.out.println("after all buffers wrote, unregister OP_WRITE from interestOps"); key.interestOps(SelectionKey.OP_READ); } System.out.println("end doWrote"); } private void disconnect(SelectionKey key) { try { key.channel().close(); } catch (IOException e) { e.printStackTrace(); } } private class UserInteractive implements Runnable { SelectionKey key; public UserInteractive(SelectionKey key) { this.key = key; } public void run() { LinkedList<ByteBuffer> outseq = (LinkedList<ByteBuffer>) key .attachment(); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); while (true) { try { String inputLine = br.readLine(); if ("quit".equalsIgnoreCase(inputLine)) { key.channel().close(); System.exit(1); break; } ByteBuffer bb = ByteBuffer.allocate(BUFFERSIZE); bb = ByteBuffer.wrap(inputLine.getBytes()); outseq.offer(bb); System.out .println("after input, register OP_WRITE to interestOps and wakeup selector"); key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE); sel.wakeup(); } catch (IOException e) { e.printStackTrace(); } } } } /** * @param args */ public static void main(String[] args) { try { MyNioClient client = new MyNioClient(10001); client.startup(); } catch (Exception e) { e.printStackTrace(); System.err.println("Exception caught, program exiting..."); } } } |
一?span style="font: 7pt 'Times New Roman'">
|络|络游戏Q首先面临的问题当然是如何进行网l通信。首先考虑的是HTTP协议Q因为所有的J2ME手机都支持这个,我们当然惛_可能的兼容用戗而且HTTP协议装E度已经非常高了Q不用去考虑U程、同步、状态管理、连接池Q不qHTTP协议有两个不爽的地方Q?/span>
?nbsp;协议无状态,q个问题已经困扰q很多h很多ơ了。我曾考虑q的解决办法是改造HTTP协议Q在数据传输完成之后不关闭socketQ但是这样做工作量非常大Q在目周期中,基本上就是Mission impossibleQ不予考虑。那么客户也只能通过轮询的方式向服务器请求数据?/span>
?nbsp;|络量q大。就q个目来说Q网l间传递的只是指oQ但是每ơ传递都要加上一堆毫无用处的HTTP HeadQ再加上客户端需要做轮询Q这个流量对于手机来说简直恐怖,l简单测试,按照0.03?K的GPRS|络费用计算Q一局牌居然要消?元多的费用(每秒轮询Q,实在不可接受。也许我们可以采用流量费包月的资Ҏ式,不过q个话题与技术无兟?/span>
以上问题D我们选择了SocketQ这意味着我们没有一?a target="_blank">Web环境Q很多东襉K要靠自己d玎ͼU程理、客L态监控、对象池、控制台……….|络部分打算采用Java NIO。每个客Lq接之后Q会有一个独立的
二?span style="font: 7pt 'Times New Roman'"> 通信协议
q个目q没有复杂的通信指oQ命令数量很有限Q但是还是有个关键问题需要关注:量。ؓ了尽量减流量,我们使用字节代替字符串来保存pȝ指oQ这样可以ɋ量减少一半,比如使用一个字节来保存一张扑克牌Q字节高位表CԌ字节低位表示数字Q如?代表黑桃Q那么黑桃三应该是0x03Q这个需要靠位操作来实现Q?/span>
int m=0;
int n=3;
byte card=(byte)(m)<<4)|((byte)n; //m左移四位Q然后与n左或操作
游戏中需要传递用LU分Q这是一个大整数Q用四个字节来保存比较保险Q将整数转换为四个字节的操作如下Q?/span>
四个字节{回来的操作如下:
三?span style="font: 7pt 'Times New Roman'"> 数据?/a>q接?/span>
׃没有一个web环境Q所以我们需要自己实C个数据库q接池,apache有一个项目叫做commons DBCPQ这是一个基于apache自己的对象池Qapache commons poolQ实现的数据库连接池Q我们可以直接拿来用,apache的Y件未必是最好的Q但是极大可能比我们自己写的要好。Commons DBCP需要三?jarQcommons-collections-3.1.jar、commons-dbcp-1.2.1.jar、commons-pool-1.2.jarq三个文仉可以在apache – Jakarta – commons目?a target="_blank">下蝲Q加入到工程中即可。构造一个数据库q接池的代码如下Q?/span>
误行处理操作中的各U异常?/span>
四?span style="font: 7pt 'Times New Roman'"> 扑克牌的生成
游戏中需要ؓ用户生成随机的扑克牌Q首先我们需要初始化一副牌Q放C个Hashmap中,每张牌以一个字节表C,高ؓ代表pQ的Z表数字,生成整副牌:
如何随机地得到其中的N张牌呢?我们的做法是生成一?-55的随机数Q用q个随机C主键从Hashmap中获得对象,取得之后Q把该对象从队列中删除,以免重复取得。由于java中的随机数是Ҏ旉生成的,所以有可能D用户得到的牌不够散,每个用户都摸C条龙岂不是笑话?所以在生成随机数的时候我们加入了一个大素数来作q算Q?/span>
long cardId=new Long((Math.round(Math.random() * 87) % 55)).intValue();通过修改q个大素敎ͼ可以控制某个用户的牌比较好?/span>
五?span style="font: 7pt 'Times New Roman'"> U程
实际上本pȝq没有复杂的U程理Q但是我x供一个控制台让管理员可以理游戏ȝE,可以让它停止、中Dc恢复、重启动Q本来的设计是管理员通过与线EA打交道,通过Aȝ理主U程BQ但是熟悉javaU程的朋友都知道Q线E互相管理基本上是不实际的QD个最单的例子QA如何销毁BQ也怽会说调用B的destroy()Ҏ好了,|上很多讲解javaU程的资料也实是这么说的,但是他们都是鬼扯的,自己ȝ看java源代码吧QThread.destroy()Ҏ的实际代码如下:
public void destroy()
{
throw new NoSuchMethodError();
}
事实真相是,Thread.destroy()Ҏ自始至终没有被实现q。所有写文章Q教别h用这个方法销毁线E的人,都去撞墙吧,丢h丢大了。最好的办法是A负责生成一个Bq且启动它,然后B自己理生存周期QA和B通过使用可共享的Ҏ来通信Q这是sun推荐的做法?/span>
六?span style="font: 7pt 'Times New Roman'"> 异步消息
用户玩牌的过E中Q有很多东西需要记录下来,比如记录用户的积分、等U变化,记录玩牌日志供数据统计等Q当用户数量很多的时候,在数据库中记录这些信息会很耗费资源Q用L了一局之后会可能会{待很长旉。解册个问题的Ҏ是利?a target="_blank">J2EE的消息bean来提供异步通信的机Ӟ需要记录数据的时候,pȝ会封装一个值对象,发送给J2EE容器Q这个操作是很快的,完成之后p回,用户可以l箋操作Q不用关心消息何时被处理。J2EE的消息框架具备如下特征:
◇消息一定会被阅读,而且只阅Mơ。JMS框架有自q法Q把消息~冲?a target="_blank">盘Q就J2EE服务器死掉,消息也不会丢失?/span>
◇系l采用点对点的Queue消息队列Q可以保证同{优先的消息先q先出?/span>
在Jboss 4.0中,部v消息Bean和Queue队列Q都比weblogic 8.1来的ҎQ只需要在jboss.XML中声明消息目的地Q如果jboss发现该目的地不存在的话,会自动徏立一个,实在很简单。关于消息bean的开发与部vQ我有专门的文章描述?/span>
七?span style="font: 7pt 'Times New Roman'"> 启动与退?/span>
Z让系l具备让人满意的性能Q应该尽量多的重用对象,减少创徏新对象。比如上面提到的消息发送,我们的操作是提供一个静态类Q在pȝ启动的时候就初始化,保持与JMS服务器的q接Q系l发送消息的时候,不用再去查询JNDI和生成QueueConnectionFactoryQ这样可以提高系l响应速度?/span>
在数据库q接池的问题上,我们也采用同L操作Q启动的时候初始化N个连接。但是如果在关闭q程的时候不做Q何操作,会导致JMS抛出socket异常Q虽然没什么大的媄响,但L得不专业Q而且池中的连接不被释攄话,也可能导致问题。最好能够让pȝ像jboss{控制台E序一Pctrl+c之后能够执行操作Q释放资源再退出。我们可以通过l进E?U程加上一个Hook来实玎ͼWindowsE序员应该对q个非常熟悉?/span>
Hook应该是一个线E方法,如下Q?/span>
public class Hook extends Thread
{
public void run()
{
//释放数据库连接,销毁连接池
//关闭与JMS的连?/span>
}
}
在主U程中加入:Runtime.getRuntime().addShutdownHook(new Hook()) ;那么q程/U程会在退出的时候执行Hook的runҎQ清理资源?/span>