Client端
/**
* 《构建高性能的大型分布式Java应用》
* 书中的示例代码
* 版权所有 2008---2009
*/
package book.chapter1.tcpnio;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
/**
* 描述:基于java NIO实现的tcp client
*
* @author bluedavy
* 创建时间: 2008-12-2
*/
public class Client {
public static void main(String[] args) throws Exception{
Charset charset = Charset.forName("UTF-8");
int port=9527;
SocketChannel channel=SocketChannel.open();
channel.configureBlocking(false);//设置为非阻塞模式
SocketAddress target=new InetSocketAddress("127.0.0.1",port);
channel.connect(target);//对于非阻塞模式,立即返回false,表示连接正在建立中
Selector selector=Selector.open();
channel.register(selector, SelectionKey.OP_CONNECT);//向register注册该channel及其感兴趣的连接事件
BufferedReader systemIn=new BufferedReader(new InputStreamReader(System.in));
while(true){//这里体现了NIO需要不停地去检测是否有感兴趣的事件到达这个特点,是与AIO最大的区别之一
if(channel.isConnected()){
String command=systemIn.readLine();
channel.write(charset.encode(command));
if(command==null || "quit".equalsIgnoreCase(command.trim())){
systemIn.close();
channel.close();
selector.close();
System.out.println("Client quit!");
System.exit(0);
}
}
//阻塞至有兴趣的IO事件发生,或达到超时(1000ms)时间,如果希望一直等至有兴趣的IO事件发生,可调用
//无参数的select()方法,如果希望不阻塞直接返回目前是否有感兴趣的事件发生,可调用selectNow()方法
int nKeys=selector.select(1000);//超时时间:1000ms
if(nKeys>0){//select方法返回的nKeys是感兴趣的事件的个数
for (SelectionKey key : selector.selectedKeys()) {
if(key.isConnectable()){//可连接事件
SocketChannel sc=(SocketChannel) key.channel();
sc.configureBlocking(false);
//注册感兴趣的IO读事件,通常不直接注册写事件,在socket发送缓冲区未满的情况下,一直是
//可写的。如果注册了写事件,而又不用写数据,很容易造成程序空转,CPU消耗100%的现象。
sc.register(selector, SelectionKey.OP_READ);
//查看SocketChanel.connect(SocketAddress)方法注释可知,finishConnect方法
//必须被调用,否则连接没有建立完成,注册的事件不会生效
sc.finishConnect();
} else if(key.isReadable()){//可读事件,socket读缓冲区有数据到达
ByteBuffer buffer=ByteBuffer.allocate(1024);
SocketChannel sc=(SocketChannel) key.channel();
int readBytes=0;
try{
int ret=0;
try{
//读取目前可读的数据,sc.read(buffer)返回成功复制到buffer中的字节数,
//此步骤为同步-非阻塞操作,这里体现出了NIO是【同步-非阻塞】IO模式,
//返回值可能为0,且当已经是流的结尾(Socket已关闭)时返回-1
while((ret=sc.read(buffer))>0){
readBytes+=ret;
}
}
finally{
buffer.flip();//ready for read from buffer.
}
if(readBytes>0){
System.out.println(charset.decode(buffer).toString());
buffer = null;
}
}
finally{
if(buffer!=null){
buffer.clear();
}
}
}else if(key.isWritable()) {//可写事件,socket发送缓冲区有可用空间
//首先要取消对写事件的注册
key.interestOps(key.interestOps()&(~SelectionKey.OP_WRITE));
SocketChannel sc=(SocketChannel) key.channel();
//此步骤为同步操作,这里也体现出了NIO是【同步-非阻塞】IO模式,同步直到写入socket发送
//缓冲区或网络IO出现异常,返回值为成功写入的字节数,当socket发送缓冲区已满时,此处返回0
int writtenSize = sc.write(someByteBuffer);
//如写入字节数为0,说明socket发送缓冲区已满,此时正是需要注册写事件的时刻
//其他需要进行写操作的地方,推荐的做法也是直接写入,如果写入返回0,再注册可写事件
if(writtenSize==0){
key.interestOps(key.interestOps()|SelectionKey.OP_WRITE);
}
}
}
//到此为止,已处理完本次检测出的感兴趣事件,所以需要清理待处理事件集
selector.selectedKeys().clear();
}
}
}
}
Server端
/**
* 《构建高性能的大型分布式Java应用》
* 书中的示例代码
* 版权所有 2008---2009
*/
package book.chapter1.tcpnio;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
/**
* 描述:基于Java NIO实现的tcp服务器端
*
* @author bluedavy
* 创建时间: 2008-12-2
*/
public class Server {
public static void main(String[] args) throws Exception{
int port=9527;
Selector selector=Selector.open();
ServerSocketChannel ssc=ServerSocketChannel.open();
ServerSocket serverSocket=ssc.socket();
serverSocket.bind(new InetSocketAddress(port));
System.out.println("Server listen on port: "+port);
ssc.configureBlocking(false);
ssc.register(selector, SelectionKey.OP_ACCEPT);//注册监听新连接到达事件
while(true){
int nKeys=selector.select(1000);
if(nKeys>0){
for (SelectionKey key : selector.selectedKeys()) {
if(key.isAcceptable()){//ServerSocketChannel的ACCEPT事件
ServerSocketChannel server=(ServerSocketChannel) key.channel();
SocketChannel sc=server.accept();
if(sc==null){
continue;
}
sc.configureBlocking(false);
//这里SocketChannel和ServerSocketChannel共用了同一个Selector对象
sc.register(selector, SelectionKey.OP_READ);//SocketChannel注册可读事件
}
else if(key.isReadable()){//SocketChannel的READ事件
ByteBuffer buffer=ByteBuffer.allocate(1024);
SocketChannel sc=(SocketChannel) key.channel();
int readBytes=0;
String message=null;
try{
int ret;
try{
while((ret=sc.read(buffer))>0){
readBytes+=ret;
}
}
catch(Exception e){
readBytes=0;
// IGNORE
}
finally{
buffer.flip();
}
if(readBytes>0){
message=Charset.forName("UTF-8").decode(buffer).toString();
buffer = null;
}
}
finally{
if(buffer!=null){
buffer.clear();
}
}
if(readBytes>0){
System.out.println("Message from client: "+ message);
if("quit".equalsIgnoreCase(message.trim())){
sc.close();
selector.close();
System.out.println("Server has been shutdown!");
System.exit(0);
}
String outMessage="Server response:"+message;
sc.write(Charset.forName("UTF-8").encode(outMessage));
}
}
}
//清除已处理过的事件
selector.selectedKeys().clear();
}
}
}
}