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 <a href="mailto:[email protected]">fzmili</a>
* 程序基本結構: 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() + " <port>");
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<Integer, ByteBuffer> buffers = new HashMap<>();//用於存儲爲每個Channel分配的ByteBuffer,方便迴文。
try {
int id = 0;
while (true) {//對於服務器需要不斷的檢查是否有數據。
Thread.sleep(1 * 1000);
/*
*阻塞,直到有就緒事件爲止。
*有多個客戶端連接都阻塞在這個地方。每個客戶端都是一個Channel。
*一個selector可以處理多個channell。這也就是提高併發數的關鍵。
*select()不阻塞的時候,代表有需要讀寫
*/
selector.select();
///////////////處理init()中選擇的建/////////////////////////////
Set<SelectionKey> readySelectionKey = selector.selectedKeys();
Iterator<SelectionKey> 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() + " <port>");
return;
}
new EchoServer(address).start();
}
}