Java網絡編程簡明教程
計算機網絡相關概念
計算機網絡是兩台或更多的計算機組成的網絡,同一網絡內的任意兩台計算機可以直接通信,所有計算機必須遵循同一種網絡協議。
-
互聯網
- 互聯網是連接計算機網絡的網絡
- 互聯網采取TCP/IP協議
- 其中最重要的兩個協議是TCP協議和IP協議
-
IP地址和網關
-
IP地址用於唯一標識一個網絡接口
-
IPv4采用32位地址
IPv4地址實際是一個二進制32位的整數,為了便於識別,用十六進制表示后可以分為4組數字,每組數字轉換成十進制后用“.”隔開就是我們見到的IP地址:

-
IPv6采用128位地址
-
公網IP地址可以直接被訪問
-
內網IP地址只能在內網訪問
-
本機地址使用127.0.0.1
-
通常路由器或交換機有兩個網卡(兩個IP地址),分別連接兩個不同的網絡:

-
同一網絡下的計算機可以直接通信,他們的網絡號相同,網絡號由IP地址和子掩碼按組對齊做與運算得到:

-
不同網絡下的計算機需要通過路由器或交換機網絡設備間接通信,這樣的網絡設備叫做網關:

-
網關的作用是連接多個網絡,負責把一個網絡的數據包發送到另一個網絡,過程叫做路由:

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

-
-
域名
由於IP地址不便於記憶,通常使用域名來訪問特定的服務,域名解析服務器DNS負責將域名翻譯成對應的IP地址,客戶端再根據IP地址訪問服務器:

-
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進行通信。

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信息,終端打印出了當前的時間信息:

如果我們先運行客戶端的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協議的原理,方法如下:
- 發送郵件
-
首先創建一個Session對象,傳入郵件服務器信息和用戶名密碼認證信息
Session session = Session.getInstance(props, new Authticator()); -
然后創建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"); -
最后使用Transport工具類的send()方法發送郵件
Transport.send(message);
-
- 接收郵件
-
首先創建Session對象,同樣需要傳入服務器信息和登錄名密碼認證
Session session = Session.getInstance(props, null); -
然后創建Store對象,Store對象代表用戶在服務器上的整個存儲
Store store = new POP3SSLStore(session, url) -
通過Store對象獲取Folder對象,獲取用戶相應的文件夾,比如收件箱“INBOX”
Folder folder = store.getFolder("INBOX"); -
從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服務端的編程。






