Java.NIO 网络编程 入门讲解

时间:2022-02-01 20:19:00

服务器:java在服务器编程方面已经成就霸主地位,非其他语言能够比拟的
2。移动设备,java与.net有较量,因为大的手机场上不信任微软
3。桌面应用,java不是.net的对手(个人观点)
所以,如果你对java熟悉的程度越高,你的收入会越高,但使用的地方在中国会越少,所以尽管有国外的很多软件开发者熟悉java,但在中国依然有很多人说java没有用!我用.net开发客户端的软件,java开发服务器端!

Java的发展前景

Sun公司以及IBM、Oracle、Netscape等公司都在大力推进Java的应用。旨在提高Java运行速度的Java芯片也在紧锣密鼓地赶制当中,有人预计不久的将来,Java的代码的运行速度和C++一样快。Java在WWW应用中的地位得到肯定以后,Sun又反过来扩展Java在家电领域的应用,Java的应用有向更广阔的领域扩展的趋势。Sun公司的高层人士称“Java的潜力远远超过作为编程语言带来的好处。事实上,Java是一种新计算模式的使能技术”。看来,Java的前程不可限量。

Java主要是用在服务器端的Middle Tier的编程上。随着JDK-1.4的普及,这样的时代也许已经成为历史,可以宣告结束了。让我们一起来看看Java的新面貌。

 

自JDK-1.4开始的新I/O(NIO)为Java程序员提供了新的高效率的I/O能力。效率的提高主要来源于一个新的内存共享技术。传统上,Java内存来自JVM的堆,Native code(JNI)不能很好的与Java code共享这些Java内存,因为Native code对这些Java内存的访问和使用受到很多的限制。JDK-1.2对此略有改进,提供给Native code一点比较好的控制Java内存的能力,但是并没有从根本上解决问题。JDK-1.4提供了一个特殊的包装技术,使得任何一块系统内存在经过包装之后可以很好地被Native与Java code共享。这些系统内存可以是JVM的堆以外的,甚至是JVM之外的内存,例如video memory,device controller memory,等等。这意味着,Java的I/O效率已经达到了与C相似的境界。换句话说,我们终于可以用纯Java来编写复杂的,实时(real-time)的,3D,交互式的高级应用程序了。

 

Full-Screen Exclusive Mode也是JDK-1.4新增的功能。熟悉微软的DirectX的程序员也许早已通晓Full-Screen Exclusive Mode,但对Java程序员来讲也许是个新的概念。Full-Screen Exclusive Mode允许程序员暂停并跳过窗口系统的画图操作流程而直接对屏幕进行画图操作。也就是说,程序不应再等待PAINT EVENT来执行paint方法,而是要主动地,直接在屏幕上,执行画图操作了。这个技术应用在高效率作图以及游戏程序中。在有些平台上, 一些先进的技术例如Page Flipping, Stereo Buffering等等,必须在Full-Screen Exclusive Mode下才可以使用。

 

现代应用程序越来越多地在用户界面上使用优美的图像。Java2D早已为我们提供了丰富多彩的高级图形和图像处理功能。JDK-1.4不但提高了它的效率,又为它改进和新增了很多重要的功能,例如New Pipeline Architecture, Pluggable Image I/O Framework, Hardware Acceleration for Offscreen Images, New Java Print Service, Complete Porter-Duff Support,等等。这使得Java2D的能力更上一层楼。

 

Reflection是一个极其重要的Java技术。它使得我们可以在Runtime时去发掘任何一个Object的定义,并且使用其定义。例如,在我们对某一个O一无所知的情况下,我们可以发现O是C的一个实例,还发现它有某个方法M,我们可以执行这个M。Reflection是JavaBean,Object Serialization等等重要Java技术的基础,也是各种Java工具赖以生存的重要技术。JDK-1.4对Reflection的实现作了相当的改进,使得它的速度大大提高了数倍。这对Java整体性能的提高有着重要的意义。

 

其他一些重要的,也许已为大家所熟悉的新功能,例如Logging API, AssertionFacility, Regular Expression, IPv6 Support, Preference API,XML Processing,JDBC 3.0, Unicode 3.0等等,都为我们展示了一幅美好的Java前景。

 

总而言之, Java以它精湛优美的设计思想,出人意料的发展速度而受到举世瞩目,并创造了巨额的财富和庞大的就业机会。JDK-1.4宣告了Java主要用在服务器端的形象已经成为历史。新的Java已经遨翔在更广阔的天地之间。

【聚杰网核心技术】用java.nio.*进行网络编程
前言
    因为打算用java编写异步通信的server和client程序,笔者便学习使用java.nio
开发包,其间遇到一些问题,上网却发现网上对它的应用描述的不是很多。所以,笔者不惜班门弄斧,做些简单的讨论,以便大家更进一步的讨论。
对相关类的简单介绍
    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
////////////////////////
import java.nio.channels.SocketChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.spi.SelectorProvider;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Iterator;
import java.util.LinkedList;
import java.io.IOException;
class AsyncServer implements Runnable{ 
 private ByteBuffer r_buff = ByteBuffer.allocate(1024);
 private ByteBuffer w_buff = ByteBuffer.allocate(1024);
 private static int port = 8848;
 
 public AsyncServer(){
  new Thread(this).start();
 }
 
 public void run(){    
  try{
   //生成一个侦听端
   ServerSocketChannel ssc = ServerSocketChannel.open();
   //将侦听端设为异步方式
   ssc.configureBlocking(false);
   //生成一个信号监视器
   Selector s = Selector.open();
   //侦听端绑定到一个端口
   ssc.socket().bind(new InetSocketAddress(port));
   //设置侦听端所选的异步信号OP_ACCEPT
   ssc.register(s,SelectionKey.OP_ACCEPT);
   
   System.out.println("echo server has been set up ......");
 
   while(true){
    int n = 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(); 
  }
 }
  
 public void DealwithData(SelectionKey key) throws IOException{
  int count;
  //由key获取指定socketchannel的引用
  SocketChannel sc = (SocketChannel)key.channel();
  r_buff.clear();
  //读取数据到r_buff
  while((count = sc.read(r_buff))> 0)
   ;
  //确保r_buff可读
  r_buff.flip();
  
  w_buff.clear();
  //将r_buff内容拷入w_buff  
  w_buff.put(r_buff);
  w_buff.flip();
  //将数据返回给客户端
  EchoToClient(sc);
 
  w_buff.clear();
  r_buff.clear();
 }
 
 public void EchoToClient(SocketChannel sc) throws IOException{
  while(w_buff.hasRemaining())
   sc.write(w_buff);
 }
 
 public static void main(String args[]){
  if(args.length > 0){
   port = Integer.parseInt(args[0]);
  }
  new AsyncServer();
 }
}
在当前目录下运行:
    javac AsynServer.java
后,若无编译出错,接下来可运行:
    java AsynServer  或 java AsynServer ×××(端口号)
上述服务程序在运行时,可指定其侦听端口,否则程序会取8848为默认端口。
 
2.客户端的简单示例:
////////////////////////
//AsyncClient.java
//   by zztudou@163.com
////////////////////////
import java.nio.channels.SocketChannel;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Selector;
import java.nio.channels.SelectionKey;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
class AsyncClient{
 private SocketChannel sc;
 private final int MAX_LENGTH = 1024;
 private ByteBuffer r_buff = ByteBuffer.allocate(MAX_LENGTH);
 private ByteBuffer w_buff = ByteBuffer.allocate(MAX_LENGTH);
 private static String host ;
 private static int port = 8848;
 
 public AsyncClient(){
  try {
   InetSocketAddress addr = new InetSocketAddress(host,port);
   //生成一个socketchannel
   sc = SocketChannel.open();
         
   //连接到server
   sc.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 = new BufferedReader(new InputStreamReader(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();
  }  
 }


用java.nio.*进行网络编程

 ////////////
 //读取server端发回的数据,并显示
 public void Rec() throws IOException{
  int count;
  r_buff.clear(); 
  count=sc.read(r_buff);
  r_buff.flip();  
  byte[] temp = new byte[r_buff.limit()];
  r_buff.get(temp);
  System.out.println("reply is " + count +" long, and content is: " + new String(temp));
 }
 
 public static void main(String args[]){
  if(args.length < 1){//输入需有主机名或IP地址
   try{
    System.err.println("Enter host name: ");
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    host = br.readLine();
   }catch(IOException ioe){
    System.err.println("sth. is wrong with br.readline() ");
   }
  }
  else if(args.length == 1){
   host = args[0];
  }
  else if(args.length > 1){
   host = args[0];
   port = Integer.parseInt(args[1]);
  }
  new AsyncClient();
 }
}
在当前目录下运行:
  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为默认端口。
 
2.客户端的简单示例:
////////////////////////
//AsyncClient.java
//   by zztudou@163.com
////////////////////////
import java.nio.channels.SocketChannel;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Selector;
import java.nio.channels.SelectionKey;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
class AsyncClient{
 private SocketChannel sc;
 private final int MAX_LENGTH = 1024;
 private ByteBuffer r_buff = ByteBuffer.allocate(MAX_LENGTH);
 private ByteBuffer w_buff = ByteBuffer.allocate(MAX_LENGTH);
 private static String host ;
 private static int port = 8848;
 
 public AsyncClient(){
  try {
   InetSocketAddress addr = new InetSocketAddress(host,port);
   //生成一个socketchannel
   sc = SocketChannel.open();
         
   //连接到server
   sc.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 = new BufferedReader(new InputStreamReader(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端发回的数据,并显示
 public void Rec() throws IOException{
  int count;
  r_buff.clear(); 
  while((count=sc.read(r_buff))>0)
   ;
  r_buff.flip();  
  byte[] temp = new byte[r_buff.limit()];
  System.out.println("reply is : " + new String(temp));
 }
 
 public static void main(String args[]){
  if(args.length < 1){//输入需有主机名或IP地址
   try{
    System.err.println("Enter host name: ");
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    host = br.readLine();
   }catch(IOException ioe){
    System.err.println("sth. is wrong with br.readline() ");
   }
  }
  else if(args.length == 1){
   host = args[0];
  }
  else if(args.length > 1){
   host = args[0];
   port = Integer.parseInt(args[1]);
  }
  new AsyncClient();
 }
}
在当前目录下运行:
   javac AsynClient.java
后,若无编译出错,确认AsyncServer已经运行的情况下,接下来可运行:
   java AsynClient hostname 或 java AsynClient hostname ×××(端口号)
并按提示进行操作即可。
总结
    总的来说,用nio进行网络编程还是很有新意的,服务器端软件能在一个线程中维护与众多客户端的通信连接。笔者在本文中试图用一个典型的回射例子说明如何用nio建立最基本的C/S应用。希望大家能试着用用它。
    另外,笔者在实践中也发现nio在应用中存在的一些难题,比如如何应用SocketChannel的继承类,以及如何在socketchannel之上应用SSL(Secure Socket Layer)等等,因而希望这篇文章只是抛砖引玉,引起大家对nio作进一步的讨论。

 在JDK 1.4以前,Java的IO操作集中在java.io这个包中,是基于流的同步(blocking)API。对于大多数应用来说,这样的API使用很方便,然而,一些对性能要求较高的应用,尤其是服务端应用,往往需要一个更为有效的方式来处理IO。从JDK 1.4起,NIO API作为一个基于缓冲区,并能提供异步(non-blocking)IO操作的API被引入。本文对其进行深入的介绍。

 

NIO API主要集中在java.nio和它的subpackages中:

 

java.nio

定义了Buffer及其数据类型相关的子类。其中被java.nio.channels中的类用来进行IO操作的ByteBuffer的作用非常重要。

 

java.nio.channels

定义了一系列处理IO的Channel接口以及这些接口在文件系统和网络通讯上的实现。通过Selector这个类,还提供了进行异步IO操作的办法。这个包可以说是NIO API的核心。

 

java.nio.channels.spi

定义了可用来实现channel和selector API的抽象类。

 

java.nio.charset

定义了处理字符编码和解码的类。

 

java.nio.charset.spi

定义了可用来实现charset API的抽象类。

 

java.nio.channels.spi和java.nio.charset.spi这两个包主要被用来对现有NIO API进行扩展,在实际的使用中,我们一般只和另外的3个包打交道。下面将对这3个包一一介绍。

 

Package java.nio

这个包主要定义了Buffer及其子类。Buffer定义了一个线性存放primitive type数据的容器接口。对于除boolean以外的其他primitive type,都有一个相应的Buffer子类,ByteBuffer是其中最重要的一个子类。

 

下面这张UML类图描述了java.nio中的类的关系:

 

 


Buffer

定义了一个可以线性存放primitive type数据的容器接口。Buffer主要包含了与类型(byte, char…)无关的功能。值得注重的是Buffer及其子类都不是线程安全的。

 

每个Buffer都有以下的属性:

 

capacity

这个Buffer最多能放多少数据。capacity一般在buffer被创建的时候指定。

limit

在Buffer上进行的读写操作都不能越过这个下标。当写数据到buffer中时,limit一般和capacity相等,当读数据时,limit代表buffer中有效数据的长度。

position

读/写操作的当前下标。当使用buffer的相对位置进行读/写操作时,读/写会从这个下标进行,并在操作完成后,buffer会更新下标的值。

mark

一个临时存放的位置下标。调用mark()会将mark设为当前的position的值,以后调用reset()会将position属性设置为mark的值。mark的值总是小于等于position的值,假如将position的值设的比mark小,当前的mark值会被抛弃掉。

 

这些属性总是满足以下条件:

0 <= mark <= position <= limit <= capacity

 

limit和position的值除了通过limit()和position()函数来设置,也可以通过下面这些函数来改变:

 

Buffer clear()

把position设为0,把limit设为capacity,一般在把数据写入Buffer前调用。

Buffer flip()

把limit设为当前position,把position设为0,一般在从Buffer读出数据前调用。

Buffer rewind()

把position设为0,limit不变,一般在把数据重写入Buffer前调用。

 

Buffer对象有可能是只读的,这时,任何对该对象的写操作都会触发一个ReadOnlyBufferException。isReadOnly()方法可以用来判定一个Buffer是否只读。

 

ByteBuffer

在Buffer的子类中,ByteBuffer是一个地位较为非凡的类,因为在java.io.channels中定义的各种channel的IO操作基本上都是围绕ByteBuffer展开的。

 

ByteBuffer定义了4个static方法来做创建工作:

 

ByteBuffer allocate(int capacity)