Web全棧~28.網絡編程

Web全棧~28.網絡編程

上一期

前言

       網絡編程往往都是困難、複雜,而且極易出錯的。程序員必須掌握與網絡有關的大量細節,有時甚至要對硬件有深刻的認識。一般地,我們需要理解網絡協議中不同的“層”(Layer)。而且對於每個連網庫,一般都包含了數量衆多的函數,分別涉及信息塊的連接、打包和拆包;這些塊的來回運輸;以及握手等等。這是一項令人痛苦的工作。但是,連網本身的概念並不是很難。我們想獲得位於其他地方某臺機器上的信息,並把它們移到這兒;或者相反。這與讀寫文件非常相似,只是文件存在於遠程機器上,而且遠程機器有權決定如何處理我們請求或者發送的數據。

       Java 最出色的一個地方就是它的“無痛苦連網”概念。有關連網的基層細節已被儘可能地提取出去,並隱藏在JVM 以及Java 的本機安裝系統裏進行控制。我們使用的編程模型是一個文件的模型;事實上,網絡連接(一個“套接字”)已被封裝到系統對象裏,所以可象對其他數據流那樣採用同樣的方法調用。除此以外,在我們處理另一個連網問題——同時控制多個網絡連接——的時候,Java 內建的多線程機制也是十分方便的。

       當然,爲了分辨來自別處的一臺機器,以及爲了保證自己連接的是希望的那臺機器,必須有一種機制能獨一無二地標識出網絡內的每臺機器。早期網絡只解決了如何在本地網絡環境中爲機器提供唯一的名字。但Java面向的是整個因特網,這要求用一種機制對來自世界各地的機器進行標識。爲達到這個目的,我們採用了IP(互聯網地址)的概念。

InetAddress類和InetSocketAddress 類的使用

   public static void main(String[] args) throws IOException {
   
   
        //認識本機的IP地址
        InetAddress ia = InetAddress.getLocalHost();
        System.out.println(ia);
        System.out.println(ia.getHostName());
        System.out.println(ia.getHostAddress());
        byte[] buf = ia.getAddress();
        System.out.println(buf.length);
        System.out.println(Arrays.toString(buf));
        //認識百度的IP地址
        InetAddress ia2 = InetAddress.getByName("www.baidu.com");
        System.out.println(ia2);
        System.out.println(ia2.getHostAddress());
        System.out.println(Arrays.toString(ia2.getAddress()));

        InetAddress addr = InetAddress.getByName("www.baidu.com");
        int port = 4321;
        InetSocketAddress isa = new InetSocketAddress(addr, port);
        System.out.println(isa);
        System.out.println(isa.getAddress());
        System.out.println(isa.getPort());
    }

服務器和客戶機

       網絡最基本的精神就是讓兩臺機器連接到一起,並相互“交談”或者“溝通”。一旦兩臺機器都發現了對方,就可以展開一次令人愉快的雙向對話。服務器和客戶機它們之間的區別只有在客戶機試圖同服務器連接的時候才顯得非常明顯。一旦連通,就變成了一種雙向通信,誰來扮演服務器或者客戶機便顯得不那麼重要了。所以服務器的主要任務是偵聽建立連接的請求,這是由我們創建的特定服務器對象完成的。而客戶機的任務是試着與一臺服務器建立連接,這是由我們創建的特定客戶機對象完成的。一旦連接建好,那麼無論在服務器端還是客戶機端,連接只是魔術般地變成了一個 IO數據流對象。從這時開始,我們可以象讀寫一個普通的文件那樣對待連接。所以一旦建好連接,我們只需使用自己熟悉的 IO 命令即可。這正是Java連網最方便的一個地方。

簡單的網絡連接案例

客戶端
public class ClientTest {
   
   
    public static void main(String[] args) throws IOException {
   
   
        //localhost 表示 127.0.0.1 也就是主機自身  8080表示端口號  一般的web項目默認端口號是8080
        Socket socket = new Socket(InetAddress.getByName("localhost"),8080);
        //向服務器端發出一個請求
        OutputStream outputStream = socket.getOutputStream();
        DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
        dataOutputStream.writeUTF("客戶端給服務器端發送了一個請求");
        //釋放資源
        outputStream.close();
        dataOutputStream.close();
    }
}
服務器端
public class ServerTest {
   
   
    public static void main(String[] args) throws IOException {
   
   
        System.out.println("----------------服務器已啓動----------------");
        //監聽客戶端
        ServerSocket serverSocket = new ServerSocket(8080);
        Socket socket = serverSocket.accept();
        //接受客戶端發來的請求
        InputStream inputStream = socket.getInputStream();
        DataInputStream dataInputStream = new DataInputStream(inputStream);
        //打印結果
        String result = dataInputStream.readUTF();
        System.out.println("服務器端收到了客戶端發來的請求 : " + result);
        dataInputStream.close();
        inputStream.close();
    }
}
端口:機器內獨一無二的場所

       有些時候,一個IP地址並不足以完整標識一個服務器。這是由於在一臺物理性的機器中,往往運行着多個服務器(程序)。由IP 表達的每臺機器也包含了“端口”(Port)。我們設置一個客戶機或者服務器的時候,必須選擇一個無論客戶機還是服務器都認可連接的端口。就象我們去拜會某人時,IP 地址是他居住的房子,而端口是他在的那個房間。注意端口並不是機器上一個物理上存在的場所,而是一種軟件抽象(主要是爲了表述的方便)。客戶程序知道如何通過機器的IP 地址同它連接,但怎樣才能同自己真正需要的那種服務連接呢(一般每個端口都運行着一種服務,一臺機器可能提供了多種服務,比如HTTP 和 FTP等等)?端口編號在這裏扮演了重要的角色,它是必需的一種二級定址措施。也就是說,我們請求一個特定的端口,便相當於請求與那個端口編號關聯的服務。“報時”便是服務的一個典型例子。通常,每個服務都同一臺特定服務器機器上的一個獨一無二的端口編號關聯在一起。客戶程序必須事先知道自己要求的那項服務的運行端口號。系統服務保留了使用端口1 到端口 1024 的權力,所以不應讓自己設計的服務佔用這些以及其他任何已知正在使用的端口。

       服務器的全部工作就是等候建立一個連接,然後用那個連接產生的Socket創建一個 InputStream 以及一個OutputStream。在這之後,它從InputStream讀入的所有東西都會反饋給OutputStream,直到接收到行中止(END)爲止,最後關閉連接。客戶機連接與服務器的連接,然後創建一個OutputStream。文本行通過 OutputStream發送。客戶機也會創建一個InputStream,用它收聽服務器說些什麼(本例只不過是反饋回來的同樣的字句)。服務器與客戶機(程序)都使用同樣的端口號,而且客戶機利用本地主機地址連接位於同一臺機器中的服務器(程序),所以不必在一個物理性的網絡裏完成測試(在某些配置環境中,可能需要同真正的網絡建立連接,否則程序不能工作——儘管實際並不通過那個網絡通信)

       ServerSocket需要的只是一個端口編號,不需要 IP地址(因爲它就在這臺機器上運行)。調用accept()時,方法會暫時陷入停頓狀態(堵塞),直到某個客戶嘗試同它建立連接。換言之,儘管它在那裏等候連接,但其他進程仍能正常運行(參考第 14章)。建好一個連接以後,accept()就會返回一個 Socket 對象,它是那個連接的代表。清除套接字的責任在這裏得到了很藝術的處理。假如ServerSocket 構建器失敗,則程序簡單地退出(注意必須保證ServerSocket 的構建器在失敗之後不會留下任何打開的網絡套接字)。針對這種情況,main()會“擲”出一個IOException 違例,所以不必使用一個try塊。若 ServerSocket構建器成功執行,則其他所有方法調用都必須到一個 try-finally代碼塊裏尋求保護,以確保無論塊以什麼方式留下,ServerSocket都能正確地關閉。

網路登錄,雙向通信

用戶實體類, get set省略
public class Person implements Serializable {
   
   
    //賬號
    private String son;
    //密碼
    private String password;
}
客戶端
public class ClientTest {
   
   
    public static void main(String[] args) throws IOException {
   
   
        //localhost 表示 127.0.0.1 也就是主機自身  8080表示端口號  一般的web項目默認端口號是8080
        Socket socket = new Socket(InetAddress.getByName("localhost"),8080);
        //向服務器端發出一個請求
        InputStream inputStream = socket.getInputStream();
        OutputStream outputStream = socket.getOutputStream();
        //使用對象流
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
        //獲得用戶輸入的結果
        Person person = new Person("369613719","123456");
        //將對象發送過去
        objectOutputStream.writeObject(person);
        //接受服務器發來的結果
        DataInputStream dataInputStream = new DataInputStream(inputStream);
        System.out.println(dataInputStream.readUTF());
        //釋放資源
        outputStream.close();
        objectOutputStream.close();
    }
}
服務器端
public class ServerTest {
   
   
    public static void main(String[] args) throws IOException, ClassNotFoundException {
   
   
        System.out.println("----------------服務器已啓動----------------");
        //監聽客戶端
        ServerSocket serverSocket = new ServerSocket(8080);
        Socket socket = serverSocket.accept();
        //創建流用來數據傳輸
        OutputStream outputStream = socket.getOutputStream();
        InputStream inputStream = socket.getInputStream();
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
        //接受客戶端發來的請求
        Person person = (Person) objectInputStream.readObject();
        String result = "";
        //判斷是否登陸成功
        if("369613719".equals(person.getSon()) && "123456".equals(person.getPassword())){
   
   
            result = "登陸成功";
        }else{
   
   
            result = "登錄失敗";
        }
        //發送結果
        dataOutputStream.writeUTF(result);
        //釋放資源
        inputStream.close();
        outputStream.close();
        objectInputStream.close();
        dataOutputStream.close();
    }
}

       使用ObjectInputStream與ObjectOutputStream來實現對象的傳遞時需要注意的是。如果client端的源程序與server端的源程序位於兩個項目中,那麼需要傳遞對象的類的包名必須一樣,否則會拋出:java.lang.ClassNotFoundException的異常。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章