摘要:緩沖區的限制不能為負,并且不能大于其容量。如果指向的位置超過限制,則拋出異常。使用臨時緩沖區執行低層次操作。臨時緩沖區對象離開作用域,并最終成為被回收的無用數據。
前天剛好看了點《UNIX網絡編程》,比較頭大。現在我來整理一下所學所得,并用于個人備忘。如果有不對,請批評。
想要解鎖更多新姿勢?請訪問https://blog.tengshe789.tech/
IO模型介紹IO模型是什么?很多書籍或者百度百度百科,都沒有給出明確的解釋,我也不敢亂下定義。以我愚見,IO模型,是通過根據前人主觀意識的思考而構成客觀闡述IO復雜操作邏輯的物件。
要知道,應用程序使用系統資源的一個過程,進程無法直接操作IO設備的,因為用戶進程不能直接訪問磁盤,所以要通過內核的系統調用讀取,這個內核讀取的過程就是用戶進程等待的過程,等待內核讀取后將數據從內核內存復制到進程內存。因此操作系統設立一個IO模型進行規范,就非常有必要了。
為了更好地了解IO模型,我們需要事先回顧下:同步、異步、阻塞、非阻塞
同步與異步:描述的是用戶線程與內核的交互方式,同步指用戶線程發起IO請求后需要等待或者輪詢內核IO操作完成后才能繼續執行;而異步是指用戶線程發起IO請求后仍然繼續執行,當內核IO操作完成后會通知用戶線程,或者調用用戶線程注冊的回調函數。
阻塞與非阻塞:描述是用戶線程調用內核IO操作的方式,阻塞是指IO操作需要徹底完成后才返回到用戶空間;而非阻塞是指IO操作被調用后立即返回給用戶一個狀態值,無需等到IO操作徹底完成。
IO模型一共有5類:
blocking-IO BIO(阻塞IO)
non-blocking IO NIO(非阻塞IO)
IO multiplexing IO多路復用
signal driven IO 信號驅動IO
asynchronous IO AIO(異步IO)
由于signal driven IO(信號驅動IO)在實際中并不常用,所以主要介紹其余四種IO Model。
BIO(blocking io)先來看看讀操作流程
從圖中可以看出,用戶進程調用了recvfrom這個系統調用,kernel就開始了IO的第一個階段:準備數據。
對于network io來說,很多時候數據在一開始還沒有到達(比如,還沒有收到一個完整的UDP包),這個時候kernel就要等待足夠的數據到來。
而在用戶進程這邊,整個進程會被阻塞。當kernel一直等到數據準備好了,它就會將數據從kernel中拷貝到用戶內存,然后kernel返回結果,用戶進程才解除block的狀態,重新運行起來。
也就是說,blocking IO的特點就是在IO執行的兩個階段(等待數據和拷貝數據兩個階段)都被block了。
JAVA 阻塞 demo下面的例子主要使用Socket通道進行編程。服務端如下:
/** * @program: socketTest * @description: one thread demo for bio version * @author: tEngSHe789 * @create: 2018-08-26 21:17 **/ public class Server { public static void main(String[] args) { try { ServerSocket serverSocket=new ServerSocket(8888); System.out.println("服務端Start...."); //等待客戶端就緒 -> 堵塞 while (true){ Socket socket = serverSocket.accept(); System.out.println("發現客戶端連接"); InputStream is=socket.getInputStream(); byte[] b =new byte[1024]; //等待客戶端發送請求 -> 堵塞 while (true) { int data = is.read(b); String info=null; if (data!=-1){ info=new String(b,0,data,"GBK"); } System.out.println(info); } } } catch (IOException e) { } } }
客戶端
/** * @program: socketTest * @description: one thread demo for bio version * @author: tEngSHe789 **/ public class Client { public static void main(String[] args) { try { Socket socket=new Socket("127.0.0.1",8888); OutputStream os = socket.getOutputStream(); System.out.println("正在發送數據"); os.write("這是來自客戶端的信息".getBytes()); os.flush(); } catch (IOException e) { e.printStackTrace(); } } }PY 阻塞 demo
服務端
import socket s = socket.socket() s.bind(("127.0.0.1",8888)) print("服務端啟動....") # 等待客戶端就緒 -> 堵塞 s.listen() # 等待客戶端發送請求 -> 堵塞 conn,addr = s.accept() msg = conn.recv(1024).decode("utf-8") print(msg) conn.close() s.close()
客戶端
import socket s = socket.socket() s.connect(("127.0.0.1",8888)) print("客戶端已啟動....") s.send("正在發送數據".encode("utf-8")) s.close()NIO(non blocking io)
NIO就不一樣了,recvform系統調用調用之后,進程并沒有被阻塞,內核馬上返回給進程,如果數據還沒準備好,此時會返回一個error。進程在返回之后,可以干點別的事情,然后再發起recvform系統調用。重復上面的過程,循環往復的進行recvform系統調用。這個過程通常被稱之為輪詢。
輪詢檢查內核數據,直到數據準備好,再拷貝數據到進程,進行數據處理。需要注意,拷貝數據整個過程,進程仍然是屬于阻塞的狀態。
JAVA 與NIOJava NIO(New IO)是一個可以替代標準Java IO API的IO API(從Java 1.4開始),Java NIO提供了與標準IO不同的IO工作方式。
在java中,標準的IO基于字節流和字符流進行操作的,而NIO是基于通道(Channel)和緩沖區(Buffer)進行操作,數據總是從通道讀取到緩沖區中,或者從緩沖區寫入到通道中。
我們先看看Buffer類
Buffer類Java NIO中的Buffer主要用于與NIO通道進行交互,數據是從通道讀入到緩沖區,從緩沖區寫入通道中的。概念上,緩沖區可以看成包在一個對象內的數組,下面看一個圖
這是一個新創建的容量為10的ByteBuffer邏輯圖,他有四個屬性來提供關于其包含的數據元素信息,分別是:
1)容量(capacity):表示Buffer最大數據容量,緩沖區容量不能為負,并且建立后不能修改。
2)限制(limit):也叫上界。第一個不應該讀取或者寫入的數據的索引,即位于limit后的數據不可以讀寫。緩沖區的限制不能為負,并且不能大于其容量(capacity)。
3)位置(position):下一個要讀取或寫入的數據的索引。緩沖區的位置不能為負,并且不能大于其限制(limit)。
4)標記(mark)與重置(reset):標記是一個索引,通過Buffer中的mark()方法指定Buffer中一個特定的position,之后可以通過調用reset()方法恢復到這個position。
從這幅圖可以看到,他的容量(capacity)和限制(limit)設置為10,位置設置為0,每個緩沖區容量是固定的,標記是未定義的,其他三個屬性可以通過使用緩沖區解決。
緩沖區存儲數據支持的數據類型支持七種數據類型,他們是:
1.byteBuffer
2.charBuffer
3.shortBuffer
4.IntBuffer
5.LongBuffer
6.FloatBuffer
7.DubooBuffer
使用Buffer讀寫數據一般遵循以下四個步驟:
(1) 寫入數據到Buffer,一般有可以從Channel讀取到緩沖區中,也可以調用put方法寫入。
(2) 調用flip()方法,切換數據模式。
(3) 從Buffer中讀取數據,一般從緩沖區讀取數據寫入到通道中,也可以調用get方法讀取。
(4) 調用clear()方法或者compact()方法。
緩沖區API首先,用allocate 指定緩沖區大小1024
ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
我們可以用put 存入數據到緩沖區
byteBuffer.put("tengshe789".getBytes());
當調用put時,會指出下一個元素應當被插入的位置,位置(position)指向的是下一個元素。如果指向的位置超過限制(limit),則拋出BufferOverFlowException異常。
Flip將一個能夠繼續添加數據元素的填充狀態的緩沖區翻轉成一個準備讀出元素的釋放狀態
byteBuffer.flip();
具體有什么用呢?
對于已經寫滿了緩沖區,如果將緩沖區內容傳遞給一個通道,以使內容能被全部寫出。
但如果通道現在在緩沖區上執行get,那么它將從我們剛剛插入的有用數據之外取出未定義數據。通過翻轉將位置值重新設為 0,通道就會從正確位置開始獲取。
例如我們定義了一個容量是10的buffer,并填入hello,如下圖所示
翻轉后如下圖所示
Rewind與 flip相似,但不影響上界屬性。它只是將位置值設回 0。可以使用 rewind()后退,重讀已經被翻轉的緩沖區中的數據。
byteBuffer.rewind();
翻轉完了,就可以用get獲取緩沖區數據了
byte[] b= new byte[byteBuffer.limit()]; byteBuffer.get(b);
當調用get時,會指出下一個元素應當被索引的位置,位置(position)返回時會+1s。如果指向的位置超過限制(limit),則拋出BufferUnderFlowException異常。如果提供的索引超過范圍,也會拋出IndexOutOfBoundsException異常
remaining可以告訴你從當前位置(position)到限制(limit)還剩的元素數目
int count = byteBuffer.remaining();
clear將緩沖區重置為空狀態
byteBuffer.clear();
如果我們只想從緩沖區中釋放一部分數據,而不是全部,然后重新填充。為了實現這一點,未讀的數據元素需要下移以使第一個元素索引為 0。盡管重復這樣做會效率低下,但這有時非常必要,而 API 對此為您提供了一個 compact()函數。
byteBuffer.compact();
標記是一個索引,通過Buffer中的mark()方法指定Buffer中一個特定的position,之后可以通過調用reset()方法恢復到這個position。要知道緩沖區的標記在mark()函數被調用前時未定義的,如果標記未定義,調用reset()會導致InvalidMarkException異常
byteBuffer.position(2).mark().position(4).reset();
要注意,java.nio中的類特意被設計為支持級聯調用,優雅的使用級聯調用,可以產生優美易讀的代碼。
直接緩沖區與非直接緩沖區上面我們說了ByteBuffer,也就是緩沖區的用法,譬如用allocate() 方法指定緩沖區大小,然后進行填充或翻轉操作等等等。我們所創建的緩沖區,都屬于直接緩沖區。他們都是在JVM中內存中創建,在每次調用基礎操作系統的一個本機IO之前或者之后,虛擬機都會將緩沖區的內容復制到中間緩沖區(或者從中間緩沖區復制內容),緩沖區的內容駐留在JVM內,因此銷毀容易,但是占用JVM內存開銷,處理過程中有復制操作。
非直接緩沖區寫入步驟:
1.創建一個臨時的直接ByteBuffer對象。
2.將非直接緩沖區的內容復制到臨時緩沖中。
3.使用臨時緩沖區執行低層次I/O操作。
4.臨時緩沖區對象離開作用域,并最終成為被回收的無用數據。
/** * @program: UndirectBuffer * @description: 利用通道完成文件的復制(非直接緩沖區) * @author: tEngSHe789 **/ public class UndirectBuffer { public static void main(String[] args) throws IOException { // 創建流 FileInputStream fis = new FileInputStream("d://blog.md"); FileOutputStream fos = new FileOutputStream("d://blog.md"); //獲取管道 FileChannel in = fis.getChannel(); FileChannel out = fos.getChannel(); // 分配指定大小的緩沖區 ByteBuffer buffer = ByteBuffer.allocate(1024); while (in.read(buffer) !=-1){ buffer.flip();// 準備讀數據了 out.write(buffer); buffer.clear(); } out.close(); in.close(); fis.close(); fos.close(); } }
直接緩沖區,是通過 allocateDirect() 方法在JVM內存外開辟內存,在每次調用基礎操作系統的一個本機IO之前或者之后,虛擬機都會避免將緩沖區的內容復制到中間緩沖區(或者從中間緩沖區復制內容),緩沖區的內容駐留在物理內存內,會少一次復制過程,如果需要循環使用緩沖區,用直接緩沖區可以很大地提高性能。
雖然直接緩沖區使JVM可以進行高效的I/O操作,但它使用的內存是操作系統分配的,繞過了JVM堆棧,建立和銷毀比堆棧上的緩沖區要更大的開銷。
/** * @program: DirectBuffer * @description: 使用直接緩沖區完成文件的復制(內存映射文件) * @author: tEngSHe789 **/ public class DirectBuffer { public static void main(String[] args) throws IOException { //創建管道 FileChannel in=FileChannel.open(Paths.get("d://blog.md"),StandardOpenOption.READ); FileChannel out=FileChannel.open(Paths.get("d://blog.md"),StandardOpenOption.WRITE ,StandardOpenOption.READ,StandardOpenOption.CREATE); // 拿到將管道內容映射到內存的直接緩沖區映射文件(一個位置在硬盤的基于內存的緩沖區) MappedByteBuffer inMappedByteBuffer = in.map(FileChannel.MapMode.READ_ONLY, 0, in.size()); MappedByteBuffer outMappedByteBuffer = out.map(FileChannel.MapMode.READ_WRITE, 0, in.size()); // 對直接緩沖區進行數據讀寫操作 byte[] bytes=new byte[inMappedByteBuffer.limit()]; inMappedByteBuffer.get(bytes); outMappedByteBuffer.put(bytes); in.close(); out.close(); } }
字節緩沖區要么是直接的,要么是非直接的。如果為直接字節緩沖區,則 Java 虛擬機會盡最大努力直接在此緩沖區上執行本機 I/O 操作。也就是說,在每次調用基礎操作系統的一個本機 I/O 操作之前(或之后),虛擬機都會盡量避免將緩沖區的內容復制到中間緩沖區中(或從中間緩沖區中復制內容)。
直接字節緩沖區可以通過調用此類的 allocateDirect() 工廠方法來創建。此方法返回的緩沖區進行分配和取消分配所需成本通常高于非直接緩沖區。直接緩沖區的內容可以駐留在常規的垃圾回收堆之外,因此,它們對應用程序的內存需求量造成的影響可能并不明顯。所以,建議將直接緩沖區主要分配給那些易受基礎系統的本機 I/O 操作影響的大型、持久的緩沖區。一般情況下,最好僅在直接緩沖區能在程序性能方面帶來明顯好處時分配它們。
直接字節緩沖區還可以通過 FileChannel 的 map() 方法 將文件區域直接映射到內存中來創建。該方法返回MappedByteBuffer 。 Java 平臺的實現有助于通過 JNI 從本機代碼創建直接字節緩沖區。如果以上這些緩沖區中的某個緩沖區實例指的是不可訪問的內存區域,則試圖訪問該區域不會更改該緩沖區的內容,并且將會在訪問期間或稍后的某個時間導致拋出不確定的異常。
字節緩沖區是直接緩沖區還是非直接緩沖區可通過調用其 isDirect() 方法來確定。提供此方法是為了能夠在性能關鍵型代碼中執行顯式緩沖區管理。
Channel通道是java.nio的第二個創新,表示提供 IO 設備(例如:文件、套接字)的直接連接。
若需要使用 NIO 系統,需要獲取用于連接 IO 設備的通道以及用于容納數據的緩沖區。然后操作緩沖區,對數據進行處理。這其中,Channel負責傳輸, Buffer 負責存儲。
通道是由java.nio.channels 包定義的,Channel 表示 IO 源與目標打開的連接。Channel 類似于傳統的“流”。只不過 Channel本身不能直接訪問數據, Channel 只能與Buffer 進行交互。
接口java.nio.channels.Channel 接口:
FileChannel
SocketChannel
ServerSocketChannel
DatagramChannel
與緩沖區不同,通道API主要由接口指定,不同操作系統上通道的實現會不一樣
實現直接緩沖區與非直接緩沖區的栗子
分散讀取與聚集寫入通道可以有選擇地實現兩個新的接口: ScatteringByteChannel 和 GatheringByteChannel。
ScatteringByteChannel 有2個read方法,我們都叫她分散讀取(scattering Reads),分散讀取中,通道依次填充每個緩沖區。填滿一個緩沖區后,它就開始填充下一個。在某種意義上,緩沖區數組就像一個大緩沖區。
GatheringByteChannel中有2個wirte方法,我們都叫她聚集寫入(gathering Writes),他可以將多個緩沖區的數據聚集到通道中
分散讀取/聚集寫入對于將數據劃分為幾個部分很有用。例如,您可能在編寫一個使用消息對象的網絡應用程序,每一個消息被劃分為固定長度的頭部和固定長度的正文。您可以創建一個剛好可以容納頭部的緩沖區和另一個剛好可以容難正文的緩沖區。當您將它們放入一個數組中并使用分散讀取來向它們讀入消息時,頭部和正文將整齊地劃分到這兩個緩沖區中。
我們從緩沖區所得到的方便性對于緩沖區數組同樣有效。因為每一個緩沖區都跟蹤自己還可以接受多少數據,所以分散讀取會自動找到有空間接受數據的第一個緩沖區。在這個緩沖區填滿后,它就會移動到下一個緩沖區。
Python與NIO服務端(具體見注釋)
from socket import * import time s=socket(AF_INET,SOCK_STREAM) s.bind(("127.0.0.1",8888)) s.listen(5) s.setblocking(False) #設置socket的接口為非阻塞 conn_l=[] # 存儲和server的連接 的 連接 del_l=[] # 存儲和和server的斷開 的 連接 while True: try: # 這個過程是不阻塞的 conn,addr=s.accept() # 當沒人連接的時候會報錯,走exception(<- py中是except) conn_l.append(conn) except BlockingIOError: print(conn_l) for conn in conn_l: try: data=conn.recv(1024) if not data: del_l.append(conn) # 這個過程是不阻塞的 data=conn.recv(1024) # 不阻塞 if not data: # 如果拿不到data del_l.append(conn) # 在廢棄列表中添加conn continue conn.send(data.upper()) except BlockingIOError: pass except ConnectionResetError: del_l.append(conn) for conn in del_l: conn_l.remove(conn) conn.close() del_l=[]
客戶端
from socket import * c=socket(AF_INET,SOCK_STREAM) c.connect(("127.0.0.1",8888)) while True: msg=input(">>: ") if not msg:continue c.send(msg.encode("utf-8")) data=c.recv(1024) print(data.decode("utf-8"))IO復用(IO multiplexing)
I/O多路復用實際上就是用select, poll, epoll監聽多個io對象,當io對象有變化(有數據)的時候就通知用戶進程。有些地方也稱這種IO方式為事件驅動IO(event driven IO)。與多進程和多線程技術相比,I/O多路復用技術的最大優勢是系統開銷小,系統不必創建進程/線程,也不必維護這些進程/線程,從而大大減小了系統的開銷。當然具體的可以看看這篇博客,現在先來看下I/O多路復用的流程:
(1)當用戶進程調用了select,那么整個進程會被block;
(2)而同時,kernel會“監視”所有select負責的socket;
(3)當任何一個socket中的數據準備好了,select就會返回;
(4)這個時候用戶進程再調用read操作,將數據從kernel拷貝到用戶進程。
這個圖和BIO的圖其實并沒有太大的不同,事實上還更差一些。因為這里需要使用兩個系統調用(select和recvfrom),而BIO只調用了一個系統調用(recvfrom)。但是,用select的優勢在于它可以同時處理多個connection。
JAVA實現IO復用這里我們使用的是java.nio下模塊來完成I/O多路復用的例子。我用到的Selector(選擇器),是Java NIO中能夠檢測一到多個NIO通道,并能夠知曉通道是否為諸如讀寫事件做好準備的組件。這樣,一個多帶帶的線程可以管理多個channel,從而管理多個網絡連接。
Selector的使用 Selector的創建Selector selector = Selector.open();向Selector注冊通道
為了將Channel和Selector配合使用,必須將channel注冊到selector上。通過SelectableChannel.register()方法來實現,如下:
channel.configureBlocking(false); SelectionKey key = channel.register(selector,Selectionkey.OP_READ);
register()方法的第二個參數是一個“interest集合”,意思是在通過Selector監聽Channel時對什么事件感興趣??梢员O聽四種不同類型的事件:Connect、Accept、Read、Write
通道觸發了一個事件意思是該事件已經就緒。所以,某個channel成功連接到另一個服務器稱為“連接就緒”。一個server socket channel準備好接收新進入的連接稱為“接收就緒”。一個有數據可讀的通道可以說是“讀就緒”。等待寫數據的通道可以說是“寫就緒”。
這四種事件用SelectionKey的四個常量來表示:
SelectionKey.OP_CONNECT可連接
SelectionKey.OP_ACCEPT可接受連接
SelectionKey.OP_READ可讀
SelectionKey.OP_WRITE可寫
SelectionKey當向Selector注冊Channel時,register()方法會返回一個SelectionKey對象。它包含了:
interest集合
ready集合
Channel
Selector
附加的對象(可選)
interest集合是你所選擇的感興趣的事件集合??梢酝ㄟ^SelectionKey讀寫interest集合,像這樣:
int interestSet = selectionKey.interestOps(); boolean isInterestedInAccept = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT; boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT; boolean isInterestedInRead = interestSet & SelectionKey.OP_READ; boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;
可以看到,用“位與”操作interest 集合和給定的SelectionKey常量,可以確定某個確定的事件是否在interest 集合中。
ready 集合是通道已經準備就緒的操作的集合。在一次選擇(Selection)之后,你會首先訪問這個ready set。Selection將在下一小節進行解釋。可以這樣訪問ready集合:
int readySet = selectionKey.readyOps();
可以用像檢測interest集合那樣的方法,來檢測channel中什么事件或操作已經就緒。但是,也可以使用以下四個方法,它們都會返回一個布爾類型:
selectionKey.isAcceptable(); selectionKey.isConnectable(); selectionKey.isReadable(); selectionKey.isWritable();從SelectionKey訪問Channel和Selector
Channel channel = selectionKey.channel(); Selector selector = selectionKey.selector();java代碼
/** * @program: NIOServer * @description: 服務端 * @author: tEngSHe789 **/ public class NIOServer { public static void main(String[] args) throws IOException { System.out.println("服務端Start...."); // 創建通道 ServerSocketChannel serverSocketChannel=ServerSocketChannel.open(); // 設置非阻塞 serverSocketChannel.configureBlocking(false); // 綁定連接 serverSocketChannel.bind(new InetSocketAddress(8888)); // 獲取選擇器 Selector selector=Selector.open(); // 將通道注冊到選擇器 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); // 輪調式獲取選擇“已經準備就緒”的事件 while (selector.select() > 0){ // 獲取當前選擇器的左右已經準備就緒的監聽事件(選擇key) Iterator客戶端:iterator=selector.selectedKeys().iterator(); while (iterator.hasNext()){ // 獲取準備就緒事件 SelectionKey selectionKey=iterator.next(); // 判斷具體是什么事件 if (selectionKey.isAcceptable()){//如果是“接受就緒” SocketChannel socketChannel=serverSocketChannel.accept();// 獲取連接 socketChannel.configureBlocking(false); // 設置非阻塞 //將該通道注冊到服務器上 socketChannel.register(selector, SelectionKey.OP_READ); }else if (selectionKey.isReadable()){//如是“已經就緒” SocketChannel socketChannel= (SocketChannel) selectionKey.channel();//獲取連接 //讀數據 ByteBuffer buffer=ByteBuffer.allocate(1024); int len = 0; //分散讀取 len=socketChannel.read(buffer); while (len > 0){ buffer.flip(); System.out.println(new String(buffer.array(),0,len)); buffer.clear(); } } iterator.remove(); } } } }
/** * @program: NIOClient * @description: 客戶端 * @author: tEngSHe789 **/ public class NIOClient { public static void main(String[] args) throws IOException { System.out.println("客戶端Start...."); // 創建通道 SocketChannel socketChannel=SocketChannel.open(new InetSocketAddress("127.0.0.1",8888)); // 設置SocketChannel接口為非阻塞 socketChannel.configureBlocking(false); //指定緩沖區大小 ByteBuffer buffer=ByteBuffer.allocate(1024); Scanner scanner=new Scanner(System.in); while (scanner.hasNext()){ String msg = scanner.next(); // 存儲 buffer.put((new Date().toString()+" "+msg).getBytes()); // 翻轉 buffer.flip(); // 聚集寫入 socketChannel.write(buffer); // 釋放 buffer.clear(); } socketChannel.close(); } }python實現IO復用
對比java用的是Selector,可以幫我們在默認操作系統下選擇最合適的select, poll, epoll這三種多路復合模型,python是通過一種機制一個進程能同時等待多個文件描述符,而這些文件描述符(套接字描述符)其中的任意一個進入讀就緒狀態,select()函數就可以返回。
服務端from socket import * import select s=socket(AF_INET,SOCK_STREAM) s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) s.bind(("127.0.0.1",8888)) s.listen(5) s.setblocking(False) #設置socket的接口為非阻塞 read_l=[s,] # 數據可讀通道的列表 while True: # 監聽的read_l中的socket對象內部如果有變化,那么這個對象就會在r_l # 第二個參數里有什么對象,w_l中就有什么對象 # 第三個參數 如果這里的對象內部出錯,那會把這些對象加到x_l中 # 1 是超時時間 r_l,w_l,x_l=select.select(read_l,[],[],1) print(r_l) for ready_obj in r_l: if ready_obj == s: conn,addr=ready_obj.accept() #此時的ready_obj等于s read_l.append(conn) else: try: data=ready_obj.recv(1024) #此時的ready_obj等于conn if not data: ready_obj.close() read_l.remove(ready_obj) raise Exception("連接斷開") ready_obj.send(data.upper()) except ConnectionResetError: ready_obj.close() read_l.remove(ready_obj)客戶端
from socket import * c=socket(AF_INET,SOCK_STREAM) c.connect(("127.0.0.1",8888)) while True: msg=input(">>>: ") if not msg:continue c.send(msg.encode("utf-8")) data=c.recv(1024) print(data.decode("utf-8"))AIO(asynchronous io)
真正的異步I/O很牛逼,流程大概如下:
(1)用戶進程發起read操作之后,立刻就可以開始去做其它的事。
(2)而另一方面,從kernel的角度,當它受到一個asynchronous read之后,首先它會立刻返回,所以不會對用戶進程產生任何block。
(3)然后,kernel會等待數據準備完成,然后將數據拷貝到用戶內存,當這一切都完成之后,kernel會給用戶進程發送一個signal,告訴它read操作完成了。
JavaJava中使用AIO需要用到java.nio.channels.AsynchronousChannelGroup和java.nio.channels.AsynchronousServerSocketChannel的包,由于實際項目鮮有人用,就不演示了
總結回顧一下各個IO Model的比較,如圖所示:
blocking io :阻塞型io,再熟悉不過,處理accept、read、write都會阻塞用戶進程
non blocking io:當通過系統調用的時候,如果沒有連接或者數據到達就直接返回一個錯誤,用戶進程不阻塞但是不斷的輪詢。注意這個不是java nio框架中對應的網絡模型
io multiplexing:io多路復用才是nio對應的網絡io模型。該模型對于用戶進程也是阻塞的,優點是可以同時支持多個connetciotn。前三種都屬于同步模式,既然都是同步的,如果要做到看似非阻塞,那么就需要輪詢機制。相對于上一種模型,這種只是將輪詢從用戶進程轉移到了操作系統內核,通過調用select函數,不斷輪詢多個connection是否ready,如果有一種ready好的,就通過事件通知用戶進程,用戶進程再通過事件來處理。所以在java的nio中會看到一大堆事件處理。這種模型的阻塞不是在socket層面的阻塞,而是在調動select函數的阻塞。而且相對于blocking io,還多了一次select的系統調用,其實性能會更低,所以在低吞吐量下,這種io不見得比bio+線程池的模型優越。
sign driven:極少使用,不知道
async io :java7時候開始升級,也成為nio2。實現了異步的io。前三種都是通過用戶進程在主動獲取(bio的阻塞,nbio的輪詢和iomult的按事件獲?。鴄io交互很簡單,用戶進程調用后立即返回,用戶進程不阻塞,內核當完成網絡io和數據復制后,主動通知用戶進程。前面說到的系統內核做的操作,除了等待網絡io就緒數據到達內核,還有從系統內核復制用戶空間去的過程,異步io這兩者對于用戶進程而言都是非阻塞的,而前三種,在數據從內核復制到用戶空間這個過程,都是阻塞的。
參考資料前言說的那本書
Ron Hitchens于2002年 著的《java nio》
findumars
冬瓜蔡
彼岸船夫
NIO的/分散讀取和聚集寫入
并發編程網
感謝
續1s時間全片結束,覺得我寫的不錯?想要了解更多精彩新姿勢?趕快打開我的
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/76954.html
摘要:緩沖區的限制不能為負,并且不能大于其容量。如果指向的位置超過限制,則拋出異常。使用臨時緩沖區執行低層次操作。臨時緩沖區對象離開作用域,并最終成為被回收的無用數據。 前天剛好看了點《UNIX網絡編程》,比較頭大?,F在我來整理一下所學所得,并用于個人備忘。如果有不對,請批評。 想要解鎖更多新姿勢?請訪問https://blog.tengshe789.tech/ IO模型介紹 IO模型是什么...
摘要:參考鏈接面向對象編程模型現在的很多編程語言基本都具有面向對象的思想,比如等等,而面向對象的主要思想對象,類,繼承,封裝,多態比較容易理解,這里就不多多描述了。 前言 在我們的日常日發和學習生活中會常常遇到一些名詞,比如 命令式編程模型,聲明式編程模型,xxx語言是面向對象的等等,這個編程模型到處可見,但是始終搞不清是什么?什么語言又是什么編程模型,當你新接觸一門語言的時候,有些問題是需...
摘要:編程基礎要學習如何用進行數據分析,數據分析師建議第一步是要了解一些的編程基礎,知道的數據結構,什么是向量列表數組字典等等了解的各種函數及模塊。數據分析師認為數據分析有的工作都在處理數據。 showImg(https://segmentfault.com/img/bVbnbZo?w=1024&h=653); 本文為CDA數據分析研究院原創作品,轉載需授權 1.為什么選擇Python進行數...
摘要:四種模型把同步阻塞同步非阻塞異步阻塞異步非阻塞的模型講得很清楚。有人對于模型有一些批判,認為多線程模型同步阻塞模型不比事件模型差,講了提到的多線程模型的性能瓶頸在如今的內核里已經不存在了,而多線程模型開發起來更簡單。 四種IO模型 Boost application performance using asynchronous I/O把同步阻塞、同步非阻塞、異步阻塞、異步非阻塞的模型講...
閱讀 3136·2021-11-11 16:54
閱讀 2290·2021-09-04 16:48
閱讀 3219·2019-08-29 16:08
閱讀 641·2019-08-29 15:13
閱讀 1343·2019-08-29 15:09
閱讀 2659·2019-08-29 12:45
閱讀 1926·2019-08-29 12:12
閱讀 444·2019-08-26 18:27