zoukankan      html  css  js  c++  java
  • Java之HTTP网络编程(一):TCP/SSL网页下载

    目录

    一、简介:HTTP程序设计

        1、HTTP系统设计

        2、HTTP客户端工作过程

        3、HTTP服务端工作过程

    二、基于TCP Socket的HTTP网页下载

    三、基于SSL Socket的HTTPS网页下载

    四、HTTP客户端完整代码

    五、界面完整代码

    六、最后+演示


    一、简介: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
    "+
                "HOST: "+domainName+"
    "+
                "Accept: */*
    "+
                "Accept-Language: zh-CN
    "+
                "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
    "+
                "Connection: Keep-Alive
    "
    );

     换行符使用 是为了避免由于编码问题出错。

    发送请求之后如果网页信息显示区返回的第一条信息是“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("服务器连接成功。
    ");
    
                        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+"
    ");
                                    });
                                }
                            }
                        });
                        readThread.start();
                    }
    
                    else if (port.equals("80")){
                        httpClient = new HTTPClient(ip, port);
                        taDisplay.appendText("服务器连接成功。
    ");
                        readThread = new Thread(()-> {
                            String receiveMsg = null;
                            while ((receiveMsg = httpClient.receive()) != null) {
                                String msgTemp = receiveMsg;
                                Platform.runLater(() -> {
                                    taDisplay.appendText(msgTemp + "
    ");
                                });
                            }
                        });
                        readThread.start();
                    }
    
    
                }catch (Exception e){
                    taDisplay.appendText("服务器连接失败!"+e.getMessage()+"
    ");
                }
            });
    
            //网页请求按钮事件
            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
    "+
                                    "HOST: "+domainName+"
    "+
                                    "Accept: */*
    "+
                                    "Accept-Language: zh-CN
    "+
                                    "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
    "+
                                    "Connection: Keep-Alive
    "
                            );
                    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);
        }
    
    }
    View Code

    六、最后+演示

    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


    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
    本文链接:https://blog.csdn.net/Charzous/article/details/111470556
    ---------------------------------------------------------书上有路,学海无涯。 生活总是很忙碌,也许这才是生活真正的奥秘。--------------------------------------------------------- 作者:Charzueus 来源:博客园 本博文版权归作者所有! 禁止商业转载等用途或联系作者授权,非商业转载请注明出处! 版权声明:本文为博主原创文章,转载请附上原文出处链接和本声明。
  • 相关阅读:
    【leetcode】496. Next Greater Element I
    工具网站
    [err]Traceback (most recent call last): File "/usr/local/bin/pip", line 7, in <module> from pip._internal import main ImportError: No module named 'pip._internal'
    []TLD code run
    【动手学深度学习】
    【论文阅读】Wing Loss for Robust Facial Landmark Localisation with Convolutional Neural Networks
    【linux基础】ubuntu系统NVIDIA驱动安装
    【linux基础】linux不能进入系统
    【leetcode】492. Construct the Rectangle
    【leetcode】485. Max Consecutive Ones
  • 原文地址:https://www.cnblogs.com/chenzhenhong/p/14435762.html
Copyright © 2011-2022 走看看