NIO與Netty-1-BIO
標簽: NIO與Netty java socket nio netty
NIO與Netty-1-BIO
文章目錄
-
BIO是同步阻塞IO,在服務端獲取等待連接時需要阻塞,且每個連接的socket去獲取客戶端傳來的數據也需要阻塞,因此需要為每個連接都需要建立一個線程來處理,所以線程過多性能較低。
-
示例
-
//一個BIO的服務器 public static void main(String[] args) throws Exception { ServerSocket serverSocket = new ServerSocket(6666); System.out.println("服務器啟動了"); while (true) { Socket socket = serverSocket.accept();//此方法無連接會阻塞 System.out.println("連接到一個客戶端"); Thread connect=new Thread(new Runnable() { //創建一個線程來處理此連接 public void run() { handlerConnect(socket);//連接處理函數 } }); connect.run(); } } public static void handlerConnect(Socket socket) { try { byte[] bytes = new byte[1024]; //通過socket 獲取輸入流 InputStream inputStream = socket.getInputStream(); //循環的讀取客戶端發送的數據 while (true) { int read = inputStream.read(bytes);//此方法讀取客戶端傳來的信息會阻塞 if(read != -1) { System.out.println(new String(bytes, 0, read)); //輸出客戶端發送的數據 } else { break; } } }catch (Exception e) { e.printStackTrace(); }finally { System.out.println("關閉和client的連接"); try { socket.close(); }catch (Exception e) { e.printStackTrace(); } } }
-
-
BIO阻塞模型
- BIO存在一個負責監聽建立連接事件的線程,在有新連接時便創建一個線程來處理此連接。
- 因此連接數將等于處理連接的線程數。
- 因為是阻塞式IO,單線程情況下,處理者線程可能阻塞在其中一個套接字的read上,導致另一個套接字即使準備好了數據也無法處理,這個時候解決的方法就只能是針對每一個套接字,都新建一個線程處理其數據讀取。
-
ServerSocket源碼解讀
-
ServerSocket創建流程
-
//以ServerSocket(int port)構造器為例 ServerSocket serverSocket = new ServerSocket(6666);
-
//ServerSocket(int port)構造方法 public ServerSocket(int port) throws IOException { this(port, 50, null); //調用ServerSocket(int port, int backlog, InetAddress bindAddr) } public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException { setImpl();//*設置serverSocket實例的SocketImpl屬性 /**代碼省略-port、baklog校驗**/ try { bind(new InetSocketAddress(bindAddr, port), backlog);//*綁定端口 } /**代碼省略-異常處理**/ }
-
ServerSocket.setImpl()
-
private void setImpl() { if (factory != null) {//此時factory為null /**代碼省略-利用工廠創建impl**/ } else {//執行此代碼塊 impl = new SocksSocketImpl();//創建一個新的SocketImpl設置上去 } if (impl != null) impl.setServerSocket(this);//將剛剛創建的impl與此serverSocket綁定 }
-
-
ServerSocket.bind(SocketAddress endpoint, int backlog)
-
public void bind(SocketAddress endpoint, int backlog) throws IOException { /**代碼省略-校驗拋異常**/ InetSocketAddress epoint = (InetSocketAddress) endpoint; /**代碼省略-endpoint校驗**/ try { SecurityManager security = System.getSecurityManager(); if (security != null) security.checkListen(epoint.getPort()); //為此ServerSocket的SocketImpl綁定監聽的地址和端口 getImpl().bind(epoint.getAddress(), epoint.getPort()); //為impl設置為監聽模式 getImpl().listen(backlog); bound = true; } /**代碼省略-異常處理**/ }
-
-
注意盡管調用了setImpl但第一次調用getImpl()方法時還會對impl進行一些設置,例如獲取文件描述符fd。getImpl->createImpl->impl.create->獲取文件描述符。
-
impl的bind與listen分別在jvm層調用了操作系統的**bind()和listen()**函數。
-
-
ServerSocket.accept()解讀
-
//通過此方法建立連接,并返回連接對應的socket Socket socket = serverSocket.accept();
-
此方法為阻塞的,若沒有連接到來則一直阻塞,有連接到來才返回新建立的socket。
-
//ServerSocket.accept() public Socket accept() throws IOException { /**代碼省略-校驗拋異常**/ Socket s = new Socket((SocketImpl) null);//創建新的socket對象 implAccept(s);//!!!!!!!!!!!! return s; }
-
ServerSocket.implAccept(Socket s)
-
protected final void implAccept(Socket s) throws IOException { SocketImpl si = null; try { if (s.impl == null)//此時s.impl為null s.setImpl();//為s設置一個新的impl else { s.impl.reset(); } si = s.impl; s.impl = null; si.address = new InetAddress(); si.fd = new FileDescriptor(); getImpl().accept(si);//通過s的新impl去執行具體的accpet邏輯 SocketCleanable.register(si.fd); /**代碼省略-權限檢查**/ } /**代碼省略-異常處理**/ s.impl = si; s.postAccept(); }
-
getImpl().accept(SocketImpl s)
-
protected void accept(SocketImpl s) throws IOException { acquireFD(); try { socketAccept(s);//具體的accept方法 } finally { releaseFD(); } }
-
PlainSocketImpl.socketAccept(SocketImpl s)
-
void socketAccept(SocketImpl s) throws IOException { int nativefd = checkAndReturnNativeFD(); /**代碼省略**/ int newfd = -1; InetSocketAddress[] isaa = new InetSocketAddress[1]; if (timeout <= 0) {//無超時時間的accept newfd = accept0(nativefd, isaa); //調用jvm中的accept方法 //jvm調用操作系統的accept方法獲取連接 //返回新連接對應的文件描述符 } else {//含有超時時間的accept configureBlocking(nativefd, false); try { waitForNewConnection(nativefd, timeout); newfd = accept0(nativefd, isaa); //調用jvm中的accept方法 if (newfd != -1) { configureBlocking(newfd, true); } } finally { configureBlocking(nativefd, true); } } /* 將s的fd設置為獲取到的連接的文件描述符 */ fdAccess.set(s.fd, newfd); /* 設置建立的新連接的端口號,ip地址等 */ InetSocketAddress isa = isaa[0]; s.port = isa.getPort(); s.address = isa.getAddress(); s.localport = localport; /**代碼省略**/ }
-
accept0方法調用的jvm本地方法最終是通過調用操作系統的**accept()**函數實現的。
-
-
-
-
-
-
-
Socket中InputStream解讀
-
每個連接的Socket是由ServerSocket.accpet()返回來的,若想讀取連接中客戶端發送的數據,則需要獲取InputStream,并調用其read方法。
-
InputStream獲取流程
-
protected synchronized InputStream getInputStream() throws IOException { synchronized (fdLock) { /**代碼省略-校驗拋異常**/ if (socketInputStream == null) socketInputStream = new SocketInputStream(this); } return socketInputStream; }
-
new SocketInputStream(this)
-
SocketInputStream(AbstractPlainSocketImpl impl) throws IOException { super(impl.getFileDescriptor());//設置文件描述符 this.impl = impl;//設置此流對應的impl socket = impl.getSocket();//設置對應的socket }
-
-
-
-
SocketInputStream.read(byte b[])
-
public int read(byte b[]) throws IOException { return read(b, 0, b.length); } public int read(byte b[], int off, int length) throws IOException { return read(b, off, length, impl.getTimeout()); } int read(byte b[], int off, int length, int timeout) throws IOException { int n; if (eof) {//連接已經結束 return -1; } /**代碼省略-校驗拋異常**/ FileDescriptor fd = impl.acquireFD();//獲取對應的impl的文件描述符 try { n = socketRead(fd, b, off, length, timeout);//真正的read函數 if (n > 0) { return n; } } catch (ConnectionResetException rstExc) { impl.setConnectionReset(); } finally { impl.releaseFD(); } /**代碼省略-校驗拋異常**/ eof = true; return -1; }
-
socketRead(fd, b, off, length, timeout)
-
private int socketRead(FileDescriptor fd, byte b[], int off, int len, int timeout) throws IOException { return socketRead0(fd, b, off, len, timeout);//調用jvm本地方法 }
-
調用的jvm本地方法最終也是通過操作系統的**recv()**函數來實現read的。
-
-
-
-
-
對應的操作系統方法
-
java的socket編程其實底層也是對應的c/c++對應的那套操作系統的socket編程方法。
-
作用 java方法 操作系統方法 創建socket獲得fd impl.socketCreate(boolean isServer) socket() 將host,port綁定到socket上 impl.bind(InetAddress host, int port) bind() 將socket設置為監聽狀態 impl.listen(int backlog) listen() 服務端socket獲取連接 impl.accept(SocketImpl s) accept() 連接socket讀取收到的內容 socketInputStream.read(byte b[]) recv() -
操作系統方法詳解
-
socket() https://baike.baidu.com/item/socket%28%29 bind() https://baike.baidu.com/item/bind%28%29 listen() https://baike.baidu.com/item/listen%28%29 accept() https://baike.baidu.com/item/accept%28%29 recv() https://baike.baidu.com/item/recv%28%29
-
-
智能推薦
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 所寫,首先總結了前端組件化樣式中的最佳實踐原則,然后在此基...
猜你喜歡
19.vue中封裝echarts組件
19.vue中封裝echarts組件 1.效果圖 2.echarts組件 3.使用組件 按照組件格式整理好數據格式 傳入組件 home.vue 4.接口返回數據格式...
【一只蒟蒻的刷題歷程】【藍橋杯】歷屆試題 九宮重排 (八數碼問題:BFS+集合set)
資源限制 時間限制:1.0s 內存限制:256.0MB 問題描述 如下面第一個圖的九宮格中,放著 1~8 的數字卡片,還有一個格子空著。與空格子相鄰的格子中的卡片可以移動到空格中。經過若干次移動,可以形成第二個圖所示的局面。 我們把第一個圖的局面記為:12345678. 把第二個圖的局面記為:123.46758 顯然是按從上到下,從左到右的順序記錄數字,空格記為句點。 本題目的任務是已知九宮的初態...
dataV組件容器寬高發生變化后,組件不會自適應解決方法
項目中需要大屏幕數據展示,于是使用了dataV組件,但是使用是發現拖動瀏覽器邊框,dataV組件顯示異常,如圖: 于是查了官網,官網的解釋如下: 于是按照官網的意思編寫代碼: 于是可以自適應了...
CSS3干貨10:如何做一個板塊標題水平線左邊帶顏色效果
很多網站在設計欄目標題的時候,喜歡用下劃線分開欄目標題和內容部分。 而且線條左邊的部分往往還有顏色,且這個顏色跟標題的文字長短保持一致。效果如圖所示: 這種效果其實很簡單。 我這里給大家推薦兩種方式: 假定我們的標題部分 HTML 結構如下: 方式一:利用下邊框。灰色部分是 h1 的下邊框,藍色部分是 span 標簽的下邊框。 h1 的高度為 40px,span 也設置它的高度為 40px。這樣,...