Java之HTTP网络编程(一):TCP/SSL网页下载
目录
一、简介:HTTP程序设计
期末复习之HTTP网络编程,主要学习记录HTTP(s)协议的网络编程,包括使用TCP Socket进行三次握手的HTTP网页下载,和使用SSL Socket的安全传输的HTTPs网页下载,通过案例实践自行完成编程,认识http(s)的实际工作机制!
现在的HTTP客户端比早期的复杂得多,不仅包括了网页文件下载和显示,还有许多新的功能:跨平台的显示、参数的传递、动态网页的实现和用户交互等。
1、HTTP系统设计
- 客户端软件(web浏览器:Chrome、360浏览器等)
- 服务端软件(web服务器:微软的IIS、Apache Tomcat)
2、HTTP客户端工作过程
- 客户端软件和服务器建立连接(TCP的三次握手);
- 发送HTTP头格式协议;
- 接收网页文件;
- 显示网页。
3、HTTP服务端工作过程
- 服务器软件开启80端口;
- 响应客户的要求、完成TCP连接;
- 检查客户端的HTTP头格式发送客户请求的网页文件(含动态网页)。
图1 HTTP请求-响应完整过程
网页下载技术是搜索引擎、网络爬虫、网页采集器或网络推送服务等相关应用领域内的基础技术,下面会介绍日常使用到的两种协议(http和https)的网页访问下载。
二、基于TCP Socket的HTTP网页下载
对于TCP套接字的连接过程已经有很深刻的认识了,在本地测试通信也使用过TCP的Socket建立连接,同理,与HTTP服务器建立连接,也是利用TCP进行信息交互的。
建立连接之后,需要发送HTTP请求头,服务器确认请求者,开启两端的通信,客户端可以接收网页文件信息,进而经过渲染后显示网页页面。这里我们先实现接收网页文件信息,在下一篇实现浏览器对网页渲染之后的功能。
以www.baidu.com为例,与HTTP服务器建立连接之后,需要我们发送网页请求,也就是HTTP请求头。构造请求头如下:
GET / HTTP/1.1
HOST: www.baidu.com
Accept: */*
Accept-Language: zh-cn
User-Agent: User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Connection: Keep-Alive
需要严格按照格式发送,并且通常用StringBuffer类的toString()方法可将完整的HTTP请求头转换为字符串,一致发送到HTTP服务器。
- StringBuffer msg = new StringBuffer();
- msg.append("GET / HTTP/1.1\r\n"+
- "HOST: "+domainName+"\r\n"+
- "Accept: */*\r\n"+
- "Accept-Language: zh-CN\r\n"+
- "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)\r\n"+
- "Connection: Keep-Alive\r\n"
- );
换行符使用\r\n是为了避免由于编码问题出错。
发送请求之后如果网页信息显示区返回的第一条信息是“HTTP/1.1 200 OK”,则说明访问正常。
可以看到HTTP服务器返回许多信息,这也是响应头,包含了许多关键信息内容。
三、基于SSL Socket的HTTPS网页下载
以上面设计的基于TCP通信传输的HTTP,我们尝试访问www.sina.com.cn,结果发现响应头信息第一行是HTTP/1.1 302 Moved Temporarily(站点被移除),出于安全考虑,现在绝大部分的web站点都将放弃HTTP而启用HTTPS,都使用了安全加密传输的HTTPS协议,而关闭了HTTP,只允许启用了SSL/TLS的HTTPS安全连接,这种连接默认是使用443端口。所以TCP Socket建立连接的方式无正常访问网页。
那只是端口改为443能正常吗,答案如下。
原因在前面也能看出,需要使用SSL/TLS的HTTPS安全连接,来建立与HTTPS服务器的通信,因此需要修改Socket类型。
这里使用到了Java安全套接字扩展(Java Secure Socket Extension,JSSE),基于SSL和TLS协议的Java网络应用程序提供了Java API以及参考实现,这里使用其客户端的SSLSocket套接字。SSLSocket相对之前学习的客户端套接字,只是创建方法不同,SSLSocket对象由SSLSocketFactory创建。
在类中声明成员变量以及创建Socket连接:
- private SSLSocket socket;
- private SSLSocketFactory factory;
- factory=(SSLSocketFactory)SSLSocketFactory.getDefault();
- socket=(SSLSocket)factory.createSocket(ip,Integer.parseInt(port));
对SSL Socket的使用与TCP相同,只是创建方法不同,经过稍微修改之后,可以成功请求HTTPS网站的网页信息。
四、HTTP客户端完整代码
这里给出HTTP客户端的完整代码,HTTPS只需改改上述讲到的SSL Socket。
- /*
- * HTTPClient.java
- * Copyright (c) 2020-12-21
- * author : Charzous
- * All right reserved.
- */
- package chapter08;
- import java.io.*;
- import java.net.Socket;
- public class HTTPClient {
- private Socket socket;
- private PrintWriter pw;
- private BufferedReader br;
- /**
- * @param ip
- * @param port
- * @return
- * @author Charzous
- * @date 2020/12/21 14:52
- *
- */
- public HTTPClient(String ip, String port) throws IOException{
- //主动向服务器发起连接,实现TCP三次握手
- //不成功则抛出错误,由调用者处理错误
- socket =new Socket(ip,Integer.parseInt(port));
- //得到网络流输出字节流地址,并封装成网络输出字符流
- OutputStream socketOut=socket.getOutputStream();
- //参数true表示自动flush数据
- pw=new PrintWriter(new OutputStreamWriter(socketOut,"utf-8"),true);
- //得到网络输入字节流地址,并封装成网络输入字符流
- InputStream socketIn=socket.getInputStream();
- br=new BufferedReader(new InputStreamReader(socketIn,"utf-8"));
- }
- public void send(String msg) throws InterruptedException {
- //输出字符流,由socket调用系统底层函数,经网卡发送字节流
- try {
- Thread.sleep(500);
- }catch (InterruptedException e){
- e.printStackTrace();
- }
- pw.println(msg);
- }
- public String receive(){
- String msg=null;
- try {
- //从网络输入字符流中读取信息,每次只能接受一行信息
- //不够一行时(无行结束符),该语句阻塞
- //直到条件满足,程序往下运行
- msg=br.readLine();
- }catch (IOException e){
- e.printStackTrace();
- }
- return msg;
- }
- public void close(){
- try {
- if (socket!=null)
- socket.close();
- }catch (IOException e){
- e.printStackTrace();
- }
- }
- }
五、界面完整代码
我直接用一个图形界面来访问http和https,融合以上两个图形客户端的功能,使得该图形客户端既能访问443的https内容,也可以访问非443端口(一般是80)的http内容。


- /*
- * HTTPAllClientFX.java
- * Copyright (c) 2020-12-21
- * author : Charzous
- * All right reserved.
- */
- package chapter08;
- 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;
- public class HTTPAllClientFX extends Application {
- private Button btnExit=new Button("退出");
- private Button btnSend = new Button("网页请求");
- // private TextField tfSend=new TextField();//输入信息区域
- private TextArea taDisplay=new TextArea();//显示区域
- private TextField ipAddress=new TextField();//填写ip地址
- private TextField tfport=new TextField();//填写端口
- private Button btConn=new Button("连接");
- private HTTPSClient httpsClient;
- private HTTPClient httpClient;
- private Thread readThread;
- public static void main(String[] args) {
- launch(args);
- }
- @Override
- public void start(Stage primaryStage) {
- BorderPane mainPane=new BorderPane();
- //连接服务器区域
- HBox hBox1=new HBox();
- hBox1.setSpacing(10);
- hBox1.setPadding(new Insets(10,20,10,20));
- hBox1.setAlignment(Pos.CENTER);
- hBox1.getChildren().addAll(new Label("网页地址:"),ipAddress,new Label("端口:"),tfport,btConn);
- mainPane.setTop(hBox1);
- VBox vBox=new VBox();
- vBox.setSpacing(10);
- vBox.setPadding(new Insets(10,20,10,20));
- vBox.getChildren().addAll(new Label("网页信息显示区"),taDisplay);
- VBox.setVgrow(taDisplay, Priority.ALWAYS);
- mainPane.setCenter(vBox);
- HBox hBox=new HBox();
- hBox.setSpacing(10);
- hBox.setPadding(new Insets(10,20,10,20));
- hBox.setAlignment(Pos.CENTER_RIGHT);
- hBox.getChildren().addAll(btnSend,btnExit);
- mainPane.setBottom(hBox);
- Scene scene =new Scene(mainPane,700,500);
- primaryStage.setScene(scene);
- primaryStage.show();
- //连接按钮
- btConn.setOnAction(event -> {
- String ip=ipAddress.getText().trim();
- String port=tfport.getText().trim();
- taDisplay.clear();
- try {
- if (port.equals("443")){
- httpsClient = new HTTPSClient(ip, port);
- //成功连接服务器,接受服务器发来的第一条欢迎信息
- taDisplay.appendText("服务器连接成功。\n");
- readThread = new Thread(()->{
- String receiveMsg=null;//从服务器接收一串字符
- if (port.equals("443")){
- while ((receiveMsg=httpsClient.receive())!=null){
- //lambda表达式不能直接访问外部非final类型局部变量,需要定义一个临时变量
- //若将receiveMsg定义为类成员变量,则无需临时变量
- String msgTemp = receiveMsg;
- Platform.runLater(()->{
- taDisplay.appendText(msgTemp+"\n");
- });
- }
- }
- });
- readThread.start();
- }
- else if (port.equals("80")){
- httpClient = new HTTPClient(ip, port);
- taDisplay.appendText("服务器连接成功。\n");
- readThread = new Thread(()-> {
- String receiveMsg = null;
- while ((receiveMsg = httpClient.receive()) != null) {
- String msgTemp = receiveMsg;
- Platform.runLater(() -> {
- taDisplay.appendText(msgTemp + "\n");
- });
- }
- });
- readThread.start();
- }
- }catch (Exception e){
- taDisplay.appendText("服务器连接失败!"+e.getMessage()+"\n");
- }
- });
- //网页请求按钮事件
- btnSend.setOnAction(event -> {
- String ip=ipAddress.getText().trim();
- String port=tfport.getText().trim();
- String domainName=ipAddress.getText().trim();
- try {
- StringBuffer msg = new StringBuffer();
- msg.append("GET / HTTP/1.1\r\n"+
- "HOST: "+domainName+"\r\n"+
- "Accept: */*\r\n"+
- "Accept-Language: zh-CN\r\n"+
- "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)\r\n"+
- "Connection: Keep-Alive\r\n"
- );
- if (port.equals("443"))
- httpsClient.send(msg.toString());
- else if (port.equals("80"))
- httpClient.send(msg.toString());
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- });
- btnExit.setOnAction(event -> {
- try {
- exit();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- });
- //窗体关闭响应的事件,点击右上角的×关闭,客户端也关闭
- primaryStage.setOnCloseRequest(event -> {
- try {
- exit();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- });
- }
- private void exit() throws InterruptedException {
- if (httpsClient!=null||httpClient!=null){
- readThread.sleep(1000);//多线程等待,关闭窗口时还有线程等待IO,设置1s间隔保证所有线程已关闭
- httpsClient.close();
- httpClient.close();
- }
- System.exit(0);
- }
- }
六、最后+演示
HTTP连接www.baidu.com,成功
HTTP连接www.sina.com.cn,失败
HTTPS连接www.sina.com.cn,成功
期末复习,顺便写博客记录下来,这篇为上篇,介绍HTTP网页请求下载,主要是HTTP(s)协议的网络编程,包括使用TCP Socket进行三次握手的HTTP网页下载,和使用SSL Socket的安全传输的HTTPs网页下载,通过案例实践自行完成编程,认识http(s)的实际工作机制!
期待:Java之HTTP网络编程(下篇:网页浏览器程序设计),将看到网页的HTML源代码,以及经过浏览器功能渲染之后的网页!
如果觉得不错欢迎“一键三连”哦,点赞收藏关注,有问题直接评论,交流学习!
我的博客园:https://www.cnblogs.com/chenzhenhong/p/14435762.html
我的CSDN博客:https://blog.csdn.net/Charzous/article/details/111470556
Java之HTTP网络编程(一):TCP/SSL网页下载的更多相关文章
- Java 网络编程 -- 基于TCP 模拟多用户登录
Java TCP的基本操作参考前一篇:Java 网络编程 – 基于TCP实现文件上传 实现多用户操作之前先实现以下单用户操作,假设目前有一个用户: 账号:zs 密码:123 服务端: public c ...
- Java学习:网络编程总结
Java网络编程总结 一.概述 计算机网络是通过传输介质.通信设施和网络通信协议,把分散在不同地点的计算机设备互连起来,实现资源共享和数据传输的系统.网络编程就就是编写程序使联网的两个(或多个)设备( ...
- Java学习之网络编程实例
转自:http://www.cnblogs.com/springcsc/archive/2009/12/03/1616413.html 多谢分享 网络编程 网络编程对于很多的初学者来说,都是很向往的一 ...
- 黑马程序员:Java基础总结----网络编程
黑马程序员:Java基础总结 网络编程 ASP.Net+Android+IO开发 . .Net培训 .期待与您交流! 网络编程 网络通讯要素 . IP地址 . 网络中设备的标识 . 不易记忆,可用 ...
- Java进阶之网络编程
网络编程 网络编程对于很多的初学者来说,都是很向往的一种编程技能,但是很多的初学者却因为很长一段时间无法进入网络编程的大门而放弃了对于该部分技术的学习. 在 学习网络编程以前,很多初学者可能觉得网络编 ...
- 第84节:Java中的网络编程(中)
第84节:Java中的网络编程(中) 实现客户端和服务端的通信: 客户端需要的操作,创建socket,明确地址和端口,进行键盘录入,获取需要的数据,然后将录入的数据发送给服务端,为socket输出流, ...
- 第78节:Java中的网络编程(上)
第78节:Java中的网络编程(上) 前言 网络编程涉及ip,端口,协议,tcp和udp的了解,和对socket通信的网络细节. 网络编程 OSI开放系统互连 网络编程指IO加网络 TCP/IP模型: ...
- 第62节:探索Java中的网络编程技术
前言 感谢! 承蒙关照~ 探索Java中的网络编程技术 网络编程就是io技术和网络技术的结合,网络模型的定义,只要共用网络模型就可以两者连接.网络模型参考. 一座塔有七层,我们需要闯关. 第一层物理层 ...
- java第九节 网络编程的基础知识
/** * * 网络编程的基础知识 * 网络协议与TCP/IP * IP地址和Port(端口号) * 本地回路的IP地址:127.0.0.1 * 端口号的范围为0-65535之间,0-1023之间的端 ...
随机推荐
- java--Aop--记录日志
package com.pt.modules.log; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; imp ...
- 3.DHCP原理
1.DHCP服务器给首次接入网络的客户端分配网络参数的工作原理 只有跟DHCP客户端在同一个网段的DHCP服务器才能收到DHCP客户端广播的DHCP DISCOVER报文.当DHCP客户端与DHCP服 ...
- STL_优先队列
一.简介 优先队列容器与队列一样,只能从队尾插入元素,从队首删除元素.但是它有一个特性,就是队列中最大的元素总是位于队首,所以出队时,并非按照先进先出的原则进行,而是将当前队列中最大的元素出队. 元素 ...
- UDP实现多人聊天
发送端 package com.zy.exercise; import java.net.DatagramPacket; import java.net.DatagramSocket; import ...
- 2019牛客暑期多校训练营(第二场) H-Second Large Rectangle(单调栈)
题意:给出由01组成的矩阵,求求全是1的次大子矩阵. 思路: 单调栈 全是1的最大子矩阵的变形,不能直接把所有的面积存起来然后排序取第二大的,因为次大子矩阵可能在最大子矩阵里面,比如: 1 0 0 1 ...
- HDU6504 Problem E. Split The Tree【dsu on tree】
Problem E. Split The Tree Problem Description You are given a tree with n vertices, numbered from 1 ...
- 01背包记录路径 (例题 L3-001 凑零钱 (30分))
题意: 就是找出来一个字典序最小的硬币集合,且这个硬币集合里面所有硬币的值的和等于题目中的M 题解: 01背包加一下记录路径,如果1硬币不止一个,那我们也不采用多重背包的方式,把每一个1硬币当成一个独 ...
- Python内置模块(你还在pip install time?)&& apt-get install -f
一.内置模块 之前不知道time是python自带的,还用pip安装.......还报错..... Python中有以下模块不用单独安装 1.random模块 2.sys模块 3.time模块 4.o ...
- VUE 3.0 初体验之路
在2020年9月中旬,vue.js发布了3.0正式版,在不久的将来,VUE3.0 也终将成为大前端的必然趋势, 环境搭建 node 版本要求: Node.js8.9 或更高版本 ,输入 node -v ...
- 弹性伸缩 AS(Auto Scaling)
根据业务需求和策略设置伸缩规则,在业务需求增长时自动为您增加 ECS 实例以保证计算能力,在业务需求下降时自动减少 ECS 实例以节约成本,弹性伸缩不仅适合业务量不断波动的应用程序,同时也适合业务量稳 ...