对于用ServerSocket及Socket编写的服务器程序和客户端程序,它们在运行过程中常常会发生阻塞。
假如服务器程序需要同时与多个客户端通信,就必须分配多个工作线程,让他们分别负责与一个客户端通信,当然每一个工作线程都有可能经常处于长时间的阻塞状态。
线程阻塞的原因:
1、线程执行了Thread.sleep(int n)方法,线程放弃cpu,睡眠n毫秒,然后恢复运行。
2、线程要执行一段同步代码,由于无法获得相关的资源,只好进入阻塞状态,等待获得同步锁,才恢复运行。
3、线程执行了一个对象的wait()方法,进入阻塞状态,只有等到其它线程知心话了该对象的notify()或notifiAll()方法,才可能将其唤醒。
4、线程执行I/O操作或进行远程通信时,会因为等待相关的资源而进入阻塞状态。例如:当一个线程执行ServerSocket的accpet()方法时,假如没有客户端进行连接,该线程 就会一直等到待客户端的连接才会从accpet()方法返回。
5、请求与服务器建立连接时,即当线程执行Socket的带参数的构造方法,或执行Socket的connect()方法时,会进入阻塞状态,直到连接成功,此线程才从Socket的构造方法 中或connect()方法中返回。
6、线程从Socket的输入流读入数据时,如果没有足够的数据,了就会进入阻塞状态,直到读到足够的数据,或者到输入流的末尾,或者出现异常,才从输入流的read()方法 中返回或异常中断。例如:当线程执行Socket的read()方法时,如果输入流中没有数据,该线程就会一直等到读入足够的数据才从read()方法中返回。
7、线程向Socket的输出流写一段数据时,可能会进入阻塞状态,等到输出了所有的数据,或者出现异常,才会从输出流的write()方法返回或异常中断。
8、调用Socket的setSoLinger()方法设置了关闭Socket的延迟时间,那么当线程执行Socket的close()方法时,会进入阻塞状态,知道底层Socket发送完所有剩余的数据,或者超过了SetSoLinger()方法设置的延迟时间,才从close()方法返回。
服务器程序采用多线程来处理I/O,尽管能满足同时相应多客户端请求的需求,
但是有一下局限:
1、java虚拟机会每个线程分配独立的堆栈空间,工作线程数目越多,系统开销就越大,而且增大的java虚拟机调度工作线程的复杂度,增加了线程之间同步的复杂度,并且提高了线程的死锁的可能性。
2、工作线程的许多时间浪费在阻塞I/O操作上,java虚拟机需要频繁的转让cpu的使用权。
从JDK1.4版本引入了非阻塞通信机制,服务器程序接受客户端的连接、客户端程序建立于服务器的连接,以及服务器程序与客户端程序收发数据的操作都可以按照非阻塞通信的方式进行。服务器程序之需要创建一个线程,就可以完成同时与多个客户端通信的任务。
一、
支持非阻塞通信的类:
1、ServerSocketChannel:ServerSocket的替代类,支持阻塞与非阻塞通信。
2、SocketChannel:Socket的替代类,支持阻塞与非阻塞通信。
3、Selector:为ServerSocketChannel监控接收连接就绪事件,为SocketChannel监控连接就绪、读就绪和写就绪事件。
4、SelectionKey:代表ServerSocketChannel及SocketChannel向Selector注册事件的句柄。当一个SelectionKey对象位于selected-keys集合中时,就表示这与 SelecyionKey对象相关的事发生了。
二、应用范例
服务器端:
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;
import java.net.*;
import java.util.*;
public class EchoServer{
private Selector selector = null;
private ServerSocketChannel serverSocketChannel = null;
private int port = 8000;
private Charset charset=Charset.forName("GBK");
public EchoServer()throws IOException{
// 创建一个Selector对象
selector = Selector.open();
// 创建一个ServerSocketChannel对象
serverSocketChannel= ServerSocketChannel.open();
// 使得在同一台主机关闭了服务器程序,紧接着在启动该服务器程序时,可以顺利绑定到相同的端口
serverSocketChannel.socket().setReuseAddress(true);
//使ServrSocketChannel工作于非阻塞模式
serverSocketChannel.configureBlocking(false);
// 把服务器进程于一个本地端口绑定
serverSocketChannel.socket().bind(new InetSocketAddress(port));
System.out.println("服务器启动!!!");
}
public void service() throws IOException{
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT );
while (selector.select() > 0 ){
//获得Selector的selected-keys集合
Set readyKeys = selector.selectedKeys();
Iterator it = readyKeys.iterator();
while (it.hasNext()){
// 处理SelectionKey
SelectionKey key=null;
try{
// 取出一个SelectionKey
key = (SelectionKey) it.next();
// 把一个SelectionKey从Selector的selected-key集合中删除
it.remove();
// 处理接受连接就绪事件
if (key.isAcceptable()) {
// 获得与SeletionKey关联的ServerSocketChannel
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
// 获得与客户端连接的SocketChannel
SocketChannel socketChannel = (SocketChannel) ssc.accept();
System.out.println("接收到客户连接,来自:" +
socketChannel.socket().getInetAddress() +
":" + socketChannel.socket().getPort());
// 把SocketChannel设置为非阻塞模式
socketChannel.configureBlocking(false);
// 创建一个用于存放用户发送来的数据缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
// SocketChannel向Selector注册读就绪事件和写就绪事件
socketChannel.register(selector,
SelectionKey.OP_READ |
SelectionKey.OP_WRITE, buffer);//关联了一个buffer附件
}
// 处理读就绪事件
if (key.isReadable()) {
receive(key);
}
// 处理写就绪事件
if (key.isWritable()) {
send(key);
}
}catch(IOException e){
e.printStackTrace();
try{
if(key!=null){
// 使这个SelectionKey失效,使得Selector不在监控这个SelectorKey感兴趣的事件
key.cancel();
// 关闭与这个SelectioKey关联的SocketChannel
key.channel().close();
}
}catch(Exception ex){e.printStackTrace();}
}
}//#while
}//#while
}
public void receive(SelectionKey key)throws IOException{
// 获得与SelectionKey关联的附件
ByteBuffer buffer=(ByteBuffer)key.attachment();
// 获得与SelectionKey关联的SocketChannel
SocketChannel socketChannel=(SocketChannel)key.channel();
// 创建一个ByteBuffer,用于存放读取到的数据
ByteBuffer readBuff= ByteBuffer.allocate(32);
//
socketChannel.read(readBuff);
//
readBuff.flip();
// 把buffer的极限设置为容量
buffer.limit(buffer.capacity());
// 把readBuffer中的内容拷贝到buffer中,假定buff的容量足够大,不会出现缓冲区溢出异常
buffer.put(readBuff);
}
public void send(SelectionKey key)throws IOException{
// 获得与SelectionKEY关联的ByteBuffer
ByteBuffer buffer=(ByteBuffer)key.attachment();
// 获取与SelectionKey关联的SocketChannel
SocketChannel socketChannel=(SocketChannel)key.channel();
// 把极限设为位置,把位置设为0
buffer.flip(); //把极限设为位置,把位置设为0
// 将buffer中的字节装换为字符串
String data=decode(buffer);
// 如果还没有读取一行数据,就返回
if(data.indexOf("/r/n")==-1)return;
// 截取一行数据
String outputData=data.substring(0,data.indexOf("/n")+1);
System.out.print(outputData);
// 转换为字节,并放在outputBuffer中
ByteBuffer outputBuffer=encode("服务器 echo:"+outputData);
// 输出outputBuffer中所有的字节
while(outputBuffer.hasRemaining())
socketChannel.write(outputBuffer);
// 转换为字节,并放在ByteBuffer中
ByteBuffer temp=encode(outputData);
// 把buffer的位置设置为temp的极限
buffer.position(temp.limit());
// 删除buffer中衣橱里的数据
buffer.compact();
// 如果已经输出字符串“bye/r/n”,就使的SelectionKey失效,并关闭SocketChannel
if(outputData.equals("bye/r/n")){
key.cancel();
socketChannel.close();
System.out.println("关闭与客户的连接");
}
}
public String decode(ByteBuffer buffer){ //解码
CharBuffer charBuffer= charset.decode(buffer);
return charBuffer.toString();
}
public ByteBuffer encode(String str){ //编码
return charset.encode(str);
}
public static void main(String args[])throws Exception{
EchoServer server = new EchoServer();
server.service();
}
}
三、问题发现
不知为什么,程序已运行cpu就100%!!!是程序写的有问题,还是别的问题?望高手指点.