java 网络学习 之nio模型基础介绍Selector(7)

Selector

说明:

  • FileChannel是可读可写的Channel,它必须阻塞,不能用在非阻塞模式中

  • SocketChannel与FileChannel不同:新的Socket Channel能在非阻塞模式下运行并且是可选择的。不再需要为每个socket连接指派线程了。使用新的NIO类,一个或多个线程能管理成百上千个活动的socket连接,使用Selector对象可以选择可用的Socket Channel。

以前的Socket程序是阻塞的,服务器必须始终等待客户端的连接,而NIO可以通过Selector完成非阻塞操作。

备注:其实NIO主要的功能是解决服务端的通讯性能。

Selector一些主要方法:

方法 说明
open() 打开一个选择器。
select()

查看选择器监听的通道是否已为 I/O 操作准备就绪。

int select(long timeout);//可以设置超时的select()操作

int selectNow();//进行一个立即返回的select()操作

selectedKeys() 返回此选择器的就绪的键集。
wakeup() Selector wakeup();//使一个还未返回的selecor()操作立即返回

 

SelectionKey的四个重要常量:

字段 说明
OP_ACCEPT 用于套接字接受操作的操作集位。
OP_CONNECT 用于套接字连接操作的操作集位。
OP_READ 用于读取操作的操作集位。
OP_WRITE 用于写入操作的操作集位。

说明:其实四个常量就是Selector监听SocketChannel四种不同类型的事件。

如果你对不止一种事件感兴趣,那么可以用"位或"操作符将常量连接起来,如下: int interestSet = SelectionKey.OPREAD | SelectionKey.OPWRITE;

 

nio 示例

1 简单示例  客户端socketchannel 示例

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.Iterator;

public class TestSocketChannel {
	
	public static void main(String[] args) {
		Selector selector = null;
		SocketChannel socket = null;
		
		try {
			// TODO 创建一个Selector
			selector = Selector.open();
			
			// TODO 创建并注册Socket
			socket = SocketChannel.open();
			socket.configureBlocking(false);
			socket.register(selector, SelectionKey.OP_CONNECT);
			
			// TODO 连接到远程地址
			InetSocketAddress ip = new InetSocketAddress("localhost", 12345);
			socket.connect(ip);
			
			//TODO 监听事件
			while(true){
				selector.select();
				//事件来源列表
				Iterator it = selector.selectedKeys().iterator();
				while(it.hasNext()){
					SelectionKey key = it.next();
					//删除当前事件
					it.remove();
					
					//判断当前事件类型
					if(key.isConnectable()){
						//连接事件
						SocketChannel channel = (SocketChannel)key.channel();
						channel.register(selector, SelectionKey.OP_READ);
					}else if(key.isReadable()){
						//读取数据事件
						SocketChannel channel = (SocketChannel)key.channel();
						channel.register(selector, SelectionKey.OP_WRITE);
						
						//读取数据
						CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
						ByteBuffer buffer = ByteBuffer.allocate(1024);
						int count = channel.read(buffer);
						System.out.println(count + ":" + decoder.decode(buffer));
					}else if(key.isWritable()){
						//写入数据事件
						SocketChannel channel = (SocketChannel)key.channel();
						channel.register(selector, SelectionKey.OP_READ);
						
						//写入数据
						CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
						channel.write(encoder.encode(CharBuffer.wrap("Hello")));
					}
				}
			}
		} catch (ClosedChannelException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (CharacterCodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally{
			try {
				selector.close();
				socket.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

2 简单示例  客户端serverSocketchannel 示例

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.Iterator;

public class TestServerSocketChannel {

	public static void main(String[] args) {
		Selector selector = null;
		ServerSocketChannel server = null;
		
		try {
			// TODO 创建一个Selector
			selector = Selector.open();
			
			// TODO 创建Socket并注册
			server = ServerSocketChannel.open();
			server.configureBlocking(false);
			server.register(selector, SelectionKey.OP_ACCEPT);
			
			// TODO 启动端口监听
			InetSocketAddress ip = new InetSocketAddress("localhost", 12345);
			server.socket().bind(ip);
			
			// TODO 监听事件
			while(true){
				//监听事件
				selector.select();
				//事件来源列表
				Iterator it = selector.selectedKeys().iterator();
				while(it.hasNext()){
					SelectionKey key = it.next();
					//删除该事件
					it.remove();
					
					//判断事件类型
					if(key.isConnectable()){
						//连接事件
						SocketChannel channel = (SocketChannel)key.channel();
						if(channel.isConnectionPending()){
							channel.finishConnect();
						}
						channel.register(selector, SelectionKey.OP_READ);
					}else if(key.isReadable()){
						//读取数据事件
						//读取数据事件
						SocketChannel channel = (SocketChannel)key.channel();
						channel.register(selector, SelectionKey.OP_WRITE);
						
						//读取数据
						CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
						ByteBuffer buffer = ByteBuffer.allocate(1024);
						int count = channel.read(buffer);
						System.out.println(count + ":" + decoder.decode(buffer));
					} else if(key.isWritable()){
						SocketChannel channel = (SocketChannel)key.channel();
						channel.register(selector, SelectionKey.OP_READ);
						
						//写入数据
						CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
						channel.write(encoder.encode(CharBuffer.wrap("Hello")));
					}
				}
			}
		} catch (ClosedChannelException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (CharacterCodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally{
			//关闭
			try {
				selector.close();
				server.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

}

3 优化: 支持多客户端的Client/Server任务响应程序

演示一个可以接受多个客户端请求的服务器程序,服务端使用非阻塞模式监听多个客户端的连接和发送来的消息,在收到消息后根据消息命令来处理不同的业务逻辑,然后回复给客户端,客户端通过控制台输入的字符串发送给服务器端。

以下是服务器代码

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.Iterator;

public class NIOServer {

	public static void main(String[] args) {
		Selector selector = null;
		ServerSocketChannel server = null;
		
		try {
			// TODO 创建一个Selector
			selector = Selector.open();
			
			// TODO 创建Socket并注册
			server = ServerSocketChannel.open();
			server.configureBlocking(false);
			server.register(selector, SelectionKey.OP_ACCEPT);
			
			// TODO 启动端口监听
			InetSocketAddress ip = new InetSocketAddress(12345);
			server.socket().bind(ip);
			
			// TODO 监听事件
			while(true){
				//监听事件
				selector.select();
				//事件来源列表
				Iterator it = selector.selectedKeys().iterator();
				while(it.hasNext()){
					SelectionKey key = it.next();
					//删除该事件
					it.remove();
					
					//判断事件类型
					if(key.isAcceptable()){
						//连接事件
						ServerSocketChannel server2 = (ServerSocketChannel) key.channel();
						SocketChannel channel = server2.accept();
						channel.configureBlocking(false);
						if(channel.isConnectionPending()){
							channel.finishConnect();
						}
						channel.register(selector, SelectionKey.OP_READ);
						System.out.println("accept客户端连接:"
								+ channel.socket().getInetAddress().getHostName()
								+ channel.socket().getPort());
					}else if(key.isReadable()){
						//读取数据事件
						SocketChannel channel = (SocketChannel)key.channel();
						//channel.register(selector, SelectionKey.OP_WRITE);
						//读取数据
						CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
						ByteBuffer buffer = ByteBuffer.allocate(64);
						int count = channel.read(buffer);
						buffer.flip();
						String msg = decoder.decode(buffer).toString();
						System.out.println(count + "收到:" + msg);
						
						//写入数据
						CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
						channel.write(encoder.encode(CharBuffer.wrap("server"+msg)));
					} else if(key.isWritable()){
						SocketChannel channel = (SocketChannel)key.channel();
						channel.register(selector, SelectionKey.OP_READ);
						
						//写入数据
						CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
						channel.write(encoder.encode(CharBuffer.wrap("Hello")));
					}
				}
			}
		} catch (ClosedChannelException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (CharacterCodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally{
			//关闭
			try {
				selector.close();
				server.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

 以下是客户端代码

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class NIOClient {

	public static void main(String[] args) {
		NIOClientThread clientThread = new NIOClientThread();
		clientThread.start();
		
		//输入输出流
		BufferedReader sin = new BufferedReader(new InputStreamReader(System.in));
		
		try {
			//循环读取键盘输入
			String readLine;
			while((readLine = sin.readLine()) != null){
				if(readLine.equals("bye")){
					clientThread.close();
					System.exit(0);
				}
				clientThread.send(readLine);
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}



import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.Iterator;

public class NIOClientThread extends Thread {
	private CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
	private CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
	private Selector selector = null;
	private SocketChannel socket = null;
	private SelectionKey clientKey = null;
	
	// TODO 启动客户端
	public NIOClientThread() {
		try {
			// 创建一个Selector
			selector = Selector.open();

			// 创建并注册Socket
			socket = SocketChannel.open();
			socket.configureBlocking(false);
			clientKey = socket.register(selector, SelectionKey.OP_CONNECT);

			// 连接到远程地址
			InetSocketAddress ip = new InetSocketAddress("localhost", 12345);
			socket.connect(ip);
		} catch (ClosedChannelException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	// TODO 读取事件
	@Override
	public void run() {
		try {
			// 监听事件
			while (true) {
				selector.select(1);
				// 事件来源列表
				Iterator it = selector.selectedKeys().iterator();
				while (it.hasNext()) {
					SelectionKey key = it.next();
					// 删除当前事件
					it.remove();

					// 判断当前事件类型
					if (key.isConnectable()) {
						// 连接事件
						SocketChannel channel = (SocketChannel) key.channel();
						if(channel.isConnectionPending()){
							channel.finishConnect();
						}
						channel.register(selector, SelectionKey.OP_READ);
						System.out.println("连接服务器端成功!");
					} else if (key.isReadable()) {
						// 读取数据事件
						SocketChannel channel = (SocketChannel) key.channel();
						channel.register(selector, SelectionKey.OP_WRITE);

						// 读取数据
						ByteBuffer buffer = ByteBuffer.allocate(64);
						int count = channel.read(buffer);
						buffer.flip();
						String msg = decoder.decode(buffer).toString();
						System.out.println(count + "收到:" + msg);
					}
				}
			}
		} catch (ClosedChannelException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (CharacterCodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	// TODO 发送消息
	public void send(String msg) {
		// 写入数据事件
		try {
			SocketChannel channel = (SocketChannel) clientKey.channel();
			channel.write(encoder.encode(CharBuffer.wrap(msg)));
		} catch (CharacterCodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	// TODO 关闭客户端
	public void close(){
		try {
			selector.close();
			socket.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

4 基于以上的客户端代码,服务器代码稍微修改一下,就可以做成一个聊天系统,支持多个客户端通讯

首先服务器启动,然后客户端启动之后向服务器发送一个消息,告知服务器是哪个客户端 username=xx

然后就可以按照协议格式 向指定的人发送数据了,服务器会查询目的渠道,然后写入数据。

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.Hashtable;
import java.util.Iterator;

public class ChatServer {

	public static void main(String[] args) {
		// 客户端列表
		Hashtable clietList = new Hashtable();

		Selector selector = null;
		ServerSocketChannel server = null;

		try {
			// 创建一个Selector
			selector = Selector.open();

			// 创建Socket并注册
			server = ServerSocketChannel.open();
			server.configureBlocking(false);
			server.register(selector, SelectionKey.OP_ACCEPT);

			// 启动监听端口
			InetSocketAddress ip = new InetSocketAddress(12345);
			server.socket().bind(ip);
			
			System.out.println("成功启动服务端!");

			// TODO 监听事件
			while (true) {
				// 监听事件
				selector.select();
				// 事件来源列表
				Iterator it = selector.selectedKeys().iterator();
				while (it.hasNext()) {
					SelectionKey key = it.next();
					// 删除该事件
					it.remove();

					// 判断事件类型
					if (key.isAcceptable()) {
						// 连接事件
						ServerSocketChannel server2 = (ServerSocketChannel) key.channel();
						SocketChannel channel = server2.accept();
						channel.configureBlocking(false);
						if (channel.isConnectionPending()) {
							channel.finishConnect();
						}
						channel.register(selector, SelectionKey.OP_READ);
						System.out.println("客户端连接:" + channel.socket().getInetAddress().getHostName()
								+ channel.socket().getPort());
					} else if (key.isReadable()) {
						// 读取数据事件
						SocketChannel channel = (SocketChannel) key.channel();

						// 读取数据
						CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
						ByteBuffer buffer = ByteBuffer.allocate(512);
						channel.read(buffer);
						buffer.flip();
						String msg = decoder.decode(buffer).toString();
						System.out.println("收到:" + msg);

						if(msg.startsWith("username=")){
							String username = msg.replaceAll("username=", "");
							clietList.put(username, channel);
						}else{
							//转发消息给客户端
							String[] arr = msg.split(":");
							if(arr.length == 3){
								String from = arr[0];//发送者
								String to = arr[1];//接受者
								String content = arr[2];//发送内容
								
								if(clietList.containsKey(to)){
									CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
									//给接收者发送消息
									clietList.get(to).write(encoder.encode(CharBuffer.wrap(from+"】"+content)));
								}
							}else{
								String from = arr[0];
								String content = "来自服务器消息:您未指定接收人";
								CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
								//给接收者发送消息
								clietList.get(from).write(encoder.encode(CharBuffer.wrap(content)));
							}
						}
					}
				}
			}
		} catch (ClosedChannelException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (CharacterCodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally{
			try {
				selector.close();
				server.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

}

DatagramChannel 示例

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;

public class UDPServer {

	public static void main(String[] args) {
		DatagramChannel socket = null;
		try {
			//创建socket
			socket = DatagramChannel.open();
			InetSocketAddress ip = new InetSocketAddress("localhost", 12345);
			socket.socket().bind(ip);
			
			//循环监听
			while(true){
				CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
				ByteBuffer buffer = ByteBuffer.allocate(64);
				socket.receive(buffer);
				buffer.flip();
				System.out.println(decoder.decode(buffer).toString());
			}
		} catch (SocketException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (CharacterCodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally{
			try {
				socket.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

	}

}


import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.CharBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;

public class UDPClient {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		DatagramChannel socket = null;
		try {
			//创建一个Socket
			socket = DatagramChannel.open();
			InetSocketAddress ip = new InetSocketAddress("localhost", 12345);
			socket.connect(ip);
			
			//发送数据
			CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
			socket.write(encoder.encode(CharBuffer.wrap("Hello")));
		} catch (CharacterCodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally{
			try {
				socket.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

}

 

你可能感兴趣的:(java,网络)