chapter07(HTTP程序设计)
使用Java的Socket
类时,你只需要知道服务器的域名(或IP地址)和端口号就可以建立连接。Java的网络库会处理域名解析的过程,即将域名转换为IP地址。
import java.io.*;
import java.net.*;
public class SocketExample {
public static void main(String[] args) {
String hostname = "www.example.com"; // 服务器域名
int port = 80; // 端口号
try {
// 创建Socket连接
Socket socket = new Socket(hostname, port);
System.out.println("连接到服务器: " + hostname + " 在端口: " + port);
// 发送请求
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println("GET / HTTP/1.1");
out.println("Host: " + hostname);
out.println("Connection: Close");
out.println();
// 接收响应
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String responseLine;
while ((responseLine = in.readLine()) != null) {
System.out.println(responseLine);
}
// 关闭连接
in.close();
out.close();
socket.close();
} catch (IOException e) {
System.err.println("连接失败: " + e.getMessage());
}
}
}
关于HTTP协议的基本信息
你可以自行查阅相关资料,也可以查看我的博客(十分基础)python网络编程 | 0zxm
一、概述
HTTP 系统包括客户端软件(浏览器)和服务器软件(HTTP服务器)。早期的客户端软件,其主要工作可理解为文件下载和文件显示。
实际上现代的HTTP客户端比文件下载要复杂得多,它包括网页文件的下载、跨平台的本地显示,参数的传递,动态网页的实现,以及交互等功能。
HTTP系统程序设计包括:
- 客户端软件(web浏览器软件如Edge浏览器、360浏览器);
- 服务器软件(web服务器软件如IIS、Nginx、Tomcat等)。
HTTP系统客户端的工作过程是:
- 客户端软件和服务器建立连接(TCP的三次握手);
- 发送HTTP头格式协议;
- 接收网页文件;
- 显示网页。
HTTP系统服务端的工作过程:
- 服务器软件开启80端口;
- 响应客户的要求、完成TCP连接;
- 检查客户端的HTTP头格式发送客户请求的网页文件(含动态网页)。
https://i-blog.csdnimg.cn/direct/a5f36f03ce334c31b7624e59dcc938bd.png#pic_center" alt="在这里插入图片描述" />
二、第一步:基于TCP套接字的网页下载程序设计
利用TCP客户套接字Socket和HTTP服务器进行信息交互,将网页的原始内容下载显示在图形界面中,具体工作如下:
-
新建一个包chapter08,将第3讲的
TCPClient.java
复制到此包下, 重命名为HTTPClient.java
; -
创建 HTTPClientFX.java 程序,界面如图8.2所示,网页地址输入 www.baidu.com 进行测试,端口为
80
,在“连接”按钮类似以往的编码方式, 放置连接服务器的代码和启动输入流 “读线程”;在“网页请求”按钮中发送 HTTP 请求头标准格式(关于
HTTP请求头的更多信息可查阅互联网);在“退出”按钮中,相对之前章节的代码,不是发送 “bye” 表示结束,而是
send("Connection:close" +"\r\n")
表示结束连接。
https://i-blog.csdnimg.cn/direct/5159fda9958f47da8cbe839518d17607.png#pic_center" alt="在这里插入图片描述" />
具体来说,“网页请求”按钮中,我们的程序可以按顺序发送以下HTTP请求头:
GET / HTTP/1.1 //访问默认网页,注意GET后的 / 前后要留有空格
HOST: address //address 指服务器的 IP 或域名
Accept: */*
Accept-Language: zh-cn
User-Agent: User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Connection: Keep-Alive
将上述的HTTP头格式构成一整个字符串,一致发送到HTTP服务器。
(注意:Windows系统下各个请求头字符串用\r\n
分隔区分,不同操作系统的 User-Agent 也要相应修改;通常用StringBuffer类的append方法来拼接这些字符串,其toString()方法可将内容转换为字符串)。
HTTP状态码
如果网页信息显示区返回的第一条信息是“HTTP/1.1 200 OK”,则说明访问正常。现在将网页地址改为www.gdufs.edu.cn,其它不变,再次连接并点击网页请求,结果如图8.3所示:
https://i-blog.csdnimg.cn/direct/087fb45d74be4b09a2ef4cee7fa6f5b3.png#pic_center" alt="在这里插入图片描述" />
HTTPClient
import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
public class HTTPClient {
private final Socket socket; // 定义套接字
private final PrintWriter pw; // 定义字符输出流
private final BufferedReader br; // 定义字符输入流
public HTTPClient(String ip, String port) throws IOException {
// 主动向服务器发起连接,实现TCP的三次握手过程
// 如果不成功,则抛出错误信息,其错误信息交由调用者处理
socket = new Socket(ip, Integer.parseInt(port));
// 得到网络输出字节流地址,并封装成网络输出字符流
// 设置最后一个参数为true,表示自动flush数据
OutputStream socketOut = socket.getOutputStream();
pw = new PrintWriter(new OutputStreamWriter(socketOut, StandardCharsets.UTF_8), true);
// 得到网络输入字节流地址,并封装成网络输入字符流
InputStream socketIn = socket.getInputStream();
br = new BufferedReader(new InputStreamReader(socketIn, StandardCharsets.UTF_8));
}
public void send(String msg) {
// 输出字符流,由Socket调用系统底层函数,经网卡发送字节流
pw.println(msg);
}
public String receive() {
String msg = null;
try {
// 从网络输入字符流中读信息,每次只能接收一行信息
// 如果不够一行(无行结束符),则该语句阻塞等待
msg = br.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return msg;
}
public boolean isConnected(){
return socket.isConnected();
}
// 实现close方法以关闭socket连接及相关的输入输出流
public void close() {
try {
if (pw != null) {
pw.close(); // 关闭PrintWriter会先flush再关闭底层流
}
if (br != null) {
br.close(); // 关闭BufferedReader
}
if (socket != null) {
socket.close(); // 关闭Socket连接
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
HTTPClientFx
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.io.IOException;
public class HTTPClientFx extends Application {
private final Button connectBtn = new Button("连接");
private final Button requestBtn = new Button("网页请求");
private final Button clearBtn = new Button("清空");
private final Button quitBtn = new Button("退出");
private final TextField urlField = new TextField();
private final TextField portField = new TextField();
private final TextArea responseArea = new TextArea();
private HTTPClient httpc;
private Thread receiveThread;
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
primaryStage.setTitle("HTTP Client");
BorderPane mainPane = new BorderPane();
HBox topBox = new HBox();
topBox.setSpacing(10);
topBox.setPadding(new Insets(10));
topBox.getChildren().addAll(new Label("网页地址: "), urlField, new Label("端口: "), portField, connectBtn);
topBox.setAlignment(Pos.CENTER);
VBox centerBox = new VBox(10);
centerBox.setPadding(new Insets(10, 20, 10, 20));
centerBox.getChildren().addAll(new Label("网页信息显示区: "), responseArea);
centerBox.setAlignment(Pos.CENTER);
HBox bottomBox = new HBox(10);
bottomBox.setPadding(new Insets(10, 10, 10, 10));
bottomBox.getChildren().addAll(requestBtn, clearBtn, quitBtn);
bottomBox.setAlignment(Pos.CENTER_RIGHT);
VBox mainBox = new VBox();
mainBox.getChildren().addAll(topBox, centerBox, bottomBox);
mainBox.setAlignment(Pos.CENTER);
VBox.setVgrow(responseArea, Priority.ALWAYS);
VBox.setVgrow(centerBox, Priority.ALWAYS);
mainPane.setCenter(mainBox);
Scene scene = new Scene(mainPane, 720, 450);
// 按钮事件
quitBtn.setOnAction(event -> {
this.httpc.close();
receiveThread.interrupt();
httpc.send("Connection:close" + "\r\n");
System.exit(0);
});
// 连接按钮事件
connectBtn.setOnAction(event -> {
try {
String url = urlField.getText().trim();
httpc = new HTTPClient(url, portField.getText().trim());
if (httpc.isConnected()) {
this.responseArea.appendText("连接成功!\n");
}
receiveThread = new Thread(() -> {
while (true) {
String response = httpc.receive();
if (response == null) {
break;
}
Platform.runLater(() -> {
this.responseArea.appendText(response);
});
}
}, "ReceiveThread");
receiveThread.start();
} catch (IOException e) {
throw new RuntimeException(e);
}
});
clearBtn.setOnAction(event -> {
this.responseArea.clear();
});
requestBtn.setOnAction(event -> {
httpc.send("GET / HTTP/1.1");
httpc.send("Host: " + this.urlField.getText().trim());
httpc.send("Connection: Close");
httpc.send("\r\n");
});
urlField.setText("www.baidu.com");
portField.setText("80");
this.responseArea.setEditable(false);
this.responseArea.setWrapText(true);
// 添加滚轮事件
responseArea.setOnScroll(event -> {
if (event.isControlDown()) {
if (event.getDeltaY() > 0) {
responseArea.setStyle("-fx-font-size: " + (responseArea.getFont().getSize() + 1) + "px;");
} else {
responseArea.setStyle("-fx-font-size: " + (responseArea.getFont().getSize() - 1) + "px;");
}
}
});
// 界面显示
primaryStage.setScene(scene);
primaryStage.show();
}
}
第二步: 基于SSLSocket的网页下载程序设计
HTTP协议是一种基于普通套接字的不安全的协议
,要访问HTTPS站点,就不能用普通的客户端套接字,而是使用SSL套接字
。Java 安全套接字扩展(Java Secure Socket Extension,JSSE)为基于 SSL 和 TLS 协议的Java网络应用程序提供了Java API以及参考实现,相关的类都定义在 javax.net.ssl
包下面,我们这里只使用其客户端的SSLSocket套接字。
SSL/TLS协议是在TCP连接的基础上实现的,通常用于提高数据传输的安全性。这意味着SSL套接字实际上是建立在TCP套接字之上的。其工作流程如下:
- TCP连接建立:首先,客户端和服务器通过TCP建立一个可靠的连接。这个过程包括TCP的三次握手。
- SSL握手:一旦TCP连接建立,接下来进行SSL/TLS握手。握手过程包括以下几个步骤:
- 客户端发送一个“客户端问候”消息,包含支持的TLS版本、
加密算法
等信息。 - 服务器响应“服务器问候”消息,选择TLS版本和加密算法,并发送其
数字证书
。 - 客户端验证服务器的证书,确保服务器的身份。
- 双方生成共享密钥,用于加密后续的通信。
- 客户端发送一个“客户端问候”消息,包含支持的TLS版本、
- 安全数据传输:握手完成后,客户端和服务器就可以使用生成的密钥进行加密数据的传输,确保数据在传输过程中是安全的。
- 连接关闭:通信完成后,双方可以按照TLS协议关闭连接,确保安全地结束会话。
SSLSocket相对之前学习的客户端套接字,只是创建方法不同,SSLSocket对象 由SSLSocketFactory创建
HTTPSClient
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.*;
import java.nio.charset.StandardCharsets;
public class HTTPSClient {
// 定义SSL套接字
private final SSLSocket sslSocket;
// 定义SSL套接字工厂
private final SSLSocketFactory sslSocketFactory;
private final PrintWriter pw; // 定义字符输出流
private final BufferedReader br; // 定义字符输入流
public HTTPSClient(String ip, String port) throws IOException {
// 创建工厂对象()使用静态方法getDefault()获取默认的SSL套接字工厂
sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
// 创造SSL套接字对象
sslSocket = (SSLSocket) sslSocketFactory.createSocket(ip, Integer.parseInt(port));
pw = new PrintWriter(new OutputStreamWriter(sslSocket.getOutputStream(), StandardCharsets.UTF_8),true);
br = new BufferedReader(new InputStreamReader(sslSocket.getInputStream(), StandardCharsets.UTF_8));
// 开始SSL握手
sslSocket.startHandshake();
}
public void send(String message) {
pw.println(message);
}
public String receive() {
String msg = null;
try {
// 从网络输入字符流中读信息,每次只能接收一行信息
// 如果不够一行(无行结束符),则该语句阻塞等待
msg = br.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return msg;
}
public boolean isConnected() {
return sslSocket.isConnected();
}
// 实现close方法以关闭socket连接及相关的输入输出流
public void close() {
try {
if (pw != null) {
pw.close(); // 关闭PrintWriter会先flush再关闭底层流
}
if (br != null) {
br.close(); // 关闭BufferedReader
}
if (sslSocket != null) {
sslSocket.close(); // 关闭Socket连接
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
HTTPSClientFx
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.io.IOException;
public class HTTPSClientFx extends Application {
private final Button connectBtn = new Button("连接");
private final Button requestBtn = new Button("网页请求");
private final Button clearBtn = new Button("清空");
private final Button quitBtn = new Button("退出");
private final TextField urlField = new TextField();
private final TextField portField = new TextField();
private final TextArea responseArea = new TextArea();
private HTTPSClient httpsc;
private Thread receiveThread;
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
primaryStage.setTitle("HTTPS Client");
BorderPane mainPane = new BorderPane();
HBox topBox = new HBox();
topBox.setSpacing(10);
topBox.setPadding(new Insets(10));
topBox.getChildren().addAll(new Label("网页地址: "), urlField, new Label("端口: "), portField, connectBtn);
topBox.setAlignment(Pos.CENTER);
VBox centerBox = new VBox(10);
centerBox.setPadding(new Insets(10, 20, 10, 20));
centerBox.getChildren().addAll(new Label("网页信息显示区: "), responseArea);
centerBox.setAlignment(Pos.CENTER);
HBox bottomBox = new HBox(10);
bottomBox.setPadding(new Insets(10, 10, 10, 10));
bottomBox.getChildren().addAll(requestBtn, clearBtn, quitBtn);
bottomBox.setAlignment(Pos.CENTER_RIGHT);
VBox mainBox = new VBox();
mainBox.getChildren().addAll(topBox, centerBox, bottomBox);
mainBox.setAlignment(Pos.CENTER);
VBox.setVgrow(responseArea, Priority.ALWAYS);
VBox.setVgrow(centerBox, Priority.ALWAYS);
mainPane.setCenter(mainBox);
Scene scene = new Scene(mainPane, 720, 450);
// 按钮事件
quitBtn.setOnAction(event -> {
httpsc.send("Connection:close" + "\r\n");
this.httpsc.close();
receiveThread.interrupt();
System.exit(0);
});
// 连接按钮事件
connectBtn.setOnAction(event -> {
try {
String url = urlField.getText().trim();
httpsc = new HTTPSClient(url, portField.getText().trim());
if (httpsc.isConnected()) {
this.responseArea.appendText("连接成功!\n");
}
receiveThread = new Thread(() -> {
while (true) {
String response = httpsc.receive();
if (response == null) {
break;
}
Platform.runLater(() -> {
this.responseArea.appendText(response);
});
}
}, "ReceiveThread");
receiveThread.start();
} catch (IOException e) {
throw new RuntimeException(e);
}
});
clearBtn.setOnAction(event -> {
this.responseArea.clear();
});
requestBtn.setOnAction(event -> {
httpsc.send("GET / HTTP/1.1");
httpsc.send("Host: " + this.urlField.getText().trim());
httpsc.send("Accept: */*");
httpsc.send("Accept-Language: zh-cn");
httpsc.send("User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
httpsc.send("Connection: Keep-Alive");
httpsc.send("\r\n");
});
urlField.setText("www.gdufs.edu.cn");
portField.setText("443");
this.responseArea.setEditable(false);
this.responseArea.setWrapText(true);
// 添加滚轮事件
responseArea.setOnScroll(event -> {
if (event.isControlDown()) {
if (event.getDeltaY() > 0) {
responseArea.setStyle("-fx-font-size: " + (responseArea.getFont().getSize() + 1) + "px;");
} else {
responseArea.setStyle("-fx-font-size: " + (responseArea.getFont().getSize() - 1) + "px;");
}
}
});
// 界面显示
primaryStage.setScene(scene);
primaryStage.show();
}
}
第三步:基于URL类的网页下载程序设计
前面直接发送http请求的程序,对于复杂的地址,例如指向具体页面的地址,无法完成网页下载任务,我们可以使用基于URL类的解决方案。
URL(Uniform Resource Locator)
中文名为统一资源定位符,用于表示资源地址,资源如网页或者FTP地址等。
URL 的格式为 protocol://资源地址
,protocol可以是HTTP、HTTPS、FTP 和 File,资源地址中可以带有端口号及查询参数,具体可以自行搜索URL的知识。
在java.net
包中定义了URL类,该类用来处理有关URL的内容。并且其封装有一个InputStream返回类型的openStream()方法,我们的程序就可以读取 这个字节输入流来获得对应内容。
URLClientFx
- 创建程序URLClientFX.java,界面布局可参见如图8.5所示:
https://i-blog.csdnimg.cn/direct/8e578c83b0dc4aa0a2554d9c48a3c6dd.png#pic_center" alt="在这里插入图片描述" />
URLClient
import java.io.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class URLClient {
private URL url;
private BufferedReader br;
public URLClient(String urlString) throws IOException {
url = new URL(urlString);
//获得url的字节流输入
InputStream in = url.openStream();
br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
}
public String readLine() throws IOException {
return br.readLine();
}
public void close() throws Exception {
br.close();
}
}
URLClientFx
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
public class URLClientFx extends Application {
private final Button sendBtn = new Button("发送");
private final Button quitBtn = new Button("退出");
private final TextField urlField = new TextField();
private final TextArea responseArea = new TextArea();
private URLClient urlClient;
private Thread receiveThread;
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
primaryStage.setTitle("URL Downloader");
BorderPane mainPane = new BorderPane();
VBox centerBox = new VBox(10);
centerBox.setPadding(new Insets(10, 20, 10, 20));
centerBox.getChildren().addAll(new Label("网页信息显示区: "), responseArea);
centerBox.setAlignment(Pos.CENTER);
VBox urlBox = new VBox(10);
urlBox.setPadding(new Insets(10, 20, 10, 20));
urlBox.getChildren().addAll(new Label("输入URL地址: "), urlField);
urlBox.setAlignment(Pos.CENTER_LEFT);
HBox bottomBox = new HBox(10);
bottomBox.setPadding(new Insets(10, 20, 10, 20));
bottomBox.getChildren().addAll(sendBtn, quitBtn);
bottomBox.setAlignment(Pos.CENTER_RIGHT);
VBox mainBox = new VBox();
mainBox.getChildren().addAll(centerBox, urlBox, bottomBox);
mainBox.setAlignment(Pos.CENTER);
VBox.setVgrow(responseArea, Priority.ALWAYS);
VBox.setVgrow(centerBox, Priority.ALWAYS);
mainPane.setCenter(mainBox);
Scene scene = new Scene(mainPane, 720, 450);
// 按钮事件处理
quitBtn.setOnAction(event -> System.exit(0));
sendBtn.setOnAction(
event -> {
try {
String url = this.urlField.getText().trim();
try {
this.urlClient = new URLClient(url);
} catch (MalformedURLException e) {
responseArea.appendText("URL格式错误!\n");
return;
}
Thread receiveThread = new Thread(() -> {
while (true) {
try {
String line = this.urlClient.readLine();
if (line == null) {
break;
}
Platform.runLater(
() -> {
this.responseArea.appendText(line);
}
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}, "receiveThread");
receiveThread.start();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
);
urlField.setText("http://www.baidu.com");
responseArea.setEditable(false);
responseArea.setWrapText(true);
// 界面显示
primaryStage.setScene(scene);
primaryStage.show();
}
}