日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

java网络编程阻塞_Java网络编程由浅入深三 一文了解非阻塞通信的图文代码示例详解...

發布時間:2023/12/19 java 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java网络编程阻塞_Java网络编程由浅入深三 一文了解非阻塞通信的图文代码示例详解... 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本文詳細介紹組成非阻塞通信的幾大類:Buffer、Channel、Selector、SelectionKey

非阻塞通信的流程ServerSocketChannel通過open方法獲取ServerSocketChannel,通過ServerSocketChannel設置為非阻塞模式,再通過ServerSocketChannel獲取socket,綁定服務進程監聽端口。服務啟動成功。

然后就是非阻塞通信的精髓了,Selector通過靜態的open()方法獲取到Selector,然后ServerSocketChannel注冊Selection.OP_ACCEPT事件到Selector上。

Selector就會監控事件發生,Selector通過select()監控已發生的SelectionKey對象的數目,通過selectKeys()方法返回對應的selectionKey對象集合。遍歷該集合得到相應的selectionKey對象,通過該對象的channel()方法獲取關聯的ServerSocketChannel對象, 通過selector()方法就可以獲取關聯的Selector對象。

通過上面獲取的ServerSocketChannel執行accept()方法獲取SocketChannel,再通過SocketChannel設置為非阻塞模式,在將SocketChannel注冊到上面創建的Selector上,注冊SelectionKey.OP_READ |SelectionKey.OP_WRITE 事件。

Selector將在監控對應上面綁定的事件,監控到對應的事件的話執行讀和寫的操作。

示例代碼:

上面描述了服務端非阻塞方式通信的一個流程,下面通過具體代碼實現:/**

* 非阻塞模式

*

*/public class EchoServer2 {

private Selector selector = null;

private ServerSocketChannel serverSocketChannel = null;

private int port = 8001;

private Charset charset = Charset.forName("UTF-8");

public EchoServer2() throws IOException {

selector = Selector.open();

serverSocketChannel = ServerSocketChannel.open(); //服務器重啟的時候,重用端口

serverSocketChannel.socket().setReuseAddress(true); //設置非阻塞模式

serverSocketChannel.configureBlocking(false);

serverSocketChannel.socket().bind(new InetSocketAddress(port));

System.out.println("服務器啟動成功");

} /**

* 服務方法

*/

public void service() throws IOException {

serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

while (selector.select() > 0) {

Set readyKes = selector.selectedKeys();

Iterator it = readyKes.iterator();

while (it.hasNext()) {

SelectionKey key = null;

try {

key = (SelectionKey) it.next();

it.remove();

if (key.isAcceptable()) {

System.out.println("連接事件");

//連接事件

ServerSocketChannel ssc = (ServerSocketChannel) key.channel();

SocketChannel socketChannel = ssc.accept();

System.out.println("接收到客戶連接,來自:" + socketChannel.socket().getInetAddress() + " : " + socketChannel.socket().getPort());

socketChannel.configureBlocking(false);

ByteBuffer buffer = ByteBuffer.allocate(1024);

socketChannel.register(selector, SelectionKey.OP_READ |

SelectionKey.OP_WRITE, buffer);

} else if (key.isReadable()) {

//接收數據

receive(key);

} else if (key.isWritable()) {

//發送數據

send(key);

}

} catch (IOException e) {

e.printStackTrace();

try {

if (key != null) {

key.cancel();

key.channel().close();

}

}catch (IOException ex){

ex.printStackTrace();

}

}

}

}

} private void send(SelectionKey key) throws IOException {

ByteBuffer buffer = (ByteBuffer) key.attachment();

SocketChannel channel = (SocketChannel) key.channel();

buffer.flip(); //把極限設置為位置,把位置設置為0

String data = decode(buffer);

if (data.indexOf("\r\n") == -1) {

return;

}

String outputData = data.substring(0, data.indexOf("\n") + 1);

System.out.println("請求數據:" + outputData);

ByteBuffer outputBuffer = encode("echo:" + outputData);

while (outputBuffer.hasRemaining()) {

channel.write(outputBuffer);

}

ByteBuffer temp = encode(outputData);

buffer.position(temp.limit());

buffer.compact(); if (outputData.equals("bye\r\n")) {

key.cancel();

channel.close();

System.out.println("關閉與客戶的連接");

}

} private String decode(ByteBuffer buffer) {

CharBuffer charBuffer = charset.decode(buffer); return charBuffer.toString();

} private ByteBuffer encode(String s) { return charset.encode(s);

} private void receive(SelectionKey key) throws IOException {

ByteBuffer buffer = (ByteBuffer) key.attachment();

SocketChannel socketChannel = (SocketChannel) key.channel();

ByteBuffer readBuff = ByteBuffer.allocate(32);

socketChannel.read(readBuff);

readBuff.flip();

buffer.limit(buffer.capacity());

buffer.put(readBuff);

} public static void main(String[] args) throws IOException { new EchoServer2().service();

}

}/**

* 創建非阻塞客戶端

*

*/public class EchoClient2 {

private SocketChannel socketChannel; private int port = 8001; private Selector selector; private ByteBuffer sendBuffer = ByteBuffer.allocate(1024); private ByteBuffer receiveBuffer = ByteBuffer.allocate(1024); private Charset charset = Charset.forName("UTF-8"); public EchoClient2() throws IOException {

socketChannel = SocketChannel.open();

InetSocketAddress inetSocketAddress = new InetSocketAddress(InetAddress.getLocalHost(), port);

socketChannel.connect(inetSocketAddress);//

socketChannel.configureBlocking(false);//設置為非阻塞模式

System.out.println("與服務器連接成功");

selector = Selector.open();

} public static void main(String[] args) throws IOException { final EchoClient2 client = new EchoClient2();

Thread receiver = new Thread(new Runnable() { @Override

public void run() {

client.receiveFromUser();

}

});

receiver.start();

client.talk();

} private void receiveFromUser() { try {

System.out.println("請輸入數據:");

BufferedReader localReader = new BufferedReader(new InputStreamReader(System.in));

String msg = null; while ((msg = localReader.readLine()) != null) {

System.out.println("用戶輸入的數據:" + msg); synchronized (sendBuffer) {

sendBuffer.put(encode(msg + "\r\n"));

} if (msg.equalsIgnoreCase("bye")) { break;

}

}

} catch (IOException e) {

e.printStackTrace();

}

} private ByteBuffer encode(String s) { return charset.encode(s);

} private void talk() throws IOException {

socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE); while (selector.select() > 0) {

Set keys = selector.selectedKeys();

Iterator it = keys.iterator(); while (it.hasNext()) {

SelectionKey key = null; try {

key = it.next();

it.remove(); if (key.isReadable()) { //System.out.println("讀事件");

//讀事件

receive(key);

} if (key.isWritable()) { // System.out.println("寫事件");

//寫事件

send(key);

}

} catch (IOException e) {

e.printStackTrace(); if (key != null) {

key.cancel();

key.channel().close();

}

}

}

}

} private void send(SelectionKey key) throws IOException {

SocketChannel channel = (SocketChannel) key.channel(); synchronized (sendBuffer) {

sendBuffer.flip();//把極限設為位置,把位置設為零

channel.write(sendBuffer);

sendBuffer.compact();//刪除已經發送的數據。

}

} private void receive(SelectionKey key) throws IOException {

SocketChannel channel = (SocketChannel) key.channel();

channel.read(receiveBuffer);

receiveBuffer.flip();//將limit的值設置為position的值,將position的值設置為0

String receiveData = decode(receiveBuffer); if (receiveData.indexOf("\n") == -1) { return;

}

String outputData = receiveData.substring(0, receiveData.indexOf("\n") + 1);

System.out.println("響應數據:" + outputData); if (outputData.equalsIgnoreCase("echo:bye\r\n")) {

key.cancel();

socketChannel.close();

;

System.out.println("關閉與服務器的連接");

selector.close();

System.exit(0);

}

ByteBuffer temp = encode(outputData);

receiveBuffer.position(temp.limit());

receiveBuffer.compact();//刪除已經打印的數據

} private String decode(ByteBuffer receiveBuffer) {

CharBuffer buffer = charset.decode(receiveBuffer); return buffer.toString();

}

}

實現非阻塞通信的方式緩沖區

通道

Selector

緩沖區作用:減少物理讀寫次數,減少內存創建和銷毀次數。 緩沖區的屬性:capacity(最大容量)、limit(實際容量)、position(當前位置)。PS:其他地方是翻譯成capacity(容量)、limit(極限)、position位置),我個人覺得翻譯成上面的更好理解,為啥通過下面的方法解析和圖解就可明白。當然最好通過英文表達這樣最清楚。

三個屬性的關系為:capacity≥limit≥position≥0

圖解關系如下:

緩沖區類結構:

java.nio.ByteBuffer類是一個抽象類,不能被實例化。但是提供了8個具體的實現類,其中最基本的的緩沖區是ByteBuffer,它存放的數據單元是字節。

常用方法:

clear():把limit設置為capacity,再把位置設為0

flip():把limit設置為position,再把位置設置為0。

rewind():不改變limit,把位置設為0。

allocate():創建一個緩沖中,方法參數指定緩沖區大小

compact():將緩沖區的當前位置和界限之間的字節(如果有)復制到緩沖區的開始處。

測試上述方法:

測試clear()方法@Test

public void testClear() { //創建一個10chars大小的緩沖區,默認情況下limit和capacity是相等的

CharBuffer buffer = CharBuffer.allocate(10);

System.out.println("創建默認情況");

printBufferInfo(buffer);

buffer.limit(8);//修改limit的值

System.out.println("修改limit后");

printBufferInfo(buffer); // clear():把limit設置為capacity,再把位置設為0

buffer.clear();

System.out.println("執行clear()方法后");

printBufferInfo(buffer);

}

執行結果如下:

測試flip()方法:@Test

public void testFlip() {

CharBuffer buffer = CharBuffer.allocate(10);

System.out.println("創建默認情況");

printBufferInfo(buffer); //put的方法會修改position的值

buffer.put('H');

buffer.put('E');

buffer.put('L');

buffer.put('L');

buffer.put('O');

System.out.println("調用put方法后:");

printBufferInfo(buffer); //flip():把limit設置為position,再把位置設置為0。

buffer.flip();

System.out.println("調用flip方法后:");

printBufferInfo(buffer);

}

執行結果如下:

測試rewind()方法@Test

public void testRewind() {

CharBuffer buffer = CharBuffer.allocate(10);

System.out.println("創建默認情況");

printBufferInfo(buffer); //put的方法會修改position的值

buffer.put('H');

buffer.put('E');

buffer.put('L');

buffer.put('L');

buffer.put('O');

buffer.limit(8);

System.out.println("調用put、limit方法后:");

printBufferInfo(buffer); //rewind():不改變limit,把位置設為0。

buffer.rewind();

System.out.println("調用rewind方法后:");

printBufferInfo(buffer);

}

執行結果如下:

測試compact()方法@Test

public void testCompact(){

CharBuffer buffer = CharBuffer.allocate(10);

System.out.println("創建默認情況");

printBufferInfo(buffer); //put的方法會修改position的值

buffer.put('H');

buffer.put('E');

buffer.put('L');

buffer.put('L');

buffer.put('O');

buffer.limit(8);//修改limit的值

System.out.println("調用put和limit方法后:");

printBufferInfo(buffer);

System.out.println("調用compact方法后:"); //將緩沖區的當前位置和界限之間的字節(如果有)復制到緩沖區的開始處。

buffer.compact();

printBufferInfo(buffer);

}

這是JDK中介紹該方法的作用:

將緩沖區的當前位置和界限之間的字節(如果有)復制到緩沖區的開始處。即將索引 p = position() 處的字節復制到索引 0 處,將索引 p + 1 處的字節復制到索引 1 處,依此類推,直到將索引 limit() - 1 處的字節復制到索引 n = limit() - 1 - p 處。然后將緩沖區的位置設置為 n+1,并將其界限設置為其容量。如果已定義了標記,則丟棄它。

官方表示的太難理解了:

將緩沖區的當前位置和界限之間的字節(如果有)復制到緩沖區的開始處。并將limit(實際容量)設置為 capacity(最大容量)。執行compact()方法前,limit的值是:8,position的值是:5。按照上面描述的執行完compact()后,position的值計算方式是:n+1;n=limit-1-p;所有n=8-1-5=2,最后position的值為:2+1=3。和程序運行的結果一致。

可以在這種情況:從緩沖區寫入數據之后調用此方法,以防寫入不完整。buf.clear(); // Prepare buffer for use

while (in.read(buf) >= 0 || buf.position != 0) {

buf.flip();

out.write(buf);

buf.compact(); // In case of partial write

}

如果out.write()方法沒有將緩存中的數據讀取完,這個時候的position位置指向的是剩余數據的位置。達到防止寫入不完整。

通道作用: 連接緩沖區與數據源或數據目的地。

常用類:

Channel

接口有下面兩個子接口ReadableByteChannel和WritableByteChannel和一個抽象實現類SelectableChannel。

在ReadableByteChannel接口中申明了read(ByteBuffer

dst)方法。在WritableByteChannel接口中申明了write(ByteBuffer[]

srcs):方法。SelectableChannel抽象類中主要方法,configureBlocking(boolean

block)、register();方法。 ByteChannel

接口繼承了ReadableChannel和WritableChannel。所以ByteChannel具有讀和寫的功能。

ServerSocketChannel繼承了SelectableChannel類抽象類,所以SocketChannel具有設置是否是阻塞模式、向selector注冊事件功能。

SocketChannel也繼承了SelectableChannel類還實現ByteChannel接口,所以SocketChannel具有設置是否是阻塞模式、向selector注冊事件、從緩沖區讀寫數據的功能。

通過類圖展現:

Selector類:作用:只要ServerSocketChannel及SocketChannel向Selector注冊了特定的事件,Selector就會監聽這些事件的發生。

流程:

Selector通過靜態的open()方法創建一個Selector對象,SelectableChannel類向Selector注冊了特定的事件。Selector就會監控這些事件發生,Selector通過select()監控已發生的SelectionKey對象的數目,通過selectKeys()方法返回對應的selectionKey對象集合。遍歷該集合得到相應的selectionKey對象,通過該對象的channel()方法獲取關聯的SelectableChannel對象,

通過selector()方法就可以獲取關聯的Selector對象。

Note:

當Selector的select()方法還有一個重載方式:select(long timeout)。并且該方法采用阻塞的工作方式,如果相關事件的selectionKey對象的數目一個也沒有,就進入阻塞狀態。知道出現以下情況之一,才從select()方法中返回。至少有一個SelectionKey的相關事件已經發生。

其他線程調用了Selector的wakeup()方法,導致執行select()方法的線程立即返回。

當前執行的select()方法的線程被中斷。

超出了等待時間。僅限調用select(long timeout)方法時出現。如果沒有設置超時時間,則永遠不會超時。Selector類有兩個非常重要的方法: 靜態方法open(),這是Selector的靜態工廠方法,創建一個Selector對象。

selectedKeys()方法返回被Selector捕獲的SelectionKey的集合。

SelectionKey類作用:

ServerSocketChannel或SocketChannel通過register()方法向Selector注冊事件時,register()方法會創建一個SelectionKey對象,該對象是用來跟蹤注冊事件的句柄。在SelectionKey對象的有效期間,Selector會一直監控與SelectionKey對象相關的事件,如果事件發生,就會把SelectionKey對象添加到Selected-keys集合中。

SelectionKey中定義的事件: 定義了4種事件:

1、SelectionKey.OP_ACCEPT:接收連接就緒事件,表示服務器監聽到了客戶連接,服務器可以接收這個連接了。常量值為16.

2、SelectionKey.OP_CONNECT:連接就緒事件,表示客戶與服務器的連接已經建立成功。常量值為8.

3、SelectionKey.OP_READ:讀就緒事件,表示通道中已經有了可讀數據可以執行讀操作。常量值為1.

4、SelectionKey.OP_WRITE:寫就緒事件,表示已經可以向通道寫數據了。常量值為4.

常用方法:

channel()方法:返回與它關聯的SelectedChannel(包括ServerSocketChannel和SocketChannel)。

selector()方法:返回與它關聯的Selector對象。

它們之間的關系如下:

以上就是Java網絡編程由淺入深三 一文了解非阻塞通信的圖文代碼示例詳解的內容,更多相關內容請關注PHP中文網(www.php.cn)!

本文原創發布php中文網,轉載請注明出處,感謝您的尊重!

總結

以上是生活随笔為你收集整理的java网络编程阻塞_Java网络编程由浅入深三 一文了解非阻塞通信的图文代码示例详解...的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。