1. 程式人生 > >[紙上談兵]Java IO詳解(二)BIO

[紙上談兵]Java IO詳解(二)BIO

一、java io分類

java io目前包括了BIO(同步阻塞),NIO(同步非阻塞),AIO(非同步非阻塞)三種。如果不太瞭解同步、非同步、阻塞、非阻塞,看我前面的文章

二、JAVA BIO

BIO比較簡單,我先簡單說一下socket中關於埠的兩個方法。

socket.getLocalPort();//從字面理解是獲取本地埠號;對於服務端的socket就是獲取監聽的埠號;對於客戶端來講,獲取的就是客戶端socket中墮機分配的端
socket.getPort(); //這個方法是獲取遠端埠;對於服務端來講,獲取的就是客戶端socket隨機分配的埠號;對於客戶端來講,就是服務端的臨聽埠號。

 

不知道我上面的解釋大家能否理解。個人理解當客戶端和服務端建立連線後,共用一個socket(全雙工).對於一個socket(客戶端的socket),裡面是有宿主機ip,埠,伺服器的ip和埠.但站在服務端視角來看,socket資訊中就會有宿主機ip、埠,客戶端ip和埠。如果還不理解,請多看幾次。或看一下第三部分的文件再返回來看。

 

關於BIO知識可以看看網上的文章.下面的BIO的一個Demo可以直接跑,注意紅色部分

package com.cbird.io.aio;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.TimeUnit;

/**
 * <p>TODO</p>
 * <p>
 * <PRE>
 * <BR>    修改記錄
 * <BR>-----------------------------------------------
 * <BR>    修改日期         修改人          修改內容
 * </PRE>
 *
 * @author kugua
 * @version 1.0
 * @Date Created in 2018年07月11日 22:12
 * @since 1.0
 */
public class BioDemo {


    public static void main(String[] args) {

        new Thread(()->{
            BioServer.start();
        }).start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("start client");
        Client.send();
    }

    public static class BioServer {

        private static final int PORT = 8080;
        private static ServerSocket serverSocket;

        public static void start() {
            if (serverSocket != null) {
                System.out.println("serverSocket is not null");
                return ;
            }
            try {
                serverSocket = new ServerSocket(PORT);
                System.out.println("Bio server start");
                while (true) {
                    Socket socket = serverSocket.accept();
                    System.out.println("one connection port :" + socket.getPort());
new Thread(new ServerHandler(socket)).start(); } } catch (IOException e) { e.printStackTrace(); } finally { if (serverSocket != null) { System.out.println("close server"); try { serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } serverSocket = null; } } } } /** * 服務端程式 * @author kugua * @created * @param * @return */ public static class ServerHandler implements Runnable { private Socket socket; public ServerHandler(Socket socket) { this.socket = socket; } @Override public void run() { try (BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintWriter pw = new PrintWriter(socket.getOutputStream(), true)) { String line = br.readLine(); pw.println("你輸入的是:" + line); } catch (IOException e) { e.printStackTrace(); } } } public static class Client { private static int SERVER_PORT = 8080; private static final String SERVER_IP = "127.0.0.1"; public static void send() { int i = 0; while (true) { i ++; try (Socket socket = new Socket(SERVER_IP, SERVER_PORT); BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintWriter pw = new PrintWriter(socket.getOutputStream(), true)) { pw.println("My name is client" + i); TimeUnit.MILLISECONDS.sleep(400); String line = br.readLine(); System.out.println("server response:" + line + ", clientport:" + socket.getPort()
+ ",LocalPort:" + socket.getLocalPort()); } catch (Exception e) { e.printStackTrace(); } } } } }

這個是JAVA BIO的一個簡單示例,當然很容易想到服務端在處理客戶端socket連線時每次新建執行緒可以調整為執行緒池進行優化。

三、服務端只監聽了一個埠,為什麼可以和這麼多客戶端socket建立連線?

以下內容全是從參考中拷過來的。

要寫網路程式就必須用Socket,這是程式設計師都知道的。而且,面試的時候,我們也會問對方會不會Socket程式設計?一般來說,很多人都會說,Socket程式設計基本就是listen,accept以及send,write等幾個基本的操作。是的,就跟常見的檔案操作一樣,只要寫過就一定知道。
對於網路程式設計,我們也言必稱TCP/IP,似乎其它網路協議已經不存在了。對於TCP/IP,我們還知道TCP和UDP,前者可以保證資料的正確和可靠性,後者則允許資料丟失。最後,我們還知道,在建立連線前,必須知道對方的IP地址和埠號。除此,普通的程式設計師就不會知道太多了,很多時候這些知識已經夠用了。最多,寫服務程式的時候,會使用多執行緒來處理併發訪問。
 
我們還知道如下幾個事實:
1。一個指定的埠號不能被多個程式共用。比如,如果IIS佔用了80埠,那麼Apache就不能也用80埠了。
2。很多防火牆只允許特定目標埠的資料包通過。
3。服務程式在listen某個埠並accept某個連線請求後,會生成一個新的socket來對該請求進行處理。
於是,一個困惑了我很久的問題就產生了。如果一個socket建立後並與80埠繫結後,是否就意味著該socket佔用了80埠呢?如果是這樣的,那麼當其accept一個請求後,生成的新的socket到底使用的是什麼埠呢(我一直以為系統會預設給其分配一個空閒的埠號)?如果是一個空閒的埠,那一定不是80埠了,於是以後的TCP資料包的目標埠就不是80了--防火牆一定會組織其通過的!實際上,我們可以看到,防火牆並沒有阻止這樣的連線,而且這是最常見的連線請求和處理方式。我的不解就是,為什麼防火牆沒有阻止這樣的連線?它是如何判定那條連線是因為connet80埠而生成的?是不是TCP資料包裡有什麼特別的標誌?或者防火牆記住了什麼東西?
 後來,我又仔細研讀了TCP/IP的協議棧的原理,對很多概念有了更深刻的認識。比如,在TCP和UDP同屬於傳輸層,共同架設在IP層(網路層)之上。而IP層主要負責的是在節點之間(End to End)的資料包傳送,這裡的節點是一臺網路裝置,比如計算機。因為IP層只負責把資料送到節點,而不能區分上面的不同應用,所以TCP和UDP協議在其基礎上加入了埠的資訊,埠於是標識的是一個節點上的一個應用。除了增加埠資訊,UPD協議基本就沒有對IP層的資料進行任何的處理了。而TCP協議還加入了更加複雜的傳輸控制,比如滑動的資料傳送視窗(Slice Window),以及接收確認和重發機制,以達到資料的可靠傳送。不管應用層看到的是怎樣一個穩定的TCP資料流,下面傳送的都是一個個的IP資料包,需要由TCP協議來進行資料重組。
所以,我有理由懷疑,防火牆並沒有足夠的資訊判斷TCP資料包的更多資訊,除了IP地址和埠號。而且,我們也看到,所謂的埠,是為了區分不同的應用的,以在不同的IP包來到的時候能夠正確轉發。

TCP/IP只是一個協議棧,就像作業系統的執行機制一樣,必須要具體實現,同時還要提供對外的操作介面。就像作業系統會提供標準的程式設計介面,比如Win32程式設計介面一樣,TCP/IP也必須對外提供程式設計介面,這就是Socket程式設計介面--原來是這麼回事啊!

在Socket程式設計接口裡,設計者提出了一個很重要的概念,那就是socket。這個socket跟檔案控制代碼很相似,實際上在BSD系統裡就是跟檔案控制代碼一樣存放在一樣的程序控制代碼表裡。這個socket其實是一個序號,表示其在控制代碼表中的位置。這一點,我們已經見過很多了,比如檔案控制代碼,視窗控制代碼等等。這些控制代碼,其實是代表了系統中的某些特定的物件,用於在各種函式中作為引數傳入,以對特定的物件進行操作--這其實是C語言的問題,在C++語言裡,這個控制代碼其實就是this指標,實際就是物件指標啦。

現在我們知道,socket跟TCP/IP並沒有必然的聯絡。Socket程式設計介面在設計的時候,就希望也能適應其他的網路協議。所以,socket的出現只是可以更方便的使用TCP/IP協議棧而已,其對TCP/IP進行了抽象,形成了幾個最基本的函式介面。比如create,listen,accept,connect,read和write等等。

現在我們明白,如果一個程式建立了一個socket,並讓其監聽80埠,其實是向TCP/IP協議棧聲明瞭其對80埠的佔有。以後,所有目標是80埠的TCP資料包都會轉發給該程式(這裡的程式,因為使用的是Socket程式設計介面,所以首先由Socket層來處理)。所謂accept函式,其實抽象的是TCP的連線建立過程。accept函式返回的新socket其實指代的是本次建立的連線,而一個連線是包括兩部分資訊的,一個是源IP和源埠,另一個是宿IP和宿埠。所以,accept可以產生多個不同的socket,而這些socket裡包含的宿IP和宿埠是不變的,變化的只是源IP和源埠。這樣的話,這些socket宿埠就可以都是80,而Socket層還是能根據源/宿對來準確地分辨出IP包和socket的歸屬關係,從而完成對TCP/IP協議的操作封裝!而同時,放火牆的對IP包的處理規則也是清晰明瞭,不存在前面設想的種種複雜的情形。

明白socket只是對TCP/IP協議棧操作的抽象,而不是簡單的對映關係,這很重要!

套接字(socket)概念
套接字(socket)是通訊的基石,是支援TCP/IP協議的網路通訊的基本操作單元。它是網路通訊過程中端點的抽象表示,包含進行網路通訊必須的五種資訊:連線使用的協議,本地主機的IP地址,本地程序的協議埠,遠地主機的IP地址,遠地程序的協議埠。

應用層通過傳輸層進行資料通訊時,TCP會遇到同時為多個應用程式程序提供併發服務的問題。多個TCP連線或多個應用程式程序可能需要通過同一個 TCP協議埠傳輸資料。為了區別不同的應用程式程序和連線,許多計算機作業系統為應用程式與TCP/IP協議互動提供了套接字(Socket)介面。應用層可以和傳輸層通過Socket介面,區分來自不同應用程式程序或網路連線的通訊,實現資料傳輸的併發服務

 

看完上面的資料我想大家對socket會有一個入門級的認識,如果還不清楚,多看幾次,然後還不懂,就多百度或留言,如有問題請隨時指出。

五 、參考資料

https://blog.csdn.net/anxpp/article/details/51512200

https://blog.csdn.net/perfectguyipeng/article/details/70238963

https://blog.csdn.net/u011555996/article/details/72877549