Netty網絡編程一:NIO
寫這些東西的原因,因為本人14年畢業,從事工作也有幾年了,中間關于書,一直看了很多,有的甚至看過很多遍,但是總是覺得自己沒有熟透一門技術,所以在學習使用技術之余,把自己所學的東西記錄下來,便于加深印象,提升自我!
目錄
1 傳統BIO socket通信
2 NIO 編程
一:傳統socket通信
在NIO編程沒出來之前,java使用的socket編程時
socket 服務端:
public class OldSocketServer {
private void start(int port) throws IOException {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(port);
Socket socket = null;
while (true){
socket = serverSocket.accept();
new Thread(new OldSocketServerHandler(socket)).start();
}
}finally {
System.out.println("關閉服務端...");
serverSocket.close();
}
}
public static void main(String[] args) throws IOException {
new OldSocketServer().start(8090);
}
}
對應的handler處理:
public class OldSocketServerHandler implements Runnable {
private Socket socket;
public OldSocketServerHandler(Socket socket) {
this.socket = socket;
}
public void run() {
BufferedReader in = null;
try {
in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
String body = null;
while ((body = in.readLine())!=null){
System.out.println(" 服務端收到消息 : " + body);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(socket != null){
try {
if(in != null){
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
socket客戶端:
public class OldSocketClient {
private void connect(String ip, int port) throws IOException {
Socket socket = null;
PrintWriter out = null;
try {
socket = new Socket(ip, port);
out = new PrintWriter(socket.getOutputStream(), true);
out.write("this is socket client to server msg.");
}catch (Exception e){
e.printStackTrace();
}finally {
if(out !=null){
out.close();
}
if(socket != null){
socket.close();
}
}
}
public static void main(String[] args) throws IOException {
new OldSocketClient().connect("localhost",8090);
}
}
通過上面簡單的一個socket通信問題,可以找出幾個問題:
問題1. 對于上面的服務端,當每接一個客戶端請求,服務端必須new一個線程處理新的請求,對于java這種線程資源非常珍 貴的語言中,這種設計顯示是有問題的。
問題2:對于IO數據的讀取上,對于InputStream輸入流,當對socket的輸入流進行讀取操作的時候,線程會一直阻塞,直到讀取到數據,或數據讀取完畢,又或者發生空指針或I/O異常時。 同樣輸出流輸出數據,OutputStream輸出流會把所有的數據全部發送出去或者發生異常才會停止阻塞。顯然,同步阻塞這種設計是十分浪費系統資源的。
二:nio編程
NIO是JDK1.4引入的,通過快的形式處理數據。NIO常用的幾個概念:
1、Buffer(緩沖區)
在傳統的面向流的I/O中,數據是直接寫入或讀取到流對象中的,而在NIO中,所有的數據都是用緩沖區來處理的。緩沖區實質上就是一個數組,通過定義數據的結構,是的緩沖區能被重復利用。
NIO定義的Buffer的繼承關系圖:
緩沖區定義四個屬性來提供關于其所包含的數據元素的信息:
1).容量 ( Capacity )
緩沖區能夠容納的數據元素的最大數量。這一容量在緩沖區創建時被設定,并且永遠不能 被改變。
2).上界 ( Limit )
緩沖區的第一個不能被讀或寫的元素。或者說,緩沖區中現存元素的計數。
3).位置 ( Position )
下一個要被讀或寫的元素的索引。位置會自動由相應的 get( )和 put( )函數更新。
4).標記 ( Mark )
一個備忘位置。調用 mark( )來設定 mark = postion。調用 reset( )設定 position = mark。標記在設定前是未定義的 (undefined)。 這四個屬性之間總是遵循以下關系:
0 <= mark <= position <= limit <= capacity
新初始化的Buffer圖:
2、通道Channel
Channel是一個通道,是全雙工的,就像是自來水管一樣,網絡數據通過Channel讀取和寫入。與流IO的不同之處在意,流IO只能進行讀InputStream 或者寫OutputStream, 而Channel可讀可寫。
Channel繼承關系類圖如下,主要有:ServerSocketChannel, SocketChannel, DatagramChannel
3、多路復用器Selector
多路復用器Selector提供選擇已經就緒的任務的能力。selector會不斷的輪詢注冊在其上的Channel, 如果某個Channel發生讀或者寫事件,則這個Channel就處于就緒狀態,會被Selector輪詢出來,然后通過SelectionKey獲取就緒Channel的集合,進行后續的I/O操作。
在JDK中selector使用了epoll()來實現select, 故此沒有輪詢Channel數量限制。
一個NIO的案例:
服務端代碼:
public class NioServer {
private ByteBuffer readBuf = ByteBuffer.allocate(1024);
private void start(int port) throws IOException {
ServerSocketChannel serverSocketChannel = null;
try{
// 1、打開ServerSocketChannel,用于監聽客戶端連接,是所有客戶端連接的父管道
serverSocketChannel = ServerSocketChannel.open();
// 2、綁定監聽端口,設置連接為非阻塞模式
serverSocketChannel.socket().bind(new InetSocketAddress(port));
serverSocketChannel.configureBlocking(false);
// 3、創建Reactor線程,創建多路復用器并啟動線程
Selector selector = Selector.open();
// 4、將ServerSocketChannel注冊到Reactor線程的多路復用器Selector上,監聽ACCEPT事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println(" 服務端開始工作.....................");
run(selector);
}finally {
if(serverSocketChannel!=null){
serverSocketChannel.close();
}
}
}
private void run(Selector selector) {
while(true){
try {
//1.讓多路復用器開始監聽
selector.select();
//2.返回多路復用器已經選擇的結果集
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while(keys.hasNext()){
SelectionKey key = keys.next();
keys.remove();
if(key.isValid()){
if(key.isConnectable()){
System.out.println("connectable....");
}
if(key.isWritable()){
System.out.println("writable.........");
}
if(key.isAcceptable()){
System.out.println("acceptable....");
accept(key,selector);//這里的key就是服務器端的Channel的key
}
if(key.isReadable()){
System.out.println("readable....");
read(key);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void read(SelectionKey key) {
try {
// 1. 清空舊的緩沖區
readBuf.clear();
//2.獲取之前注冊的socket通道對象
SocketChannel sc = (SocketChannel) key.channel();
//3.讀取數據
int count = sc.read(readBuf);
//4.如果沒有數據
if(count == -1){
key.channel().close();
key.cancel();
System.out.println("已無可讀數據");
return;
}
//5.有數據則進行讀取,讀取之前需要進行復位方法(把position和limit進行復位)
readBuf.flip();
//6.根據緩沖區的數據長度創建相應大小的byte數組,接收緩沖區的數據
byte[] bytes = new byte[readBuf.remaining()];
//7.接收緩沖區數據
readBuf.get(bytes);
//8.打印結果
String body = new String(bytes).trim();
System.out.println("Server: " + body);
} catch (IOException e) {
e.printStackTrace();
}
}
private void accept(SelectionKey key, Selector selector) {
try {
//1.獲取服務端通道
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
//2.執行客戶端Channel的阻塞方法
SocketChannel sc = ssc.accept();
//3.設置阻塞模式
sc.configureBlocking(false);
//4.注冊到多路復用器上,并設置讀取標識
sc.register(selector, SelectionKey.OP_READ);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
new NioServer().start(8090);
}
}
客戶端代碼:
public class NioClient {
public static void main(String[] args) {
SocketChannel sc = null;
ByteBuffer buf = ByteBuffer.allocate(1024);
try {
//打開通道
sc = SocketChannel.open();
//進行連接
sc.connect(new InetSocketAddress("127.0.0.1", 8090));
//把數據放到緩沖區
buf.put("fangyouyun".getBytes());
//復位
buf.flip();
//寫出數據
sc.write(buf);
//清空緩沖區數據
buf.clear();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
sc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
總結:NIO以多路復用的設計方式,以及Buffer緩沖區的設計,都使得性能得到很大的提升,但是API太復雜了,所以選擇Netty,因為Netty是基于NIO再次封裝,便于開發。
博客中案例代碼:https://download.csdn.net/download/qq_22871607/11072379
智能推薦
網絡編程——NIO編程
我們在之前介紹NIO的時候,提到過會使用Selector(選擇器)來實現IO的多路復用,那么Selector是什么呢? Selector Selector是“選擇器”,也可以稱為為“輪詢代理器”、“事件訂閱器”、“channel容器管理機”都行。主要作用是應用程序將向Selector對象注冊需要它關注的...
nio與netty編程(二)
文章目錄 四 netty Netty 整體設計 線程模型 單線程模型 image-20200808113429095 線程池模型 netty模型 異步模型 核心API ChannelHandler 及其實現類 Pipeline 和 和 ChannelPipeline ChannelHandlerContext ChannelOption ChannelFuture EventLoopGroup 和...
NIO網絡編程
文章目錄 1.Java NIO 2.Buffer緩存區 3.Buffer工作原理 3.1.Buffer API使用練習 3.2ByteBuffer內存類型 4.Channel通道 4.1SocketChannel 4.2ServerSocketChannel 5.NIO模擬客戶端和服務端 5.1阻塞式的方式 5.2升級之后的服務端 6.Selector選擇器 6.1用Selector來優化服務端代...
猜你喜歡
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_...