|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
java比较简单,没有C++的烦琐,但学习时最好有C++为基础.与JSP和SQL起应用,功能强大.编程|服务器|收集
从Java1.4入手下手供应的NIOAPI经常使用于开辟高功能收集服务器,本文演示了怎样用这个API开辟一个TCPEchoServer。
Java收集服务器编程一文演示了怎样利用Java的SocketAPI编写一个复杂的TCPEchoServer。其堵塞式IO的处置体例固然复杂,但每一个客户端都必要一个独自的Thread来处置,当服务器必要同时处置大批客户端时,这类做法不再可行。利用NIOAPI可让一个或无限的几个Thread同时处置毗连到服务器上的一切客户端。(关于NIOAPI的一些先容,能够在JavaNIOAPI详解一文中找到。)
NIOAPI同意一个线程经由过程Selector对象同时监控多个SelectableChannel来处置多路IO,NIO使用程序一样平常按下图所示事情:
Figure1
如Figure1所示,Client一向在轮回地举行select操纵,每次select()前往今后,经由过程selectedKeys()能够失掉必要处置的SelectableChannel并对其逐一处置。
如许做固然复杂但也有个成绩,当有分歧范例的SelectableChannel必要做分歧的IO处置时,在图中Client的代码就必要判别channel的范例然后再作响应的操纵,这常常意味着连续串的ifelse。更糟的是,每增添一种新的channel,不仅必要增添响应的处置代码,还必要对这一串ifelse举行保护。(在本文的这个例子中,我们有ServerSocketChannel和SocketChannel这两种channel必要分离被处置。)
假如思索将channel及其必要的IO处置举行封装,笼统出一个一致的接口,就能够办理这一成绩。在Listing1中的NioSession就是这个接口。
NioSession的channel()办法前往其封装的SelectableChannel对象,interestOps()前往用于这个channel注册的interestOps。registered()是当SelectableChannel被注册后挪用的回调函数,经由过程这个回调函数,NioSession能够失掉channel注册后的SelectionKey。process()函数则是NioSession接口的中心,这个办法笼统了封装的SelectableChannel所需的IO处置逻辑。
Listing1:
publicinterfaceNioSession{
publicSelectableChannelchannel();
publicintinterestOps();
publicvoidregistered(SelectionKeykey);
publicvoidprocess();
}
和NioSession一同事情的是NioWorker这个类(Listing2),它是NioSession的挪用者,封装了一个Selector对象和Figure1中轮回select操纵的逻辑。了解这个类能够匡助我们懂得该怎样利用NioSession这个接口。
NioWorker完成了Runnable接口,轮回select操纵的逻辑就在run()办法中。在NioWorker–NioSession这个框架中,NioSession在channel注册的时分会被作为attachment送进register函数,如许,在每次select()操纵的轮回中,关于selectedKeys()中的每个SelectionKey,我们都能够经由过程attachment拿到其绝对应的NioSession然后挪用其process()办法。
每次select()轮回另有一个义务,就是将经由过程add()办法到场到这个NioWorker的NioSession注册到Selector上。在Listing2的代码中能够看出,NioSession中的channel()被掏出并注册在Selector上,注册所需的interestOps从NioSession中掏出,NioSession自己则作为attachment送进register()函数。注册乐成后,NioSession的registered()回调函数会被挪用。
NioWorker的add()办法的感化是将一个NioSession到场到该NioWorker中,并wakeup以后的select操纵,如许鄙人一次的select()挪用之前,这个NioSession会被注册。stop()办法则是让一个正在run()的NioWorker中断。closeAllChannels()会封闭以后注册的一切channel,这个办法可在NioWorker不再利用时用来开释IO资本。
Listing2:
publicclassNioWorkerimplementsRunnable{
publicNioWorker(Selectorsel){
_sel=sel;
_added=newHashSet();
}
publicvoidrun(){
try{
try{
while(_run){
_sel.select();
Setselected=_sel.selectedKeys();
for(Iteratoritr=selected.iterator();itr.hasNext();){
SelectionKeykey=(SelectionKey)itr.next();
NioSessions=(NioSession)key.attachment();
s.process();
itr.remove();
}
synchronized(_added){
for(Iteratoritr=_added.iterator();itr.hasNext();){
NioSessions=(NioSession)itr.next();
SelectionKeykey=s.channel().register(_sel,s.interestOps(),s);
s.registered(key);
itr.remove();
}
}
}
}finally{
_sel.close();
}
}catch(IOExceptionex){
thrownewError(ex);
}
}
publicvoidadd(NioSessions){
synchronized(_added){
_added.add(s);
}
_sel.wakeup();
}
publicsynchronizedvoidstop(){
_run=false;
_sel.wakeup();
}
publicvoidcloseAllChannels(){
for(Iteratoritr=_sel.keys().iterator();itr.hasNext();){
SelectionKeykey=(SelectionKey)itr.next();
try{
key.channel().close();
}catch(IOExceptionex){}
}
}
protectedSelector_sel=null;
protectedCollection_added=null;
protectedvolatileboolean_run=true;
}
在EchoServer这个例子中,我们必要一个ServerSocketChannel来承受新的TCP毗连,关于每一个TCP毗连,我们还必要一个SocketChannel来处置这个TCP毗连上的IO操纵。把这两种channel和下面的NioWorker–NioSession布局整合在一同,能够失掉NioServerSession和NioEchoSession这两个类,它们分离封装了ServerSocketChannel和SocketChannel及其对应的IO操纵。上面这个UML类图形貌了这4个类的干系:
Figure2
能够看到NioWorker和NioSession对新到场的两个类没有任何依附性,NioServerSession和NioEchoSession经由过程完成NioSession这个接口为体系到场了新的功效。如许的一个别系架构切合了Open-Close准绳,新的功效能够经由过程完成NioSession被到场而无需对原本的模块举行修正,这表现了面向对象计划的壮大能力。
NioServerSession的完成(Listing3)绝对对照复杂,其封装了一个ServerSocketChannel和从这个channel上承受新的TCP毗连的逻辑。NioServerSession还必要一个NioWorker的援用,如许每承受一个新的TCP毗连,NioServerSession就为其创立一个NioEchoSession的对象,并将这个对象到场到NioWorker中。
Listing3:
publicclassNioServerSessionimplementsNioSession{
publicNioServerSession(ServerSocketChannelchannel,NioWorkerworker){
_channel=channel;
_worker=worker;
}
publicvoidregistered(SelectionKeykey){}
publicvoidprocess(){
try{
SocketChannelc=_channel.accept();
if(c!=null){
c.configureBlocking(false);
NioEchoSessions=newNioEchoSession(c);
_worker.add(s);
}
}catch(IOExceptionex){
thrownewError(ex);
}
}
publicSelectableChannelchannel(){
return_channel;
}
publicintinterestOps(){
returnSelectionKey.OP_ACCEPT;
}
protectedServerSocketChannel_channel;
protectedNioWorker_worker;
}
NioEchoSession的举动要庞大一些,NioEchoSession会先从TCP毗连中读取数据,再将这些数据用统一个毗连写归去,偏重复这个步骤直到客户端把毗连封闭为止。我们能够把“Reading”和“Writing”看做NioEchoSession的两个形态,如许能够用一个无限形态机来形貌它的举动,以下图所示:
Figure3
接上去的事情就是怎样完成这个无限形态机了。在这个例子中,我们利用State形式来完成它。上面这张UML类图形貌了NioEchoSession的计划细节。
Figure4
NioEchoSession所处的形态由EchoState这个笼统类来体现,其两个子类分离对应了“Reading”和“Writing”这两个形态。NioEchoSession会将process()和interestOps()这两个办法delegate给EchoState来处置,如许,当NioEchoSession处于分歧的形态时,就会有分歧的举动。
Listing4是EchoState的完成。EchoState界说了process()和interestOps()这两个笼统的办法来让子类完成。NioEchoSession中的process()办法会被delegate到其以后EchoState的process()办法,NioEchoSession自己也会作为一个形貌context的参数被送进EchoState的process()办法中。EchoState界说的interestOps()办法则会在NioEchoSession注册和变化State的时分被用到。
EchoState还界说了两个静态的办法来前往事后创立好的ReadState和WriteState,如许做的优点是能够制止在NioEchoSession转换state的时分创立一些不用要的对象从而影响功能。但是,如许做请求state类必需是无形态的,形态必要保留在context类,也就是NioEchoSession中。
Listing4:
publicabstractclassEchoState{
publicabstractvoidprocess(NioEchoSessions)throwsIOException;
publicabstractintinterestOps();
publicstaticEchoStatereadState(){
return_read;
}
publicstaticEchoStatewriteState(){
return_write;
}
protectedstaticEchoState_read=newReadState();
protectedstaticEchoState_write=newWriteState();
}
Listing5是NioEchoSession的完成。NioEchoSession包括有一个SocketChannel,这个channel注册后失掉的SelectionKey,一个用于寄存数据的ByteBuffer和一个纪录以后state的EchoState对象。在初始化时,EchoState被初始化为一个ReadState。NioEchoSession把process()办法和interestOps()办法都delegate到以后的EchoState中。其setState()办法用于切换以后state,在切换state后,NioEchoSession会经由过程SelectionKey更新注册的interestOps。close()办法用于封闭这个NioEchoSession对象。
Listing5:
publicclassNioEchoSessionimplementsNioSession{
publicNioEchoSession(SocketChannelc){
_channel=c;
_buf=ByteBuffer.allocate(128);
_state=EchoState.readState();
}
publicvoidregistered(SelectionKeykey){
_key=key;
}
publicvoidprocess(){
try{
_state.process(this);
}catch(IOExceptionex){
close();
thrownewError(ex);
}
}
publicSelectableChannelchannel(){
return_channel;
}
publicintinterestOps(){
return_state.interestOps();
}
publicvoidsetState(EchoStatestate){
_state=state;
_key.interestOps(interestOps());
}
publicvoidclose(){
try{
_channel.close();
}catch(IOExceptionex){
thrownewError(ex);
}
}
protectedSocketChannel_channel=null;
protectedSelectionKey_key;
protectedByteBuffer_buf=null;
protectedEchoState_state=null;
}
Listing6和Listing7分离是ReadState和WriteState的完成。ReadState在process()中会先从NioEchoSession的channel中读取数据,假如未能读到数据,NioEchoSession会持续留在ReadState;假如读掏出错,NioEchoSession会被封闭;假如读取乐成,NioEchoSession会被切换到WriteState。WriteState则卖力将NioEchoSession中已读取的数据写回到channel中,全体写完后,NioEchoSession会被切换回ReadState。
Listing6:
publicclassReadStateextendsEchoState{
publicvoidprocess(NioEchoSessions)
throwsIOException
{
SocketChannelchannel=s._channel;
ByteBufferbuf=s._buf;
intcount=channel.read(buf);
if(count==0){
return;
}
if(count==-1){
s.close();
return;
}
buf.flip();
s.setState(EchoState.writeState());
}
publicintinterestOps(){
returnSelectionKey.OP_READ;
}
}
Listing7:
publicclassWriteStateextendsEchoState{
publicvoidprocess(NioEchoSessions)
throwsIOException
{
SocketChannelchannel=s._channel;
ByteBufferbuf=s._buf;
channel.write(buf);
if(buf.remaining()==0){
buf.clear();
s.setState(EchoState.readState());
}
}
publicintinterestOps(){
returnSelectionKey.OP_WRITE;
}
}
NioEchoServer(Listing8)被用来启动和封闭一个TCPEchoServer,这个类完成了Runnable接口,挪用其run()办法就启动了EchoServer。其shutdown()办法被用来封闭这个EchoServer,注重shutdown()和run()的finallyblock中的同步代码确保了只要当EchoServer被封闭后,shutdown()办法才会前往。
Listing8:
publicclassNioEchoServerimplementsRunnable{
publicvoidrun(){
try{
ServerSocketChannelserv=ServerSocketChannel.open();
try{
serv.socket().bind(newInetSocketAddress(7));
serv.configureBlocking(false);
_worker=newNioWorker(Selector.open());
NioServerSessions=newNioServerSession(serv,_worker);
_worker.add(s);
_worker.run();
}finally{
_worker.closeAllChannels();
synchronized(this){
notify();
}
}
}catch(IOExceptionex){
thrownewError(ex);
}
}
publicsynchronizedvoidshutdown(){
_worker.stop();
try{
wait();
}catch(InterruptedExceptionex){
thrownewError(ex);
}
}
protectedNioWorker_worker=null;
}
最初,经由过程一个复杂的main()函数(Listing9),我们就能够运转这个EchoServer了。
Listing9:
publicstaticvoidmain(String[]args){
newNioEchoServer().run();
}
我们能够经由过程telnet程序来查验这个程序的运转情况:
1.翻开一个命令行,输出telnetlocalhost7来运转一个telnet程序并毗连到EchoServer上。
2.在telnet程序中输出字符,能够看到输出的字符被显现在屏幕上。(这是由于EchoServer将收到的字符写回到客户端)
3.多翻开几个telnet程序举行测试,能够看到EchoServer能经由过程NIOAPI用一个Thread服务多个客户端。
DaiJiaLin
mailto:woodydai@gmail.com
http://blog.csdn.net/DaiJiaLin
J2ME在手机游戏开发的作用也是无用质疑的。至于桌面程序,可能有人说java不行,界面不好看,但是请看看NetBeans和Eclipse吧,他们都是利用java开发的,而他们的界面是多么的华丽,所以界面决不是java的缺点。还有一个不得不提的优点就是大多java人员都挂在嘴边的java的跨平台性,目前这确实也是java优点之一。 |
|