国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - Java NIO深入分析

Java NIO深入分析

2021-02-28 11:23segmentfault Java教程

本篇技術文章主要對Java新api(New IO)做了詳細深入的講解,有助于程序對NIO有更加深入的理解。

以下我們系統通過原理,過程等方便給大家深入的簡介了java nio的函數機制以及用法等,學習下吧。

前言

本篇主要講解java中的io機制

分為兩塊:
第一塊講解多線程下的io機制
第二塊講解如何在io機制下優化cpu資源的浪費(new io)

echo服務器

單線程下的socket機制就不用我介紹了,不懂得可以去查閱下資料
那么多線程下,如果進行套接字的使用呢?
我們使用最簡單的echo服務器來幫助大家理解

首先,來看下多線程下服務端和客戶端的工作流程圖:

Java NIO深入分析

可以看到,多個客戶端同時向服務端發送請求

服務端做出的措施是開啟多個線程來匹配相對應的客戶端

并且每個線程去獨自完成他們的客戶端請求

原理講完了我們來看下是如何實現的

在這里我寫了一個簡單的服務器

用到了線程池的技術來創建線程(具體代碼作用我已經加了注釋):

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class myserver {
  private static executorservice executorservice = executors.newcachedthreadpool();  //創建一個線程池
  private static class handlemsg implements runnable{   //一旦有新的客戶端請求,創建這個線程進行處理
  socket client;   //創建一個客戶端
  public handlemsg(socket client){  //構造傳參綁定
   this.client = client;
  }
  @override
  public void run() {
   bufferedreader bufferedreader = null//創建字符緩存輸入流
   printwriter printwriter = null;   //創建字符寫入流
   try {
    bufferedreader = new bufferedreader(new inputstreamreader(client.getinputstream()));  //獲取客戶端的輸入流
    printwriter = new printwriter(client.getoutputstream(),true);   //獲取客戶端的輸出流,true是隨時刷新
    string inputline = null;
    long a = system.currenttimemillis();
    while ((inputline = bufferedreader.readline())!=null){
     printwriter.println(inputline);
    }
    long b = system.currenttimemillis();
    system.out.println("此線程花費了:"+(b-a)+"秒!");
   } catch (ioexception e) {
    e.printstacktrace();
   }finally {
    try {
     bufferedreader.close();
     printwriter.close();
     client.close();
    } catch (ioexception e) {
     e.printstacktrace();
    }
   }
  }
 }
 public static void main(string[] args) throws ioexception {   //服務端的主線程是用來循環監聽客戶端請求
  serversocket server = new serversocket(8686);  //創建一個服務端且端口為8686
  socket client = null;
  while (true){   //循環監聽
   client = server.accept();  //服務端監聽到一個客戶端請求
   system.out.println(client.getremotesocketaddress()+"地址的客戶端連接成功!");
   executorservice.submit(new handlemsg(client));  //將該客戶端請求通過線程池放入handlmsg線程中進行處理
  }
 }
}

 

上述代碼中我們使用一個類編寫了一個簡單的echo服務器
在主線程中用死循環來開啟端口監聽

簡單客戶端

有了服務器,我們就可以對其進行訪問,并且發送一些字符串數據
服務器的功能是返回這些字符串,并且打印出線程占用時間

下面來寫個簡單的客戶端來響應服務端:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class myclient {
 public static void main(string[] args) throws ioexception {
  socket client = null;
  printwriter printwriter = null;
  bufferedreader bufferedreader = null;
  try {
   client = new socket();
   client.connect(new inetsocketaddress("localhost",8686));
   printwriter = new printwriter(client.getoutputstream(),true);
   printwriter.println("hello");
   printwriter.flush();
   bufferedreader = new bufferedreader(new inputstreamreader(client.getinputstream()));   //讀取服務器返回的信息并進行輸出
   system.out.println("來自服務器的信息是:"+bufferedreader.readline());
  } catch (ioexception e) {
   e.printstacktrace();
  }finally {
   printwriter.close();
   bufferedreader.close();
   client.close();
  }
 }
}

 

代碼中,我們用字符流發送了一個hello字符串過去,如果代碼沒問題
服務器會返回一個hello數據,并且打印出我們設置的日志信息

echo服務器結果展示

我們來運行:

1.打開server,開啟循環監聽:

Java NIO深入分析

2.打開一個客戶端:

Java NIO深入分析

可以看到客戶端打印出了返回結果

3.查看服務端日志:

Java NIO深入分析

很好,一個簡單的多線程套接字編程就實現了

但是試想一下:

如果一個客戶端請求中,在io寫入到服務端過程中加入sleep,

使每個請求占用服務端線程10秒

然后有大量的客戶端請求,每個請求都占用那么長時間

那么服務端的并能能力就會大幅度下降

這并不是因為服務端有多少繁重的任務,而僅僅是因為服務線程在等待io(因為accept,read,write都是阻塞式的)

讓高速運行的cpu去等待及其低效的網絡io是非常不合算的行為

這時候該怎么辦?

nio

new io成功的解決了上述問題,它是怎樣解決的呢?

io處理客戶端請求的最小單位是線程

而nio使用了比線程還小一級的單位:通道(channel)

可以說,nio中只需要一個線程就能完成所有接收,讀,寫等操作

要學習nio,首先要理解它的三大核心

selector,選擇器

buffer,緩沖區

channel,通道

博主不才,畫了張丑圖給大家加深下印象 ^ . ^

Java NIO深入分析

再給一張tcp下的nio工作流程圖(好難畫的線條...)

Java NIO深入分析

大家大致看懂就行,我們一步步來

buffer

首先要知道什么是buffer

在nio中數據交互不再像io機制那樣使用流

而是使用buffer(緩沖區)

博主覺得圖才是最容易理解的

所以...

Java NIO深入分析

可以看出buffer在整個工作流程中的位置

來點實際點的,上面圖中的具體代碼如下:

1.首先給buffer分配空間,以字節為單位

?
1
bytebuffer bytebuffer = bytebuffer.allocate(1024);

 

創建一個bytebuffer對象并且指定內存大小

2.向buffer中寫入數據:

?
1
2
1).數據從channel到buffer:channel.read(bytebuffer);
2).數據從client到buffer:bytebuffer.put(...);

 

3.從buffer中讀取數據:

?
1
2
1).數據從buffer到channel:channel.write(bytebuffer);
2).數據從buffer到server:bytebuffer.get(...);

 

selector

選擇器是nio的核心,它是channel的管理者

通過執行select()阻塞方法,監聽是否有channel準備好

一旦有數據可讀,此方法的返回值是selectionkey的數量

所以服務端通常會死循環執行select()方法,直到有channl準備就緒,然后開始工作

每個channel都會和selector綁定一個事件,然后生成一個selectionkey的對象

需要注意的是:

channel和selector綁定時,channel必須是非阻塞模式

而filechannel不能切換到非阻塞模式,因為它不是套接字通道,所以filechannel不能和selector綁定事件

在nio中一共有四種事件:

1.selectionkey.op_connect:連接事件

2.selectionkey.op_accept:接收事件

3.selectionkey.op_read:讀事件

4.selectionkey.op_write:寫事件

channel

共有四種通道:

filechannel:作用于io文件流

datagramchannel:作用于udp協議

socketchannel:作用于tcp協議

serversocketchannel:作用于tcp協議

本篇文章通過常用的tcp協議來講解nio

我們以serversocketchannel為例:

打開一個serversocketchannel通道

?
1
serversocketchannel serversocketchannel = serversocketchannel.open();

 

關閉serversocketchannel通道:

?
1
serversocketchannel.close();

 

循環監聽socketchannel:

?
1
2
3
4
while(true){
 socketchannel socketchannel = serversocketchannel.accept();
 clientchannel.configureblocking(false);
}

 

clientchannel.configureblocking(false);語句是將此通道設置為非阻塞,也就是異步
自由控制阻塞或非阻塞便是nio的特性之一

selectionkey

selectionkey是通道和選擇器交互的核心組件

比如在socketchannel上綁定一個selector,并注冊為連接事件:

?
1
2
3
4
socketchannel clientchannel = socketchannel.open();
clientchannel.configureblocking(false);
clientchannel.connect(new inetsocketaddress(port));
clientchannel.register(selector, selectionkey.op_connect);

 

核心在register()方法,它返回一個selectionkey對象

來檢測channel事件是那種事件可以使用以下方法:

?
1
2
3
4
selectionkey.isacceptable();
selectionkey.isconnectable();
selectionkey.isreadable();
selectionkey.iswritable();

 

服務端便是通過這些方法 在輪詢中執行相對應操作

當然通過channel與selector綁定的key也可以反過來拿到他們

?
1
2
channel channel = selectionkey.channel();
selector selector = selectionkey.selector();

 

在channel上注冊事件時,我們也可以順帶綁定一個buffer:

?
1
clientchannel.register(key.selector(), selectionkey.op_read,bytebuffer.allocatedirect(1024));

 

或者綁定一個object:

?
1
2
selectionkey.attach(object);
object anthorobj = selectionkey.attachment();

 

nio的tcp服務端

講了這么多,都是理論
我們來看下最簡單也是最核心的代碼(加那么多注釋很不優雅,但方便大家看懂):

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package cn.blog.test.niotest;
import java.io.ioexception;
import java.net.inetsocketaddress;
import java.nio.bytebuffer;
import java.nio.channels.*;
import java.nio.charset.charset;
import java.util.iterator;
import java.util.set;
public class mynioserver {
 private selector selector;   //創建一個選擇器
 private final static int port = 8686;
 private final static int buf_size = 10240;
 private void initserver() throws ioexception {
  //創建通道管理器對象selector
  this.selector=selector.open();
  //創建一個通道對象channel
  serversocketchannel channel = serversocketchannel.open();
  channel.configureblocking(false);  //將通道設置為非阻塞
  channel.socket().bind(new inetsocketaddress(port));  //將通道綁定在8686端口
  //將上述的通道管理器和通道綁定,并為該通道注冊op_accept事件
  //注冊事件后,當該事件到達時,selector.select()會返回(一個key),如果該事件沒到達selector.select()會一直阻塞
  selectionkey selectionkey = channel.register(selector,selectionkey.op_accept);
  while (true){  //輪詢
   selector.select();   //這是一個阻塞方法,一直等待直到有數據可讀,返回值是key的數量(可以有多個)
   set keys = selector.selectedkeys();   //如果channel有數據了,將生成的key訪入keys集合中
   iterator iterator = keys.iterator();  //得到這個keys集合的迭代器
   while (iterator.hasnext()){    //使用迭代器遍歷集合
    selectionkey key = (selectionkey) iterator.next();  //得到集合中的一個key實例
    iterator.remove();   //拿到當前key實例之后記得在迭代器中將這個元素刪除,非常重要,否則會出錯
    if (key.isacceptable()){   //判斷當前key所代表的channel是否在acceptable狀態,如果是就進行接收
     doaccept(key);
    }else if (key.isreadable()){
     doread(key);
    }else if (key.iswritable() && key.isvalid()){
     dowrite(key);
    }else if (key.isconnectable()){
     system.out.println("連接成功!");
    }
   }
  }
 }
 public void doaccept(selectionkey key) throws ioexception {
  serversocketchannel serverchannel = (serversocketchannel) key.channel();
  system.out.println("serversocketchannel正在循環監聽");
  socketchannel clientchannel = serverchannel.accept();
  clientchannel.configureblocking(false);
  clientchannel.register(key.selector(),selectionkey.op_read);
 }
 public void doread(selectionkey key) throws ioexception {
  socketchannel clientchannel = (socketchannel) key.channel();
  bytebuffer bytebuffer = bytebuffer.allocate(buf_size);
  long bytesread = clientchannel.read(bytebuffer);
  while (bytesread>0){
   bytebuffer.flip();
   byte[] data = bytebuffer.array();
   string info = new string(data).trim();
   system.out.println("從客戶端發送過來的消息是:"+info);
   bytebuffer.clear();
   bytesread = clientchannel.read(bytebuffer);
  }
  if (bytesread==-1){
   clientchannel.close();
  }
 }
 public void dowrite(selectionkey key) throws ioexception {
  bytebuffer bytebuffer = bytebuffer.allocate(buf_size);
  bytebuffer.flip();
  socketchannel clientchannel = (socketchannel) key.channel();
  while (bytebuffer.hasremaining()){
   clientchannel.write(bytebuffer);
  }
  bytebuffer.compact();
 }
 public static void main(string[] args) throws ioexception {
  mynioserver mynioserver = new mynioserver();
  mynioserver.initserver();
 }
}

 

我打印了監聽channel,告訴大家serversocketchannel是在什么時候開始運行的

如果配合nio客戶端的debug,就能很清楚的發現,進入select()輪詢前

雖然已經有了accept事件的key,但select()默認并不會去調用

而是要等待有其它感興趣事件被select()捕獲之后,才會去調用accept的selectionkey

這時候serversocketchannel才開始進行循環監聽

也就是說一個selector中,始終保持著serversocketchannel的運行

serverchannel.accept();真正做到了異步(在initserver方法中的channel.configureblocking(false);)

如果沒有接受到connect,會返回一個null

如果成功連接了一個socketchannel,則此socketchannel會注冊寫入(read)事件

并且設置為異步

nio的tcp客戶端

有服務端必定有客戶端

其實如果能完全理解了服務端

客戶端的代碼大同小異

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package cn.blog.test.niotest;
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.util.iterator;
public class mynioclient {
 private selector selector;   //創建一個選擇器
 private final static int port = 8686;
 private final static int buf_size = 10240;
 private static bytebuffer bytebuffer = bytebuffer.allocate(buf_size);
 private void initclient() throws ioexception {
  this.selector = selector.open();
  socketchannel clientchannel = socketchannel.open();
  clientchannel.configureblocking(false);
  clientchannel.connect(new inetsocketaddress(port));
  clientchannel.register(selector, selectionkey.op_connect);
  while (true){
   selector.select();
   iterator<selectionkey> iterator = selector.selectedkeys().iterator();
   while (iterator.hasnext()){
    selectionkey key = iterator.next();
    iterator.remove();
    if (key.isconnectable()){
     doconnect(key);
    }else if (key.isreadable()){
     doread(key);
    }
   }
  }
 }
 public void doconnect(selectionkey key) throws ioexception {
  socketchannel clientchannel = (socketchannel) key.channel();
  if (clientchannel.isconnectionpending()){
   clientchannel.finishconnect();
  }
  clientchannel.configureblocking(false);
  string info = "服務端你好!!";
  bytebuffer.clear();
  bytebuffer.put(info.getbytes("utf-8"));
  bytebuffer.flip();
  clientchannel.write(bytebuffer);
  //clientchannel.register(key.selector(),selectionkey.op_read);
  clientchannel.close();
 }
 public void doread(selectionkey key) throws ioexception {
  socketchannel clientchannel = (socketchannel) key.channel();
  clientchannel.read(bytebuffer);
  byte[] data = bytebuffer.array();
  string msg = new string(data).trim();
  system.out.println("服務端發送消息:"+msg);
  clientchannel.close();
  key.selector().close();
 }
 public static void main(string[] args) throws ioexception {
  mynioclient mynioclient = new mynioclient();
  mynioclient.initclient();
 }
}

 

輸出結果

這里我打開一個服務端,兩個客戶端:

Java NIO深入分析

接下來,你可以試下同時打開一千個客戶端,只要你的cpu夠給力,服務端就不可能因為阻塞而降低性能

以上便是java nio的基礎詳解,如果大家還有什么不明白的地方可以在下方的留言區域討論。

原文鏈接:https://segmentfault.com/a/1190000012316621

延伸 · 閱讀

精彩推薦
Weibo Article 1 Weibo Article 2 Weibo Article 3 Weibo Article 4 Weibo Article 5 Weibo Article 6 Weibo Article 7 Weibo Article 8 Weibo Article 9 Weibo Article 10 Weibo Article 11 Weibo Article 12 Weibo Article 13 Weibo Article 14 Weibo Article 15 Weibo Article 16 Weibo Article 17 Weibo Article 18 Weibo Article 19 Weibo Article 20 Weibo Article 21 Weibo Article 22 Weibo Article 23 Weibo Article 24 Weibo Article 25 Weibo Article 26 Weibo Article 27 Weibo Article 28 Weibo Article 29 Weibo Article 30 Weibo Article 31 Weibo Article 32 Weibo Article 33 Weibo Article 34 Weibo Article 35 Weibo Article 36 Weibo Article 37 Weibo Article 38 Weibo Article 39 Weibo Article 40
主站蜘蛛池模板: 99亚洲 | 久久国产免费 | 日韩欧美中文字幕在线视频 | 国产精品久久久久久久久久 | 久久成人av | 欧美久久久久久久 | 亚洲成人福利 | 欧美久久视频 | www.青青草原| 久久精品国产清自在天天线 | 成人av观看 | 视频一区 中文字幕 | 日韩视频不卡 | 色狠狠一区 | 免费看的av | 国产看片网站 | 精品天堂| 国产精品高清在线观看 | 成人免费av | 久久久久久国产 | 国产麻豆乱码精品一区二区三区 | 在线视频a | 爱干视频 | a一级免费视频 | 欧美日韩一区二区三区在线观看 | 亚洲精品第一区在线观看 | 91久久精品一区二区二区 | 欧美二区三区 | 日本精品在线观看 | 在线播放高清视频www | 国产黄色一级大片 | 国产黄色网址在线观看 | 久久国产综合 | 精品一区二区久久久久久久网站 | 91大神xh98hx在线播放 | 在线一区 | 国产精品毛片无码 | 日本不卡免费一区二区三区综合久久 | 精品在线看 | 日韩一区二区三区电影在线观看 | 午夜电影一区 |