对相关类的简单介绍
java.nio.*, 据说它提供了一些更加底层的一些功能,如:类似windows环境下的AsyncSocket类的异步操作的功能,能显著降低server端程序的线程管理开销。
因为大多数应用是建立在TCP之上,所以在此只说说SocketChannel,ServerSocketChannel,Selector和ByteBuffer这几个类.前三个最终都源自channel类。而channel 类,可以理解为在具体I/O或文件对象之上抽象的一个操作对象,我们通过操作channel的读写达到对其对应的文件或I/O对象(包括socket)读写的目的。读写的内容在内存中放在ByteBuffer类提供的缓冲区。总而言之,channel作为一个桥梁,连接了I/O对象和内存中的ByteBuffer,实现了I/O的更高效的存取。
一个基于TCP的服务器端程序,必然有个侦听端和若干个通信端,它们在nio中由对应的ServerSocketChannel 和SocketChannel类来实现。为了达到异步I/O操作的目的,需要Selector类,它能检测到I/O对象的状态。
SocketChannel类是抽象类,通过调用它的静态函数open(),可生成一个SocketChannel对象,该对象对应一个java.net.Socket,可通过SocketChannel.socket()获得,而其对应的Socket也可通过调用函数getChannel()得到已建立的相应SocketChannel。
SocketChannel与它的socket是一一对应的。SocketChannel的操作与Socket也很相似.
ServerSocketChannel也是通过调用它的静态函数open()生成的,只是它不能直接调用bind()函数来绑定一个地址,需要它对应的ServerSocket来完成绑定工作,一般可按如下步骤做:
ServerSocketChannel ssc = new ServerSocketChannel.open();
ssc.socket().bind(InetSocketAddress(host,port));
罗嗦了半天,还是看看最简单的C/S实现吧,服务器提供了基本的回射(echo)功能,其中提供了较详细的注释。
源码分析
1.服务器端:
//AsyncServer.java
//by zztudou@163.com
importjava.nio.channels.SocketChannel;
importjava.nio.channels.ServerSocketChannel;
importjava.nio.channels.SelectionKey;
importjava.nio.channels.Selector;
importjava.nio.ByteBuffer;
importjava.nio.channels.SelectableChannel;
importjava.nio.channels.spi.SelectorProvider;
importjava.net.ServerSocket;
importjava.net.Socket;
importjava.net.InetSocketAddress;
importjava.net.SocketAddress;
importjava.util.Iterator;
importjava.util.LinkedList;
importjava.io.IOException;
classAsyncServerimplementsRunnable{
privateByteBuffer r_buff=ByteBuffer.allocate(1024);
privateByteBuffer w_buff=ByteBuffer.allocate(1024);
privatestaticintport=8848;
publicAsyncServer(){
newThread(this).start();
}
publicvoidrun(){
try{
//生成一个侦听端ServerSocketChannel ssc=ServerSocketChannel.open();
//将侦听端设为异步方式ssc.configureBlocking(false);
//生成一个信号监视器Selector s=Selector.open();
//侦听端绑定到一个端口ssc.socket().bind(newInetSocketAddress(port));
//设置侦听端所选的异步信号OP_ACCEPTssc.register(s,SelectionKey.OP_ACCEPT);
System.out.println("echo server has been set up
");
while(true){
intn=s.select();
if(n==0){//没有指定的I/O事件发生continue;
} Iterator it=s.selectedKeys().iterator();
while(it.hasNext()){
SelectionKey key=(SelectionKey) it.next();
if(key.isAcceptable()){//侦听端信号触发ServerSocketChannel server=(ServerSocketChannel) key.channel();
//接受一个新的连接SocketChannel sc=server.accept();
sc.configureBlocking(false);
//设置该socket的异步信号OP_READ:当socket可读时,
//触发函数DealwithData();sc.register(s,SelectionKey.OP_READ);
}
if(key.isReadable()){//某socket可读信号DealwithData(key);
} it.remove();
} } }
catch(Exception e){
e.printStackTrace();
} }
publicvoidDealwithData(SelectionKey key)throwsIOException{
intcount;
//由key获取指定socketchannel的引用SocketChannel sc=(SocketChannel)key.channel();
r_buff.clear();
//读取数据到r_buffwhile((count=sc.read(r_buff))>0)
;
//确保r_buff可读r_buff.flip();
w_buff.clear();
//将r_buff内容拷入w_buffw_buff.put(r_buff);
w_buff.flip();
//将数据返回给客户端EchoToClient(sc);
w_buff.clear();
r_buff.clear();
}
publicvoidEchoToClient(SocketChannel sc)throwsIOException{
while(w_buff.hasRemaining())
sc.write(w_buff);
}
publicstaticvoidmain(String args[]){
if(args.length>0){
port=Integer.parseInt(args[0]);
}newAsyncServer();
}}
在当前目录下运行:
javac AsynServer.java
后,若无编译出错,接下来可运行:
java AsynServer 或 java AsynServer ×××(端口号)
上述服务程序在运行时,可指定其侦听端口,否则程序会取8848为默认端口。
2.客户端的简单示例:
//AsyncClient.java
//by zztudou@163.com
importjava.nio.channels.SocketChannel;
importjava.net.InetSocketAddress;
importjava.nio.ByteBuffer;
importjava.nio.channels.Selector;
importjava.nio.channels.SelectionKey;
importjava.io.IOException;
importjava.io.BufferedReader;
importjava.io.InputStreamReader;
classAsyncClient{
privateSocketChannel sc;
privatefinalintMAX_LENGTH=1024;
privateByteBuffer r_buff=ByteBuffer.allocate(MAX_LENGTH);
privateByteBuffer w_buff=ByteBuffer.allocate(MAX_LENGTH);
privatestaticString host ;
privatestaticintport=8848;
publicAsyncClient(){
try{
InetSocketAddress addr=newInetSocketAddress(host,port);
//生成一个socketchannelsc=SocketChannel.open();
//连接到serversc.connect(addr);
while(!sc.finishConnect())
;
System.out.println("connection has been established!
");
while(true){
//回射消息String echo;
try{
System.err.println("Enter msg you'd like to send:");
BufferedReader br=newBufferedReader(newInputStreamReader(System.in));
//输入回射消息echo=br.readLine();
//把回射消息放入w_buff中w_buff.clear();
w_buff.put(echo.getBytes());
w_buff.flip();
}catch(IOException ioe){
System.err.println("sth. is wrong with br.readline()");
}
//发送消息while(w_buff.hasRemaining())
sc.write(w_buff);
w_buff.clear();
//进入接收状态Rec();
//间隔1秒Thread.currentThread().sleep(1000);
}
}catch(IOException ioe){
ioe.printStackTrace();
}
catch(InterruptedException ie){
ie.printStackTrace();
} }//读取server端发回的数据,并显示
publicvoidRec()throwsIOException{
intcount;
r_buff.clear();
count=sc.read(r_buff);
r_buff.flip();
byte[] temp=newbyte[r_buff.limit()];
r_buff.get(temp);
System.out.println("reply is"+count+"long, and content is:"+newString(temp));
}
publicstaticvoidmain(String args[]){
if(args.length<1){//输入需有主机名或IP地址
try{
System.err.println("Enter host name:");
BufferedReader br=newBufferedReader(newInputStreamReader(System.in));
host=br.readLine();
}catch(IOException ioe){
System.err.println("sth. is wrong with br.readline()");
} }
elseif(args.length==1){
host=args[0];
}
elseif(args.length>1){
host=args[0];
port=Integer.parseInt(args[1]);
}
newAsyncClient();
}}
在当前目录下运行:
javac AsynClient.java
后,若无编译出错,确认AsyncServer已经运行的情况下,接下来可运行:
java AsynClient hostname 或 java AsynClient hostname ×××(端口号)
并按提示进行操作即可。
总结
总的来说,用nio进行网络编程还是很有新意的,服务器端软件能在一个线程中维护与众多客户端的通信连接。笔者在本文中试图用一个典型的回射例子说明如何用nio建立最基本的C/S应用。希望大家能试着用用它。
另外,笔者在实践中也发现nio在应用中存在的一些难题,比如如何应用SocketChannel的继承类,以及如何在socketchannel之上应用SSL(Secure Socket Layer)等等,因而希望这篇文章只是抛砖引玉,引起大家对nio作进一步的讨论。
在当前目录下运行:
javac AsynServer.java
后,若无编译出错,接下来可运行:
java AsynServer 或 java AsynServer ×××(端口号)
上述服务程序在运行时,可指定其侦听端口,否则程序会取8848为默认端口。
总结
总的来说,用nio进行网络编程还是很有新意的,服务器端软件能在一个线程中维护与众多客户端的通信连接。笔者在本文中试图用一个典型的回射例子说明如何用nio建立最基本的C/S应用。希望大家能试着用用它。
另外,笔者在实践中也发现nio在应用中存在的一些难题,比如如何应用SocketChannel的继承类,以及如何在socketchannel之上应用SSL(Secure Socket Layer)等等,因而希望这篇文章只是抛砖引玉,引起大家对nio作进一步的讨论。