java 网络 io流_【015期】JavaSE面试题(十五):网络IO流
什么是bio
同步阻塞式IO,服務端創建一個ServerSocket,然后客戶端用一個Socket去連接那個ServerSocket,然后ServerSocket接收到一個Socket的連接請求就創建一個Socket和一個線程去跟那個Socket進行通信。
public class BioServer {
public static void main(String[] args) {
// 服務端開啟一個端口進行監聽
int port = 8080;
ServerSocket serverSocket = null; //服務端
Socket socket; //客戶端
InputStream in = null;
OutputStream out = null;
try {
serverSocket = new ServerSocket(port); //通過構造函數創建ServerSocket,指定監聽端口,如果端口合法且空閑,服務器就會監聽成功
// 通過無限循環監聽客戶端連接,如果沒有客戶端接入,則會阻塞在accept操作
while (true) {
System.out.println("Waiting for a new Socket to establish" + " ," + new Date().toString());
socket = serverSocket.accept();//阻塞 三次握手
in = socket.getInputStream();
byte[] buffer = new byte[1024];
int length = 0;
while ((length = in.read(buffer)) > 0) {//阻塞
System.out.println("input is:" + new String(buffer, 0, length) + " ," + new Date().toString());
out = socket.getOutputStream();
out.write("success".getBytes());
System.out.println("Server end" + " ," + new Date().toString());
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 必要的清理活動
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
什么是nio
同步非阻塞
包括Selector,這是多路復用器,selector會不斷輪詢注冊的channel,如果某個channel上發生了讀寫事件,selector就會將這些channel獲取出來,我們通過SelectionKey獲取有讀寫事件的channel,就可以進行IO操作。一個Selector就通過一個線程,就可以輪詢成千上萬的channel,這就意味著你的服務端可以接入成千上萬的客戶端。
public class NioDemo implements Runnable {
public int id = 100001;
public int bufferSize = 2048;
@Override
public void run() {
init();
}
public void init() {
try {
// 創建通道和選擇器
ServerSocketChannel socketChannel = ServerSocketChannel.open();
Selector selector = Selector.open();
InetSocketAddress inetSocketAddress = new InetSocketAddress(
InetAddress.getLocalHost(), 4700);
socketChannel.socket().bind(inetSocketAddress);
// 設置通道非阻塞 綁定選擇器
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_ACCEPT).attach(
id++);
System.out.println("Server started .... port:4700");
listener(selector);
} catch (Exception e) {
}
}
public void listener(Selector in_selector) {
try {
while (true) {
Thread.sleep(1 * 1000);
in_selector.select(); // 阻塞 直到有就緒事件為止
Set readySelectionKey = in_selector
.selectedKeys();
Iterator it = readySelectionKey.iterator();
while (it.hasNext()) {
SelectionKey selectionKey = it.next();
// 判斷是哪個事件
if (selectionKey.isAcceptable()) {// 客戶請求連接
System.out.println(selectionKey.attachment()
+ " - 接受請求事件");
// 獲取通道 接受連接,
// 設置非阻塞模式(必須),同時需要注冊 讀寫數據的事件,這樣有消息觸發時才能捕獲
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey
.channel();
serverSocketChannel
.accept()
.configureBlocking(false)
.register(
in_selector,
SelectionKey.OP_READ
| SelectionKey.OP_WRITE).attach(id++);
System.out
.println(selectionKey.attachment() + " - 已連接");
// 下面這種寫法是有問題的 不應該在serverSocketChannel上面注冊
/*
* serverSocketChannel.configureBlocking(false);
* serverSocketChannel.register(in_selector,
* SelectionKey.OP_READ);
* serverSocketChannel.register(in_selector,
* SelectionKey.OP_WRITE);
*/
}
if (selectionKey.isReadable()) {// 讀數據
System.out.println(selectionKey.attachment()
+ " - 讀數據事件");
SocketChannel clientChannel = (SocketChannel) selectionKey.channel();
ByteBuffer receiveBuf = ByteBuffer.allocate(bufferSize);
clientChannel.read(receiveBuf);
System.out.println(selectionKey.attachment()
+ " - 讀取數據:" + getString(receiveBuf));
}
if (selectionKey.isWritable()) {// 寫數據
System.out.println(selectionKey.attachment()
+ " - 寫數據事件");
SocketChannel clientChannel = (SocketChannel) selectionKey.channel();
ByteBuffer sendBuf = ByteBuffer.allocate(bufferSize);
String sendText = "hello\n";
sendBuf.put(sendText.getBytes());
sendBuf.flip(); //寫完數據后調用此方法
clientChannel.write(sendBuf);
}
if (selectionKey.isConnectable()) {
System.out.println(selectionKey.attachment()
+ " - 連接事件");
}
// 必須removed 否則會繼續存在,下一次循環還會進來,
// 注意removed 的位置,針對一個.next() remove一次
it.remove();
}
}
} catch (Exception e) {
System.out.println("Error - " + e.getMessage());
e.printStackTrace();
}
}
/**
* ByteBuffer 轉換 String
*
* @param buffer
* @return
*/
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 "";
}
}
}
什么是aio
異步非阻塞
每個連接發送過來的請求,都會綁定一個buffer,然后通知操作系統去異步完成讀,此時你的程序是會去干別的事兒的,等操作系統完成數據讀取之后,就會回調你的接口,給你操作系統異步讀完的數據。
public class AIOServer {
public final static int PORT = 9888;
private AsynchronousServerSocketChannel server;
public AIOServer() throws IOException {
server = AsynchronousServerSocketChannel.open().bind(
new InetSocketAddress(PORT));
}
public void startWithFuture() throws InterruptedException,
ExecutionException, TimeoutException {
while (true) {// 循環接收客戶端請求
Future future = server.accept();
AsynchronousSocketChannel socket = future.get();// get() 是為了確保 accept 到一個連接
handleWithFuture(socket);
}
}
public void handleWithFuture(AsynchronousSocketChannel channel) throws InterruptedException, ExecutionException, TimeoutException {
ByteBuffer readBuf = ByteBuffer.allocate(2);
readBuf.clear();
while (true) {// 一次可能讀不完
//get 是為了確保 read 完成,超時時間可以有效避免DOS攻擊,如果客戶端一直不發送數據,則進行超時處理
Integer integer = channel.read(readBuf).get(10, TimeUnit.SECONDS);
System.out.println("read: " + integer);
if (integer == -1) {
break;
}
readBuf.flip();
System.out.println("received: " + Charset.forName("UTF-8").decode(readBuf));
readBuf.clear();
}
}
public void startWithCompletionHandler() throws InterruptedException,
ExecutionException, TimeoutException {
server.accept(null,
new CompletionHandler() {
public void completed(AsynchronousSocketChannel result, Object attachment) {
server.accept(null, this);// 再此接收客戶端連接
handleWithCompletionHandler(result);
}
@Override
public void failed(Throwable exc, Object attachment) {
exc.printStackTrace();
}
});
}
public void handleWithCompletionHandler(final AsynchronousSocketChannel channel) {
try {
final ByteBuffer buffer = ByteBuffer.allocate(4);
final long timeout = 10L;
channel.read(buffer, timeout, TimeUnit.SECONDS, null, new CompletionHandler() {
@Override
public void completed(Integer result, Object attachment) {
System.out.println("read:" + result);
if (result == -1) {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
}
return;
}
buffer.flip();
System.out.println("received message:" + Charset.forName("UTF-8").decode(buffer));
buffer.clear();
channel.read(buffer, timeout, TimeUnit.SECONDS, null, this);
}
@Override
public void failed(Throwable exc, Object attachment) {
exc.printStackTrace();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String args[]) throws Exception {
// new AIOServer().startWithFuture();
new AIOServer().startWithCompletionHandler();
Thread.sleep(100000);
}
}
什么是epoll
一種多路復用的技術,可以解決之前poll和select大量并發連接情況下cpu利用率過高,以及需要遍歷整個被偵聽的描述符集的問題。epoll只要遍歷那些被內核IO事件異步喚醒而加入Ready隊列的描述符集合就行了。
什么是mmap技術
把一個磁盤文件映射到內存里來,然后把映射到內存里來的數據通過socket發送出去
有一種mmap技術,也就是內存映射,直接將磁盤文件數據映射到內核緩沖區,這個映射的過程是基于DMA引擎拷貝的,同時用戶緩 沖區是跟內核緩沖區共享一塊映射數據的,建立共享映射之后,就不需要從內核緩沖區拷貝到用戶緩沖區了
光是這一點,就可以避免一次拷貝了,但是這個過程中還是會用戶態切換到內核態去進行映射拷貝,接著再次從內核態切換到用戶態, 建立用戶緩沖區和內核緩沖區的映射
接著把數據通過Socket發送出去,還是要再次切換到內核態
接著直接把內核緩沖區里的數據拷貝到Socket緩沖區里去,然后再拷貝到網絡協議引擎里,發送出去就可以了,最后切換回用戶態
減少一次拷貝,但是并不減少切換次數,一共是4次切換,3次拷貝
什么是零拷貝技術
linux提供了sendfile,也就是零拷貝技術
這個零拷貝技術,就是先從用戶態切換到內核態,在內核態的狀態下,把磁盤上的數據拷貝到內核緩沖區,同時從內核緩沖區拷貝一些 offset和length到Socket緩沖區;接著從內核態切換到用戶態,從內核緩沖區直接把數據拷貝到網絡協議引擎里去
同時從Socket緩沖區里拷貝一些offset和length到網絡協議引擎里去,但是這個offset和length的量很少,幾乎可以忽略
只要2次切換,2次拷貝,就可以了
說一下select,poll,epoll的區別?
select,poll實現需要自己不斷輪詢所有fd集合,直到設備就緒,期間可能要睡眠和喚醒多次交替。
epoll也需要調用epoll_wait不斷輪詢就緒鏈表,期間也可能多次睡眠和喚醒交替,但是它是設備就緒時,調用回調函數,把就緒fd放入就緒鏈表中,并喚醒在epoll_wait中進入睡眠的進程。雖然都要睡眠和交替,但是select和poll在“醒著”的時候要遍歷整個fd集合,而epoll在“醒著”的時候只要判斷一下就緒鏈表是否為空就行了,這節省了大量的CPU時間。這就是回調機制帶來的性能提升。
select,poll每次調用都要把fd集合從用戶態往內核態拷貝一次,并且要把current往設備等待隊列中掛一次,而epoll只要一次拷貝,而且把current往等待隊列上掛也只掛一次(在epoll_wait的開始,注意這里的等待隊列并不是設備等待隊列,只是一個epoll內部定義的等待隊列)。這也能節省不少的開銷。
總結
以上是生活随笔為你收集整理的java 网络 io流_【015期】JavaSE面试题(十五):网络IO流的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java 内存映射文件 主要应用_VC+
- 下一篇: java指导手册,Java 注解指导手册