Java網絡編程簡明教程

Java網絡編程簡明教程

計算機網絡相關概念

計算機網絡是兩臺或更多的計算機組成的網絡,同一網絡內的任意兩臺計算機能夠直接通訊,全部計算機必須遵循同一種網絡協議。html

  • 互聯網

    • 互聯網是鏈接計算機網絡的網絡
    • 互聯網採起TCP/IP協議
    • 其中最重要的兩個協議是TCP協議和IP協議
  • IP地址和網關

    • IP地址用於惟一標識一個網絡接口java

    • IPv4採用32位地址
      IPv4地址實際是一個二進制32位的整數,爲了便於識別,用十六進制表示後能夠分爲4組數字,每組數字轉換成十進制後用「.」隔開就是咱們見到的IP地址:
      enter description here編程

    • IPv6採用128位地址數組

    • 公網IP地址能夠直接被訪問瀏覽器

    • 內網IP地址只能在內網訪問服務器

    • 本機地址使用127.0.0.1網絡

    • 一般路由器或交換機有兩個網卡(兩個IP地址),分別鏈接兩個不一樣的網絡:
      enter description heresession

    • 同一網絡下的計算機能夠直接通訊,他們的網絡號相同,網絡號由IP地址和子掩碼按組對齊作與運算獲得:
      enter description here多線程

    • 不一樣網絡下的計算機須要經過路由器或交換機網絡設備間接通訊,這樣的網絡設備叫作網關:
      enter description here併發

    • 網關的做用是鏈接多個網絡,負責把一個網絡的數據包發送到另外一個網絡,過程叫作路由:
      enter description here

    • 一臺計算機的網絡擁有IP地址,子網掩碼和網關(路由器)三個關鍵配置:
      enter description here

  • 域名

因爲IP地址不便於記憶,一般使用域名來訪問特定的服務,域名解析服務器DNS負責將域名翻譯成對應的IP地址,客戶端再根據IP地址訪問服務器:
enter description here

  • TCP/IP協議

    • IP協議是一個分組交換協議,不保證可靠傳輸,一個數據包經過IP協議傳輸會自動分紅若干小的數據包而後經過網絡進行傳輸
    • TCP(Transmission Control Protocol)協議是一個傳輸控制協議,創建再IP協議之上,IP協議負責傳輸數據包,TCP協議負責控制傳輸數據包;TCP協議傳輸以前須要先創建鏈接,而後才能傳輸數據,傳輸完成後斷開鏈接;TCP協議是一個可靠傳輸協議,他經過接收確認,超時重傳實現;TCP協議支持雙向通訊,雙方能夠同時傳輸和接收數據
  • UDP協議

UDP(User Datagram Protocol)協議是數據報文協議,不面向鏈接,不保證可靠傳輸,因爲UDP協議傳輸效率高,一般用來傳輸視頻等能容忍丟失部分數據的文件。

  • Socket

Socket一般稱爲套接字,用於應用程序之間創建遠程鏈接,Socket內部經過TCP/IP協議進行數據傳輸,能夠簡單的理解爲對IP地址和端口號的描述。Socket接口是由計算機操做系統提供的,編程語言提供對Socket接口調用的封裝。一般計算機同時運行多個應用程序,僅僅有IP地址是沒法肯定由哪一個應用程序接收數據,因此操做系統抽象出Socket接口,每一個應用程序對應不一樣的socket(每一個網絡應用程序分配不一樣的端口號)。端口號的範圍是0~65535,小於1024的端口須要管理員權限,大於1024的端口能夠任意用戶的應用程序打開。

Socket編程須要實現服務器端和客戶端,由於這兩個設備通信時,須要知道對方的IP和端口號。一般服務器端有個固定的端口號,客戶端直接經過服務器的IP地址和端口號進行訪問服務器端,同時告知客戶端的端口號,因而他們之間就能夠經過socket進行通訊。
enter description here

TCP編程

  • TCP客戶端

Java提供了Socket類ServerSocket類對計算機操做系統的Socket進行調用。客戶端使用Socket(InetAddress, port)構造方法傳入IP地址和端口號打開Socket,與遠程服務區指定端口進行鏈接, 而後調用socket的getInputStream和getOutputStream方法獲取輸入和輸出流就能夠讀寫TCP的字節流:

// 鏈接遠程服務器
Socket socket = new Socket(InetAddress, port);
// 讀寫字節流
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
  • TCP服務端

服務器端經過ServerSocket(port)構造方法傳入端口號來監聽指定的端口,而後經過accept()方法獲得一個Socket對象與遠程客戶端創建鏈接,一樣調用Socket對象的getInputStream和getOutputStream方法就能夠讀寫字節流,服務器端完成傳輸後能夠經過close()方法關閉遠程鏈接和監聽端口:

// 監聽端口
ServerSocket serverSocket = new ServerSocket(port);
// 創建遠程鏈接
Socket socket = serverSocket.accept();
// 讀寫字節流
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
// 關閉鏈接
socket.close();
// 關閉監聽端口
serverSocket.close();
  • TCP編程實驗

咱們能夠在本機作一個小實驗,首先編寫一個客戶端的TCPClient類,經過Java提供的InetAddress類的getLoopbackAddress()方法得到localhost地址,而後使用Java的Socket類建立一個與本機8090端口的鏈接,再將讀取字節流包裝成一個BufferedReader對象、寫入字節流包裝成BufferedWriter對象。使用BufferedWriter寫入一個「time」字符串併發送到本機的8090端口,再用BufferedReader讀取本機8090端口返回的數據並打印出來。代碼以下:

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

public class TCPClient {

    public static void main(String[] args) throws IOException {
        // 獲取本機地址,即「127.0.0.1」
        InetAddress addr = InetAddress.getLoopbackAddress(); 
        // 與本機8090端口創建鏈接
        try (Socket sock = new Socket(addr, 8090)) {
        // 將讀寫字節流包裝成BufferedReader和BufferedWriter對象
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(sock.getInputStream(), StandardCharsets.UTF_8))) {
                try (BufferedWriter writer = new BufferedWriter(
                        new OutputStreamWriter(sock.getOutputStream(), StandardCharsets.UTF_8))) {
                    // 寫入「time」字符串
                    writer.write("time\n");
                    // 將寫入內存緩衝區的數據當即發送
                    writer.flush();
                    // 讀取本機8090端口返回的數據
                    String resp = reader.readLine();
                    System.out.println("Response: " + resp);
                }
            }
        }
    }
}

在相同包下寫一個服務端的TCPServer類,利用Java的ServerSocket類監聽8090端口並打印一句話「TCP server ready.」,而後用ServerSocket類的accept()方法與監聽到的訪問8090端口的客戶端請求創建鏈接,而後和客戶端同樣包裝讀寫字節流。服務端首先讀取數據,若是讀取到的數據是一個"time"字符串,則將當前時間信息返回給客戶端,若是不是則返回一個「require data」字符串給客戶端,最後關閉鏈接和關閉監聽接口。

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;

public class TCPServer {

    public LocalDateTime currentTime() {
        return LocalDateTime.now();
    }

    public static void main(String[] args) throws Exception {
        // 監聽8090端口
        ServerSocket ss = new ServerSocket(8090);
        System.out.println("TCP server ready.");
        // 創建鏈接
        Socket sock = ss.accept();
        // 包裝讀寫字節流
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(sock.getInputStream(), StandardCharsets.UTF_8))) {
            try (BufferedWriter writer = new BufferedWriter(
                    new OutputStreamWriter(sock.getOutputStream(), StandardCharsets.UTF_8))) {
                // 讀取發送到服務端的數據 
                String cmd = reader.readLine();
                // 若是數據是「time」字符串則將當前時間信息返回客戶端
                if ("time".equals(cmd)) {
                    writer.write(LocalDateTime.now().toString() + "\n");
                    // 將寫入內存緩衝區的數據當即發送
                    writer.flush();
                } else {
                    writer.write("require data\n");
                    writer.flush();
                }
            }
        }
        // 關閉鏈接
        sock.close();
        // 關閉監聽端口
        ss.close();
    }
}

咱們首先運行服務端TCPServer類的main方法,開始監聽8090端口,而且Console打印出「TCP server ready.」,而後運行客戶端TCPClient的main方法,咱們獲得Response信息,終端打印出了當前的時間信息:
enter description here
若是咱們先運行客戶端的main方法,咱們會獲得一個異常ConnectException: Connection refused,由於服務端並無開始監聽8090端口,沒法與客戶端創建socket鏈接。

  • TCP多線程

服務端的一個ServerSocket能夠同時和多個客戶端創建鏈接進行雙向通訊,實現起來也很簡單,在設置好監聽端口後,在一個無限for循環中調用ServerSocket的accept()方法,返回與客戶端新建的鏈接,再啓動線程或者使用線程池來處理客戶端的請求,這樣就能夠同時處理多個客戶端的鏈接,代碼以下:

public class TCPServer {

    public static void main(String[] args) throws Exception {
        @SuppressWarnings("resource")
        ServerSocket ss = new ServerSocket(8090);
        System.out.println("TCP server ready.");
        for (;;) {  // 無限for循環中返回客戶端新建的鏈接
            Socket sock = ss.accept();
            // 設置線程要處理的任務
            Runnable handler = new TimeHandler(sock);
            // 使用Java提供的ExecutorService建立線程池
            ExecutorService executor = Executors.newCachedThreadPool();
            // 線程處理任務
            executor.submit(handler);
            // 任務處理完畢,關閉線程
            executor.shutdown();
        }
    }
}

class TimeHandler implements Runnable {

    Socket sock;

    TimeHandler(Socket sock) {
        this.sock = sock;
    }

    @Override
    public void run() {
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(sock.getInputStream(), StandardCharsets.UTF_8))) {
            try (BufferedWriter writer = new BufferedWriter(
                    new OutputStreamWriter(sock.getOutputStream(), StandardCharsets.UTF_8))) {
                for (;;) {
                    String cmd = reader.readLine();
                    if ("q".equals(cmd)) {
                        writer.write("bye!\n");
                        writer.flush();
                        break;
                    } else if ("time".equals(cmd)) {
                        writer.write(LocalDateTime.now().toString() + "\n");
                        writer.flush();
                    } else {
                        writer.write("require data\n");
                        writer.flush();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                this.sock.close();
                
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

UDP編程

UDP協議不須要創建鏈接,能夠直接發送和接收數據,UDP協議不保證可靠傳輸,使用Java提供的DatagramSocket的send()和receive()方法就能夠發送和接收數據,UDP協議接收和發送數據沒有IO流接口。

  • 客戶端
public class UDPClient {

    public static void main(String[] args) throws Exception {
        InetAddress addr = InetAddress.getLoopbackAddress();
        try (DatagramSocket sock = new DatagramSocket()) { // 建立DatagramSocket對象
            sock.connect(addr, 9090); // 設置要訪問的服務端地址和端口
            byte[] data = "time".getBytes(StandardCharsets.UTF_8);
            DatagramPacket packet = new DatagramPacket(data, data.length); // 將字節流包裝成DatagramPacket對象
            sock.send(packet); // 發送數據
            System.out.println("Data was sent.");
            Thread.sleep(1000);
            byte[] buffer = new byte[1024];
            DatagramPacket resp = new DatagramPacket(buffer, buffer.length);
            sock.receive(resp); // 接收數據
            byte[] respData = resp.getData();
            String respText = new String(respData, 0, resp.getLength(), StandardCharsets.UTF_8);
            System.out.println("Response: " + respText);
        }
    }
}
  • 服務端
public class UDPServer {

    public LocalDateTime currentTime() {
        return LocalDateTime.now();
    }

    public static void main(String[] args) throws Exception {
        @SuppressWarnings("resource")
        DatagramSocket ds = new DatagramSocket(9090); // 設置要監聽的端口
        System.out.println("UDP server ready.");
        for (;;) {
            // 接收數據:
            byte[] buffer = new byte[1024];
            DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
            ds.receive(packet);
            byte[] data = packet.getData();
            String s = new String(data, StandardCharsets.UTF_8);
            System.out.println("Packet received from: " + packet.getSocketAddress() + " " + s);
            // 發送數據:
            String resp = LocalDateTime.now().toString();
            packet.setData(resp.getBytes(StandardCharsets.UTF_8));
            ds.send(packet);
        }
    }
}

Email編程

電子郵件的一些基本概念:

  • MUA(Mail User Agent)發送和接收郵件所使用的郵件客戶端,一般使用IMAP或POP3協議與服務器通訊
  • MTA(Mail Transfer Agent) 經過SMTP協議發送、轉發郵件
  • MDA(Mail Deliver Agent)將MTA接收到的郵件保存到磁盤或指定地方
  • SMTP協議(Simple Mail Transfer Protocol)是發送郵件使用的標準協議,創建在TCP協議之上,標準端口25,加密端口爲465/587
  • POP3協議(Post Office Protocol version3) 是接收郵件使用的標準協議之一,創建在TCP協議之上,標準端口爲110,加密端口爲995
  • IMAP協議 (Internet Mail Access Protocol )是接收郵件使用的標準協議之一,和POP3協議的區別是IMAP協議容許用戶建立文件夾,用戶在本地的任何操做都自動同步到郵件服務器,準備端口爲143,加密端口爲993

Java提供了一個javax.mail包,能夠很方便的實現發送和接收郵件,而不用去關係SMTP協議和POP3協議的原理,方法以下:

  • 發送郵件
    1. 首先建立一個Session對象,傳入郵件服務器信息和用戶名密碼認證信息

      Session session = Session.getInstance(props, new Authticator());
    2. 而後建立MimeMessage對象,封裝郵件發件人地址、收件人地址,郵件主題,郵件正文等信息

      MimeMessage message = new MimeMessage(session);
      message.setFrom(new InternetAddress("from@email.com"));
      message.setRecipient(Message.RecipientType.TO, new InternetAddress("to@email.com"));
      message.setSubject("subject", "UTF-8");
      message.setText("body", "UTF-8");
    3. 最後使用Transport工具類的send()方法發送郵件

      Transport.send(message);
  • 接收郵件
    1. 首先建立Session對象,一樣須要傳入服務器信息和登陸名密碼認證

      Session session = Session.getInstance(props, null);
    2. 而後建立Store對象,Store對象表明用戶在服務器上的整個存儲

      Store store = new POP3SSLStore(session, url)
    3. 經過Store對象獲取Folder對象,獲取用戶相應的文件夾,好比收件箱「INBOX」

      Folder folder = store.getFolder("INBOX");
    4. 從Folder對象中獲取全部的郵件

      Message[] messages = folder.getMessage();
      for (Message message : messages) { ... };

因爲本文只是Java編程簡明教程,對Email編程就不做過多的講述,更多的功能能夠參考JAVA MAIL相關API文檔。

HTTP編程

HTTP協議(HyperText Transfer Protocol)又叫作超文本傳輸協議,它是基於TCP協議上的請求和響應協議,是目前使用最普遍的高級協議。最先的HTTP協議版本是HTTP 1.0,每一次請求,都會建立一個TCP鏈接,因爲瀏覽器打開網頁一般會請求不一樣的資源(例如圖片,CSS等其餘資源),建立TCP鏈接會有必定耗時,因此傳輸效率比較低;HTTP 1.1 則作出改進,多個HTTP請求能夠經過一個TCP鏈接完成,效率獲得提升;HTTP 2.0 一樣也是多個請求經過一個TCP鏈接完成,可是瀏覽器發送一個請求後不須要等待服務器的響應就能夠馬上發送後續的請求,服務器只要有了響應數據馬上返回,不關心請求的順序,也就是說HTTP 2.0 不須要嚴格按照收到請求再響應的方式進行。

HTTP服務器用於處理HTTP請求,發送HTTP響應。在Java中,HTTP服務器經過JAVA EE的Servlet API定義,一般Servlet容器根據收到的HTTP請求信息建立一個Request對象,再建立一個Response對象用來向Web客戶端發送響應,調用Servlet對象的service()方法處理Request和Response,具體參考Servlet教程。

HTTP客戶端用於發送HTTP請求,接收HTTP響應。Java提供的java.net.HttpURLConnection類能夠實現HTTP客戶端功能:

  • 發送GET請求
URL url = new URL("http://www.example.com/");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
int code = conn.getResponseCode(); //得到響應代碼
try (InputStream input = conn.getInputStream()) {
    // 讀取響應
}
conn.disconnect(); // 斷開鏈接
  • 發送POST請求
URL url = new URL("http://www.example.com/");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST"); 
conn.setDoOutput(true); // 須要發送請求數據
byte[] postData = contentData.getBytes(StandardCharsets.UTF_8); // 將發送請求數據轉換爲數組
conn.setRequestProperty("Content-Type", contentType); // 設置請求Content-Type
conn.setRequestProperty("Content-Length", String.valueOf(postData.length)); 設置請求Content-Length try (OutputStream output = conn.getOutputStream()) {
                output.write(postData); // 寫入請求數據
}
int code = conn.getResponseCode(); // 獲取響應代碼
try (InputStream input = conn.getInputStream()) {
// 讀取響應數據
}
conn.disconnect(); // 斷開鏈接

總結

Java提供了Socket和ServerSocket類,讓咱們能夠實現TCP/UDP協議的鏈接;Java還提供了MAIL API,咱們能夠實現基於SMTP/POP3協議的收發郵件功能;最後Java還提供了HttpURLConnection類,用於實現HTTP客戶端功能,以及提供了Servlet API用於實現HTTP服務端的編程。

相關文章
相關標籤/搜索