1.客户端程序
package cn.fzmili.archetype.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;
/**
* 代码清单 2-4 客户端的主类
*
* @author fzmili
* 程序基本结构: start()---------功能实现的主类。主类中的异常全部由main函数来实现。start()函数只是实现正常逻辑。
* main()----------实现程序的启动。
*/
public class EchoClient {
private InetSocketAddress address;
private ByteBuffer wBuffer = ByteBuffer.allocate(1024);
private ByteBuffer rBuffer = ByteBuffer.allocate(1024);
private Charset charset = Charset.forName("UTF-8");//设置发送的字符编码
public EchoClient(String host, int port) {
this.address = new InetSocketAddress(host, port);
}
public EchoClient(InetSocketAddress address) {
this.address = address;
}
public void start() throws Exception {//非阻塞连接版本。
///初始化//
final SocketChannel channel;
channel = SocketChannel.open();//创建但不连接。
//channel.configureBlocking(false);//设置为非阻塞模式,阻塞方法将立即返回
channel.connect(address);//阻塞模式和非阻塞模式的区别在于,非阻塞模式总是返回false,阻塞模式返回值根据实际情况
//非阻塞模式
if(channel.isConnectionPending())
channel.finishConnect();//非阻塞模式下,连接进行时,调用这个完成连接,连接失败时调用,抛出异常。
if(!channel.isConnected()){
System.out.println("connecting fail");
return;
}
//获取用户输入
System.out.println("Connecting Success!");
Scanner scanner = new Scanner(System.in);//scanner应该是一个单独的线程。
String text=scanner.nextLine();//到第一个换行符为止。
text+='\n';//因为scanner.nextLine()不包含换行符,但服务器需要根据换行符来判断返回。
System.out.println("发送的数据:"+text);
scanner.close();
System.in.close();
//网络交互部分。
wBuffer.put(charset.encode(text));
wBuffer.flip();
channel.write(wBuffer);
int readBytes=channel.read(rBuffer);//关键问题在于这个阻塞,一直没有数据
System.out.println("接收的数据:" + getString(rBuffer));
rBuffer.clear();
channel.close();
}
public static String getString(ByteBuffer buffer) {
String string = "";
try {
for (int i = 0; i < buffer.position(); i++) {
string += (char) buffer.get(i);//使用绝对定位,不会移动指针。
}
return string;
} catch (Exception ex) {
ex.printStackTrace();
return "";
}
}
public static void main(String[] args) throws NumberFormatException {
InetSocketAddress address;
switch (args.length) {
case 0://默认IP,默认端口,用于测试
address = new InetSocketAddress("127.0.0.1", 9999);
break;
case 2://正常情况,如果有问题,则产生异常
{
String ip = args[0];
int port = Integer.parseInt(args[1]);
address = new InetSocketAddress(ip, port);
}
default://其他情况
System.err.println("Usage: " + EchoClient.class.getSimpleName() + " ");
return;
}
try {
new EchoClient(address).start();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("---主进程结束----");
}
}
2.服务器程序
package cn.fzmili.archetype.nio;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.NotYetConnectedException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class EchoServer {
private InetSocketAddress address;
private final int bufferSize = 2048;
public EchoServer(String host, int port) {
this.address = new InetSocketAddress(host, port);
}
public EchoServer(InetSocketAddress address) {
this.address = address;
}
public void start() {
///init()初始化
Selector selector = null;
try {
//创建Channel和Selector
ServerSocketChannel channel = ServerSocketChannel.open();
selector = Selector.open();
channel.configureBlocking(false);//将通道设置为非阻塞
channel.bind(this.address);
/*
*将Channel注册到selector,并指定感兴趣的事件
*有4种事件:electionKey.OP_CONNECT SelectionKey.OP_ACCEPT SelectionKey.OP_READ SelectionKey.OP_WRITE
*OP_CONNECT----用于客户端,
*OP_ACCEPT-----用于服务器
*OP_READ & OP_WRITE--------服务器/客户端通用
*/
channel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server started .... port:" + address.getPort());
} catch (Exception e) {
System.out.println("Error-" + e.getMessage());
return;
}
//处理连接///
Map buffers = new HashMap<>();//用于存储为每个Channel分配的ByteBuffer,方便回文。
try {
int id = 0;
while (true) {//对于服务器需要不断的检查是否有数据。
Thread.sleep(1 * 1000);
/*
*阻塞,直到有就绪事件为止。
*有多个客户端连接都阻塞在这个地方。每个客户端都是一个Channel。
*一个selector可以处理多个channell。这也就是提高并发数的关键。
*select()不阻塞的时候,代表有需要读写
*/
selector.select();
///处理init()中选择的建/
Set readySelectionKey = selector.selectedKeys();
Iterator it = readySelectionKey.iterator();
//处理每一个连接
while (it.hasNext()) {
SelectionKey channelKey = it.next();
//Accept---------客户端请求TCP连接
if (channelKey.isAcceptable()) {
/*
* 获取通道 接受连接,
* 设置非阻塞模式(必须),同时需要注册 读写数据的事件,这样有消息触发时才能捕获
*每个客户端的Channel是由Selector.channel()获取的。SelectionKey就是通道的指针。
*/
ServerSocketChannel channel = (ServerSocketChannel) channelKey.channel();
channel.accept()
.configureBlocking(false)
.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE)//向selector注册channel,返回与这个Chanenl关联的SelectionKey,该Key就是Channel的指针,代表该Channel。
.attach(++id);//向SelectionKey添加一个标识,由于是Key就代表Channel,所以也是Channel标识。
/*
上面的程序分解以便理解:
channel.accept();
channel.configureBlocking(false);
SelectionKey key=channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);//向selector注册channel,返回与这个Chanenl关联的SelectionKey,该Key就是Channel的指针,代表该Channel。
key.attach(++id);//向SelectionKey添加一个标识,由于是Key就代表Channel,所以也是Channel标识。
*/
buffers.put(id, ByteBuffer.allocate(bufferSize));//为每个连接准备一个缓冲区,并和Channel关联起来。
System.out.println("ChannelID:" + id+"\tConnected");
}
if (channelKey.isReadable()) {// 读数据,所有的Channel都是在这读的,不要想象只有一个Channel。
SocketChannel clientChannel = (SocketChannel) channelKey.channel();
ByteBuffer buf = buffers.get((Integer) channelKey.attachment());
try {
int readBytes = clientChannel.read(buf);
if(readBytes==-1) continue;// read=-1代表没有东西读取,这时候没有必要输出。
System.out.print("ChannelID:" + channelKey.attachment()+ "\tInput");
System.out.print("\tReadBytes:" + readBytes);
System.out.println("\tContent:" + getString(buf));//getString采用绝对读写,不影响读写指针。
} catch (Exception e) {
clientChannel.close();
channelKey.cancel();
System.out.println("channelID:"+channelKey.attachment()+"\tClosed");
continue;
}
}
if (channelKey.isWritable()) {// 写数据
// System.out.println(channelKey.attachment()+ " - 写数据事件");
SocketChannel clientChannel = (SocketChannel) channelKey.channel();
ByteBuffer buf = buffers.get((Integer) channelKey.attachment());
/*以下这段代码是实现回文的主要代码
*buf.position----------指向下一个要写入的位置
*buf.get(index)------------获取当前最后一个字符。
*/
if (buf.position() > 0) {//该判断保证不会超出ByteBuffer的下界限,上界限没时间搞。
if (buf.get(buf.position() - 1) == '\n') {
buf.flip();
clientChannel.write(buf);
buf.clear();
}
}
}
// 必须removed 否则会继续存在,下一次循环还会进来,
// 注意removed 的位置,针对一个.next() remove一次
it.remove();
}
}
} catch (Exception e) {
// TODO: handle exception
System.out.println("Error - " + e.getMessage());
e.printStackTrace();
} finally {
if (selector != null) {
try {
selector.close();
} catch (IOException e) {
System.out.println("Error-" + e.getMessage());
e.printStackTrace();
}
}
}
}
//一个实用的类,用于转换字符串。
public static String getString(ByteBuffer buffer) {
String string = "";
try {
for (int i = 0; i < buffer.position(); i++) {
string += (char) buffer.get(i);//使用绝对定位,不会移动指针。
}
return string;
} catch (Exception ex) {
ex.printStackTrace();
return "";
}
}
public static void main(String[] args) throws Exception {
InetSocketAddress address;
switch (args.length) {
case 0://默认IP,默认端口,用于测试
address = new InetSocketAddress("127.0.0.1", 9999);
break;
case 1://指定绑定端口(IP绑定到所有端口)
{
//设置端口值(如果端口参数的格式不正确,则抛出一个NumberFormatException)
int port = Integer.parseInt(args[0]);
address = new InetSocketAddress(9999);
break;
}
case 2://正常情况,如果有问题,则产生异常
{
String ip = args[0];
int port = Integer.parseInt(args[0]);
address = new InetSocketAddress(ip, port);
}
default://其他情况
System.err.println("Usage: " + EchoServer.class.getSimpleName() + " ");
return;
}
new EchoServer(address).start();
}
}