说明:
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;
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();
}
}
}
}
5 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();
}
}
}
}