Netty編程之基礎BIO NIO AIO網絡編程模型
前言
公司業務需求,需要用到tcp數據傳輸,基于java自帶的socket太low,而且編程復雜,市面上比較流行的netty比較適合。學習netty之前,先簡單總結一下各種數據傳輸類型。
java支持的類型
Java 共支持 3 種網絡編程模型/IO 模式:BIO、NIO、AIO
Java BIO:
理解: 同步并阻塞(傳統阻塞型),服務器實現模式為一個連接一個線程,即客戶端有連接請求時服務器
端就需要啟動一個線程進行處理,如果這個連接不做任何事情會造成不必要的線程開銷
適用場景:BIO 方式適用于連接數目比較小且固定的架構,這種方式對服務器資源要求比較高,并發局限于應用中,JDK1.4
以前的唯一選擇,但程序簡單易理解。
通俗理解:一個請求需要創建一個新的線程,如果客戶端比較多的話,服務器肯定是受不了的
Java NIO :
理解: 同步非阻塞,服務器實現模式為一個線程處理多個請求(連接),即客戶端發送的連接請求都會注
冊到多路復用器上,多路復用器輪詢到連接有 I/O 請求就進行處理
適用場景:NIO 方式適用于連接數目多且連接比較短(輕操作)的架構,比如聊天服務器,彈幕系統,服務器間通訊等。
編程比較復雜,JDK1.4 開始支持。
通俗理解:NIO有緩沖池,有通道。有選擇器,一個選擇器一個線程,多個通道對應多個緩沖池,可對應一個通道,這樣有請求過來,直接放到選擇器,由選擇器一個個執行,這樣就做到了同步,并且非阻塞。
Java AIO
理解: 異步非阻塞,AIO 引入異步通道的概念,采用了 Proactor 模式,簡化了程序編寫,有效
的請求才啟動線程,它的特點是先由操作系統完成后才通知服務端程序啟動線程去處理,一般適用于連接數較
多且連接時間較長的應用,JDK1.4 開始支持。
適用場景:AIO 方式使用于連接數目多且連接比較長(重操作)的架構,比如相冊服務器,充分調用 OS 參與并發操作,
編程比較復雜,JDK7 開始支持。
通俗理解:這個是1.7以后才支持的,用的比較少,跟netty關系也不大,感興趣的可自行研究。
NIO三大核心
因為netty是基礎NIO模型的框架,所以下面著重講一下NIO的三大核心。
NIO模型圖解:
緩沖區
理解:緩沖區本質上是一個 可以讀寫數據的內存塊可以理解成是一個 容器對象(包含數組的對象)。它能夠跟蹤和記錄緩沖區內的狀態變化。
主要實現方法
頂級父類是buffer。
屬性
通道
理解:NIO 的通道類似于流,但有些區別如下
-
通道可以同時進行讀寫,而流只能讀或者只能寫
-
通道可以實現異步讀寫數據
-
通道可以從緩沖讀數據,也可以寫數據到緩沖
主要實現方法
頂級父類是channel
實現子類: -
FileChannel 用于文件的數據讀寫
-
DatagramChannel 用于 UDP 的數據讀寫
-
ServerSocketChannel是TCP服務端數據讀寫
-
SocketChannel 是客戶端TCP 的數據讀寫。
常見方法 -
public int read(ByteBuffer dst) ,從通道讀取數據并放到緩沖區中
-
public int write(ByteBuffer src) ,把緩沖區的數據寫到通道中
-
public long transferFrom(ReadableByteChannel src, long position, long count),從目標通道中復制數據到當前通道
-
public long transferTo(long position, long count, WritableByteChannel target),把數據從當前通道復制給目標通道
選擇器
理解:用非阻塞的 IO 方式。可以用一個線程,處理多個的客戶端連接,就會使用到 Selector(選擇器),一個選擇器管理多個通道,其管理的通道注冊到選擇器上,然后選擇器會監聽通道內是否發生變化,如果變化了,才會去處理,這樣就降低了連接的開銷。
常用方法
open()得到一個選擇器對象
select(long timeout) 監控所有注冊的通道。當其中有IO操作的時候,將對應的selectkey加入到內部集合并且返回。參數用來設置超時時間,
selectkeys()用來獲取所有注冊到選擇器上面的通道。
流程圖
文字解釋
代碼示例
客戶端代碼
package com.huali.nio.groupchat;
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;
import java.util.Scanner;
import java.util.Set;
public class GroupChatClient {
//定義相關的屬性
private final String HOST = "127.0.0.1"; // 服務器的ip
private final int PORT = 6667; //服務器端口
private Selector selector;
private SocketChannel socketChannel;
private String username;
//構造器, 完成初始化工作
public GroupChatClient() throws IOException {
selector = Selector.open();
//連接服務器
socketChannel = socketChannel.open(new InetSocketAddress("127.0.0.1", PORT));
//設置非阻塞
socketChannel.configureBlocking(false);
//將channel 注冊到selector
socketChannel.register(selector, SelectionKey.OP_READ);
//得到username
username = socketChannel.getLocalAddress().toString().substring(1);
System.out.println(username + " is ok...");
}
//向服務器發送消息
public void sendInfo(String info) {
info = username + " 說:" + info;
try {
socketChannel.write(ByteBuffer.wrap(info.getBytes()));
}catch (IOException e) {
e.printStackTrace();
}
}
//讀取從服務器端回復的消息
public void readInfo() {
try {
int readChannels = selector.select();
if(readChannels > 0) {//有可以用的通道
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if(key.isReadable()) {
//得到相關的通道
SocketChannel sc = (SocketChannel) key.channel();
//得到一個Buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
//讀取
sc.read(buffer);
//把讀到的緩沖區的數據轉成字符串
String msg = new String(buffer.array());
System.out.println(msg.trim());
}
}
iterator.remove(); //刪除當前的selectionKey, 防止重復操作
} else {
//System.out.println("沒有可以用的通道...");
}
}catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
//啟動我們客戶端
GroupChatClient chatClient = new GroupChatClient();
//啟動一個線程, 每個3秒,讀取從服務器發送數據
new Thread() {
public void run() {
while (true) {
chatClient.readInfo();
try {
Thread.currentThread().sleep(3000);
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
//發送數據給服務器端
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
String s = scanner.nextLine();
chatClient.sendInfo(s);
}
}
}
客戶端代碼
package com.huali.nio.groupchat;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
public class GroupChatServer {
//定義屬性
private Selector selector;
private ServerSocketChannel listenChannel;
private static final int PORT = 6667;
//構造器
//初始化工作
public GroupChatServer() {
try {
//得到選擇器
selector = Selector.open();
//ServerSocketChannel
listenChannel = ServerSocketChannel.open();
//綁定端口
listenChannel.socket().bind(new InetSocketAddress(PORT));
//設置非阻塞模式
listenChannel.configureBlocking(false);
//將該listenChannel 注冊到selector
listenChannel.register(selector, SelectionKey.OP_ACCEPT);
}catch (IOException e) {
e.printStackTrace();
}
}
//監聽
public void listen() {
System.out.println("監聽線程: " + Thread.currentThread().getName());
try {
//循環處理
while (true) {
int count = selector.select();
if(count > 0) {//有事件處理
//遍歷得到selectionKey 集合
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
//取出selectionkey
SelectionKey key = iterator.next();
//監聽到accept
if(key.isAcceptable()) {
SocketChannel sc = listenChannel.accept();
sc.configureBlocking(false);
//將該 sc 注冊到seletor
sc.register(selector, SelectionKey.OP_READ);
//提示
System.out.println(sc.getRemoteAddress() + " 上線 ");
}
if(key.isReadable()) { //通道發送read事件,即通道是可讀的狀態
//處理讀 (專門寫方法..)
readData(key);
}
//當前的key 刪除,防止重復處理
iterator.remove();
}
} else {
System.out.println("等待....");
}
}
}catch (Exception e) {
e.printStackTrace();
}finally {
//發生異常處理....
}
}
//讀取客戶端消息
private void readData(SelectionKey key) {
//取到關聯的channle
SocketChannel channel = null;
try {
//得到channel
channel = (SocketChannel) key.channel();
//創建buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
int count = channel.read(buffer);
//根據count的值做處理
if(count > 0) {
//把緩存區的數據轉成字符串
String msg = new String(buffer.array());
//輸出該消息
System.out.println("form 客戶端: " + msg);
//向其它的客戶端轉發消息(去掉自己), 專門寫一個方法來處理
sendInfoToOtherClients(msg, channel);
}
}catch (IOException e) {
try {
System.out.println(channel.getRemoteAddress() + " 離線了..");
//取消注冊
key.cancel();
//關閉通道
channel.close();
}catch (IOException e2) {
e2.printStackTrace();;
}
}
}
//轉發消息給其它客戶(通道)
private void sendInfoToOtherClients(String msg, SocketChannel self ) throws IOException{
System.out.println("服務器轉發消息中...");
System.out.println("服務器轉發數據給客戶端線程: " + Thread.currentThread().getName());
//遍歷 所有注冊到selector 上的 SocketChannel,并排除 self
for(SelectionKey key: selector.keys()) {
//通過 key 取出對應的 SocketChannel
Channel targetChannel = key.channel();
//排除自己
if(targetChannel instanceof SocketChannel && targetChannel != self) {
//轉型
SocketChannel dest = (SocketChannel)targetChannel;
//將msg 存儲到buffer
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
//將buffer 的數據寫入 通道
dest.write(buffer);
}
}
}
public static void main(String[] args) {
//創建服務器對象
GroupChatServer groupChatServer = new GroupChatServer();
groupChatServer.listen();
}
}
//可以寫一個Handler
class MyHandler {
public void readData() {
}
public void sendInfoToOtherClients(){
}
}
智能推薦
從零開始學Netty (一)-- Linux網絡IO模型 及 Java實現BIO、NIO、AIO
下面準備做一個Netty的系列教程,適合初次接觸Netty或網絡通訊的同學了解閱讀。本文主要介紹Linux網絡IO模型、BIO、NIO、AIO及代碼實現。至于更基礎的OSI、TCP/IP、HTTP等,相信大家都不陌生就不一一贅述了。由于Netty5停止維護了,所以后面所涉及的Netty都是指Netty4這個版本,故也不支持AIO。 Linux網絡IO模型 一般服務器都運行在Linux環境下,故這里...
結合代碼詳細聊聊 Java 網絡編程中的 BIO、NIO 和 AIO
本文從操作系統的角度來解釋BIO,NIO,AIO的概念,含義和背后的那些事。本文主要分為3篇。 第一篇 講解BIO和NIO以及IO多路復用 第二篇 講解磁盤IO和AIO 第三篇 講解在這些機制上的一些應用的實現方式,比如nginx,nodejs,Java NIO等 到底什么是“IO Block” 很多人說BIO不好,會“block&rdquo...
freemarker + ItextRender 根據模板生成PDF文件
1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...
電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!
Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...
猜你喜歡
requests實現全自動PPT模板
http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...
Linux C系統編程-線程互斥鎖(四)
互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...
統計學習方法 - 樸素貝葉斯
引入問題:一機器在良好狀態生產合格產品幾率是 90%,在故障狀態生產合格產品幾率是 30%,機器良好的概率是 75%。若一日第一件產品是合格品,那么此日機器良好的概率是多少。 貝葉斯模型 生成模型與判別模型 判別模型,即要判斷這個東西到底是哪一類,也就是要求y,那就用給定的x去預測。 生成模型,是要生成一個模型,那就是誰根據什么生成了模型,誰就是類別y,根據的內容就是x 以上述例子,判斷一個生產出...
styled-components —— React 中的 CSS 最佳實踐
https://zhuanlan.zhihu.com/p/29344146 Styled-components 是目前 React 樣式方案中最受關注的一種,它既具備了 css-in-js 的模塊化與參數化優點,又完全使用CSS的書寫習慣,不會引起額外的學習成本。本文是 styled-components 作者之一 Max Stoiber 所寫,首先總結了前端組件化樣式中的最佳實踐原則,然后在此基...