Java Se : Java NIO(服務端)與BIO(客戶端)通信


 

Java目前有三種IO相關的API了,下面簡單的說一下:

BIO,阻塞IO,最常用的Java IO API,提供一般的流的讀寫功能。相信學習Java的人,都用過。

NIO,非阻塞IO,在JDK1.4中開始出現,大量應用與服務器端編程,用於提高並發訪問的性能,常用的NIO框架有Netty,Mina。

AIO,異步IO,在JDK1.7開始出現。還沒有了解過,等以后了解了再說。

 

阻塞、非阻塞,同步、異步

在寫這篇文章前,在網上了解了一下,其中爭議最的問題要數阻塞、非阻塞怎么理解,異步、同步怎么理解。

       由於每個人想法的不同,很難達到一個一致的答案,又沒有真正的大牛出來給這一個准確的定義。這里也簡單的說一下,我對這兩組名詞的理解。

 

1)阻塞、非阻塞

       我認為,BIO,NIO沒有大家想的那么復雜,就是底層實現中進行數據的讀寫(IO)采取的兩種方案,只不過非阻塞讀寫要比阻塞IO讀寫更快一些。

bio中的InputStream#read()是一個block方法。

 

2)同步、異步

       同步與異步,我認為說的並不是IO本身,我認為說的是程序采用的編程模型,也就是說采用的是同步的編程模型還是異步的編程模型。

      

       BIO、NIO,他們的區別是操作系統讀寫數據采用的方式,他們是Java中的概念,在Java領域,他們的底層實現采用的是同步的編程模型。所以說BIO、NIO都是同步的。

       AIO的底層實現應當是異步的編程模型,所以說它是異步IO。

 

這里我只是闡述了我對它們的理解,沒有與大家爭論到底怎么去理解他們。也許我沒有大家想的那么深遠,畢竟我只是學習了NIO不到一天時間而已。

 

 

 

 

針對BIO、NIO,服務器編程如何提高性能

       一個程序運行的快慢,一般有會受到兩個因素的影響:1)程序代碼是否高效,2)IO讀寫是否高效。曾經看過這么一幅圖,大致內容是:一幫不同角色的人(程序員、運維、項目經理等角色的人)在一起討論一個應用程序效率地下的問題。

程序員說的是:給我3個月時間,我能夠讓程序運行效率提高,當然了,我要調整代碼的整體結構…

       運維說:…

       項目經理說:換用讀寫更快的硬件設備解決這個問題。

 

       故事我已經無法還原,但是這個故事說的內容就是程序優化帶來的效率的提升遠不及提高IO速度帶來的提升。

 

相比於BIO,NIO就是從讀寫來提升效率的。性能對於服務器來說尤為重要,服務器端編程並不是都采用了NIO編程。

      

       Tomcat服務器內部,就有BIO、NIO兩種方式。

 

1)BIO如何提高並發訪問

 

BIO,是一種阻塞IO,服務器端使用BIO進行數據讀寫時,一般都是采用了一個Socket請求對應一個Thread的方式來提高性能的。

       但是一台服務器上,可以跑的線程數量也是有限制的:線程不是越多越好,畢竟線程間的切換,也是有不小的開銷。也不是越少越好,線程太少,極端情況下一個線程,如果用一個線程來解決用戶的並發訪問,服務器接收一個客戶的請求時,其他人都要處於等待狀態。你訪問網頁,多數情況下超過5秒,估計你就關掉它了吧。

  或者采用線程池方案。

 

2)采用NIO編程時 如何提高並發訪問

采用選擇器輪詢可用通道,讀寫數據。具體的怎么做的就不說了,網上一大坨一大坨的,雖然網上大家寫的大多是copy別人的。下面給會出一個例子,所以這里就不多說了,不知道的可以網上找相關的文章。

 

一個Thread下開一個Selector,一個Selector處理多個Socket通道(也就是多個用於請求),這樣就是一個Thread線程可以同時處理多個用戶請求。

 

 

孰優孰劣

       假若說,服務器設置同時處理1000個用戶請求(也就是1000個處理用戶請求的線程)。假若有10000個人來發請求。

如果采用BIO API編程,那么就同時只能為1000個人服務,其他的9000人就處於等待狀態。

如果采用NIO API編程,也開啟1000個線程,因為一個Thread可以同時處理多個用戶請求,咱不說讓它處理太多了,就處理10個吧,這樣算下來,這個10000個用戶請求,就都可以處理了。

 

BIO(客戶端)與NIO(服務端)通信

       今天學習了NIO,就用NIO來處理瀏覽器用戶請求吧。瀏覽器發送的肯定不是采用NIO API發送Socket請求的,肯定是使用了阻塞式IO,也就是對應於Java中的BIO了。

 

package com.fjn.other.nio.socket;

 

import java.io.IOException;

import java.io.InputStream;

import java.io.OutputStream;

import java.net.InetSocketAddress;

import java.net.ServerSocket;

import java.net.Socket;

import java.nio.ByteBuffer;

import java.nio.channels.SelectionKey;

import java.nio.channels.Selector;

import java.nio.channels.ServerSocketChannel;

import java.nio.channels.SocketChannel;

import java.util.ArrayList;

import java.util.Collection;

import java.util.Iterator;

import java.util.concurrent.Callable;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

 

@SuppressWarnings({ "unchecked" })

public class NioServer {

       ServerSocketChannel serverChannel;

       ServerSocket serverSocket;

       public final int port;

       private Selector selector;

       ByteBuffer buffer = ByteBuffer.allocate(1024);

 

       NioServer(final int port) {

              this.port = port;

       }

 

       void init() throws Exception {

 

              // 創建 ServerSocketChannel、ServerSocket

              serverChannel = ServerSocketChannel.open();

              serverSocket = serverChannel.socket();

              serverSocket.bind(new InetSocketAddress(port));

 

              // 設置通道為非阻塞模式

              serverChannel.configureBlocking(false);

 

              // 開啟通道選擇器,並注冊 ServerSocketChannel

              selector = Selector.open();

              serverChannel.register(selector, SelectionKey.OP_ACCEPT);

       }

 

       void go() throws Exception {

              while (true) {

                     int num = selector.select();

                     if (num <= 0)

                            continue;

                     Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();

 

                     while (keyIter.hasNext()) {

                            final SelectionKey key = keyIter.next();

 

                            // 接收一個Socket連接

                            // key.isAcceptable()如果為true,說明channnel支持accept(),也就是說明是一個ServerSocketChannel

                            if (key.isAcceptable()) {

                                   SocketChannel clientChannel = serverChannel.accept();

                                   if (clientChannel != null) {

                                          clientChannel.configureBlocking(false);

                                          clientChannel.register(selector, SelectionKey.OP_READ

                                                        | SelectionKey.OP_WRITE);

                                   }

 

                            }

                            // 如果isReadable()為true,說明是一個SocketChannel

                            if (key.isReadable()) {

                                   String requestContent = read(key);

                                   // 業務處理

                                   // responseContent=doSomthing(requestContent);

                                   write(key, "ok" /* responseContent */);

                            }

 

                            keyIter.remove();

                     }

              }

       }

 

       // 從通道讀取數據

       String read(SelectionKey key) throws Exception {

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

              buffer.clear();// 這一步必須有

              int len = 0;

              StringBuffer str=new StringBuffer();

              while ((len = socketChannel.read(buffer)) > 0) {

                     byte[] bs = buffer.array();

                     String block=new String(bs, 0, len);

                     System.out.println("Server read: " + block);

                     str.append(block);

              }

              buffer.clear();

              return str.toString();

       }

 

       // 寫數據到通道

       void write(SelectionKey key, String str) throws Exception {

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

              buffer.clear();

              buffer.put(str.getBytes());

              buffer.flip();// 這一步必須有

              socketChannel.write(buffer);

       }

 

 

       public static void main(String[] args) throws Exception {

              final int port = 10000;

              NioServer server = new NioServer(port);

              server.init();

///========================================================

              // 接下來模擬3個Client並發訪問服務器

              int poolsize = 3;

 

              ExecutorService pool = Executors.newFixedThreadPool(poolsize);

              Collection<Callable> tasks = new ArrayList<Callable>(10);

              final String clientname="clientThread";

              for (int i = 0; i < poolsize; i++) {

                     final int n = i;

 

                     // 若每一個Client都保持使用BIO方式發送數據到Server,並讀取數據。

                     tasks.add(new Callable() {

 

                            @Override

                            public Object call() throws Exception {

 

                                   Socket socket = new Socket("127.0.0.1", port);

                                   final InputStream input = socket.getInputStream();

                                   final OutputStream out = socket.getOutputStream();

                                   final String clientname_n = clientname + "_" + n;

 

                                   // BIO讀取數據線程

                                   new Thread(clientname_n + "_read") {

                                          @Override

                                          public void run() {

                                                 byte[] bs = new byte[1024];

                                                 while (true) {

                                                        try {

                                                               Thread.sleep(1000);

                                                        } catch (InterruptedException e) {

                                                               e.printStackTrace();

                                                        }

                                                        int len = 0;

                                                        try {

                                                               while ((len = input.read(bs)) != -1) {

                                                                      System.out.println("Clinet thread "

                                                                                    + Thread.currentThread()

                                                                                                  .getName() + " read: "

                                                                                    + new String(bs, 0, len));

                                                               }

                                                        } catch (IOException e) {

                                                               e.printStackTrace();

                                                        }

                                                 }

                                          }

                                   }.start();

 

                                   // BIO寫數據線程

                                   new Thread(clientname_n + "_write") {

                                          @Override

                                          public void run() {

                                                 int a = 0;

                                                 while (true) {

 

                                                        try {

                                                               Thread.sleep(100);

                                                        } catch (InterruptedException e) {

                                                               e.printStackTrace();

                                                        }

                                                        String str = Thread.currentThread().getName()

                                                                      + " hello, " + a;

                                                        try {

                                                               out.write(str.getBytes());

                                                               a++;

                                                        } catch (IOException e) {

                                                               e.printStackTrace();

                                                        }

                                                 }

                                          }

                                   }.start();

 

                                   return null;

                            }

 

                     });

              }

              pool.invokeAll((Collection<? extends Callable<Object>>) tasks);

 

              server.go();

 

       }

}
View Code

上面的測試的是3個Client采用BIO API不斷的並發的發送Socket 請求到Server端。Server采用NIO API處理Client的請求並作出響應,然后Client接收響應。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM