一、Channel與Buffer
Channel可以寫到不同的Buffer
同理,channel也可以從不同的Buffer中讀取數據
二、NIO重寫服務器
監聽客戶端到達
接收、回送客戶端
轉發客戶端數據到另外一個客戶端
多端消息的處理
2.1 IO阻塞下
2.2 NIO下客戶端連接方式
三、
3.1 圖示
3.2 代碼
服務端:
package net.qiujuer.lesson.sample.client;
import net.qiujuer.lesson.sample.client.bean.ServerInfo;
import net.qiujuer.library.clink.utils.CloseUtils;
import java.io.*;
import java.net.Inet4Address;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
public class TCPClient {
private final Socket socket;
private final ReadHandler readHandler;
private final PrintStream printStream;
public TCPClient(Socket socket,ReadHandler readHandler) throws IOException {
this.socket = socket;
this.readHandler = readHandler;
this.printStream = new PrintStream(socket.getOutputStream());
}
public void exit(){
readHandler.exit();
CloseUtils.close(printStream);
CloseUtils.close(socket);
}
public void send(String msg){
printStream.println(msg);
}
public static TCPClient startWith(ServerInfo info) throws IOException {
Socket socket = new Socket();
// 超時時間
socket.setSoTimeout(3000);
// 連接本地,端口2000;超時時間3000ms
socket.connect(new InetSocketAddress(Inet4Address.getByName(info.getAddress()), info.getPort()), 3000);
System.out.println("已發起服務器連接,並進入後續流程~");
System.out.println("客戶端信息:" + socket.getLocalAddress() + " P:" + socket.getLocalPort());
System.out.println("服務器信息:" + socket.getInetAddress() + " P:" + socket.getPort());
try {
ReadHandler readHandler = new ReadHandler(socket.getInputStream());
readHandler.start();
return new TCPClient(socket,readHandler);
} catch (Exception e) {
System.out.println("連接異常");
CloseUtils.close(socket);
}
return null;
}
static class ReadHandler extends Thread {
private boolean done = false;
private final InputStream inputStream;
ReadHandler(InputStream inputStream) {
this.inputStream = inputStream;
}
@Override
public void run() {
super.run();
try {
// 得到輸入流,用於接收數據
BufferedReader socketInput = new BufferedReader(new InputStreamReader(inputStream));
do {
String str;
try {
System.out.println("客戶端等待讀取數據");
// 客戶端拿到一條數據
str = socketInput.readLine();
} catch (Exception e) {
continue;
}
if (str == null) {
System.out.println("連接已關閉,無法讀取數據!");
break;
}
// 打印到屏幕
System.out.println(str);
} while (!done);
} catch (Exception e) {
if (!done) {
System.out.println("連接異常斷開:" + e.getMessage());
}
} finally {
// 連接關閉
CloseUtils.close(inputStream);
}
}
void exit() {
done = true;
CloseUtils.close(inputStream);
}
}
}
package net.qiujuer.lesson.sample.server.handle;
import net.qiujuer.library.clink.utils.CloseUtils;
import java.io.*;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ClientHandler {
private final SocketChannel socketChannel;
private final ClientReadHandler readHandler;
private final ClientWriteHandler writeHandler;
private final ClientHandlerCallback clientHandlerCallback;
private final String clientInfo;
public ClientHandler(SocketChannel socketChannel, ClientHandlerCallback clientHandlerCallback) throws IOException {
this.socketChannel = socketChannel;
//設置非阻塞模式
socketChannel.configureBlocking(false);
Selector readSelector = Selector.open();
socketChannel.register(readSelector, SelectionKey.OP_READ);
this.readHandler = new ClientReadHandler(readSelector);
Selector writeSelector = Selector.open();
socketChannel.register(writeSelector,SelectionKey.OP_WRITE);
this.writeHandler = new ClientWriteHandler(writeSelector);
this.clientHandlerCallback = clientHandlerCallback;
this.clientInfo =socketChannel.getRemoteAddress().toString();
System.out.println("新客戶端連接:" + clientInfo);
}
public String getClientInfo(){
return clientInfo;
}
public void exit() {
readHandler.exit();
writeHandler.exit();
CloseUtils.close(socketChannel);
System.out.println("客戶端已退出:" + clientInfo);
}
public void send(String str) {
writeHandler.send(str);
}
public void readToPrint() {
readHandler.start();
}
private void exitBySelf() {
exit();
clientHandlerCallback.onSelfClosed(this);
}
public interface ClientHandlerCallback {
//自身關閉通知
void onSelfClosed(ClientHandler handler);
//收到消息時通知
void onNewMessageArrived(ClientHandler handler,String msg);
}
class ClientReadHandler extends Thread {
private boolean done = false;
private final Selector selector;
private final ByteBuffer byteBuffer;
ClientReadHandler(Selector selector) {
this.selector = selector;
this.byteBuffer = ByteBuffer.allocate(256);
}
@Override
public void run() {
super.run();
try {
do {
// 客戶端拿到一條數據
if(selector.select()==0){
if(done){
break;
}
continue;
}
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
if(done){
break;
}
SelectionKey key = iterator.next();
iterator.remove();
if(key.isReadable()){
SocketChannel client = (SocketChannel) key.channel();
//清空操作
byteBuffer.clear();
//讀取
int read = client.read(byteBuffer);
if(read>0){
//丟棄換行符
String str = new String(byteBuffer.array(),0,read -1);
// 打印到屏幕
// System.out.println(str);
//onNewMessageArrived()方法在調用它的線程執行。比如連續給服務器發送了3條消息,按理說這裏應該收到三條消息,然後把消息進行轉發
//一旦在onNewMessageArrived()進行了阻塞,就會導致後面兩條消息接收的延遲。所以,儘量不阻塞主幹的線程。
clientHandlerCallback.onNewMessageArrived(ClientHandler.this,str);
}else {
System.out.println("客戶端已無法讀取數據!");
// 退出當前客戶端
ClientHandler.this.exitBySelf();
break;
}
}
}
} while (!done);
} catch (Exception e) {
if (!done) {
System.out.println("連接異常斷開");
ClientHandler.this.exitBySelf();
}
} finally {
// 連接關閉
CloseUtils.close(selector);
}
}
void exit() {
done = true;
selector.wakeup();
CloseUtils.close(selector);
}
}
class ClientWriteHandler {
private boolean done = false;
private final Selector selector;
private final ByteBuffer byteBuffer;
private final ExecutorService executorService;
ClientWriteHandler(Selector selector) {
this.selector = selector;
this.byteBuffer = ByteBuffer.allocate(256);
this.executorService = Executors.newSingleThreadExecutor();
}
void exit() {
done = true;
CloseUtils.close(selector);
executorService.shutdownNow();
}
void send(String str) {
if(done) {
return;
}
executorService.execute(new WriteRunnable(str));
}
class WriteRunnable implements Runnable {
private final String msg;
WriteRunnable(String msg) {
this.msg = msg+'\n';
}
@Override
public void run() {
if (ClientWriteHandler.this.done) {
return;
}
byteBuffer.clear();
byteBuffer.put(msg.getBytes());
//反轉操作
byteBuffer.flip();
while (!done&&byteBuffer.hasRemaining()) {
try {
int len = socketChannel.write(byteBuffer);
System.out.println("len = "+len+" msg :"+msg);
//len=0 合法
if(len<0){
System.out.println("客戶端已無法發送數據");
ClientHandler.this.exitBySelf();
break;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
運行結果:
服務器可以接收、發送消息
客戶端也可以接收、發送消息
四、put模式
默認情況下,position=0,limit和capacity=容量值
每put一次,position就向後移動一位
五、get模式
position=0
通道從position=0開始讀取,每讀取一次,position後移一位,讀到limit結束
六、clear操作
將position重置爲0,將limit重置爲capacity,但是數據不會清空
七、反轉
將limit的值置爲position的值,將position置爲0,這樣就可以進行讀取操作了
八、讀模式和寫模式