• 【Java基础】网络编程


    网络编程

    网络编程概述

    网络编程的目的:直接或简洁地通过网络协议与其他计算机实现数据交换,进行通讯。

    网络编程的两个主要问题:

    • 如果准确地定位网络上一台或多台主机,并定位主机上的特定应用;
    • 找到主机后如何可靠高效地进行数据传输。

    网络通信要素概述

    通信双方地址:

    • IP
    • Port

    网络协议:

    • OSI 参考模型:模型过于理想化,未能在因特网上进行广泛推广
    • TCP / IP 参考模型:事实上的国际标准

    网络通信协议

    通信要素1:IP&Port

    IP 地址(InetAddress)

    • 唯一的标识 Internet 上的计算机(通信实体);
    • 本地回环地址(hostAddress):127.0.0.1、主机名(hostName):localhost
    • IP 地址分类方式1:IPV4 和 IPV6
      • IPV4:4个字节组成,以点分十进制表示,如 192.168.0.1
      • IPV6:16个字节组成,写成8个无符号整数,每个整数用四个十六进制位表示,如 3ffe:3201:1401:1280:c8ff:fe4d:db39:1984
    • IP地址分类方式2:公网地址(万维网使用)和私有地址(局域网使用)

    Port

    • 标识正在计算机上运行的进程(程序),不同的进程有不同的端口号;
    • 被规定为一个 16 位的整数 0~65535;
    • 端口分类:
      • 公认端口:0~1023。被预先定义的服务通信占用(如:HTTP 占用端口
        80,FTP 占用端口 21,Telnet 占用端口 23);
      • 注册端口:1024~49151。分配给用户进程或应用程序。(如:Tomcat占用端口 8080,MySQL 占用端口 3306,Oracle 占用端口 1521)。
      • 私有端口:49152~65535。

    InetAddress 类没有提供公共的构造器,而是提供了如下几个静态方法来获取
    InetAddress 实例:

    • public static InetAddress getLocalHost()
    • public static InetAddress getByName(String host)

    InetAddress 提供了如下几个常用的方法:

    • public String getHostAddress():返回 IP 地址字符串
    • public String getHostName():获取此 IP 地址的主机名
    • public boolean isReachable(int timeout):测试是否可以达到该地址
    package parzulpan.com.java;
    
    import java.io.IOException;
    import java.net.InetAddress;
    import java.net.UnknownHostException;
    
    /**
     * @Author : parzulpan
     * @Time : 2020-11-27
     * @Desc :
     */
    
    public class InetAdressTest {
        public static void main(String[] args) {
            InetAddress inet1 = null;
            InetAddress inet2 = null;
            InetAddress inet3 = null;
            InetAddress inet4 = null;
            try {
                inet1 = InetAddress.getByName("www.parzulpan.cn");
                System.out.println(inet1);
    
                inet2 = InetAddress.getByName("61.135.185.32");
                System.out.println(inet2);
    
                inet3 = InetAddress.getByName("localhost");
                System.out.println(inet3);
    
                inet4 = InetAddress.getLocalHost();
                System.out.println(inet4);
    
                System.out.println(inet1.getHostAddress());
                System.out.println(inet1.getHostName());
                boolean reachable = false;
                try {
                    reachable = inet1.isReachable(1000);
                    System.out.println(reachable);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            } catch (UnknownHostException e) {
                e.printStackTrace();
            }
        }
    }
    

    通信要素2:网络协议

    计算机网络中实现通信必须有一些约定,即通信协议(网络协议),对速率、传输代码、代码结构、传输控制步骤、出错控制等制定标准。

    在制定协议时,把复杂成份分解成一些简单的成份,再将它们复合起来。最常
    用的复合方式是层次方式,即同层间可以通信、上一层可以调用下一层,而与
    再下一层不发生关系。各层互不影响,利于系统的开发和扩展。

    传输层协议中有两个非常重要的协议:

    • 传输控制协议 TCP(Transmission Control Protocol)
    • 用户数据报协议 UDP(User Datagram Protocol)

    TCP/IP 以其两个主要协议:传输控制协议(TCP)和 网络互联协议(IP,Internet Protocol,是网络层的主要协议,支持网间互连的数据通信)而得名,实际上是一组协议,包括多个具有不同功能且互为关联的协议。

    TCP/IP 协议模型从更实用的角度出发,形成了高效的四层体系结构,即
    物理链路层、网络层、传输层和应用层。

    Socket

    客户端和服务器端工作的核心逻辑:

    客户端和服务器工作的核心逻辑

    Socket:

    • 网络上具有唯一标识的 IP地址 和 端口号 组合在一起才能构成唯一能识别的标识符套接字;
    • 通信的两端都要有 Socket,是两台机器间通信的端点,网络通信其实就是 Socket 间的通信;
    • Socket 允许程序把网络连接当成一个,数据在两个 Socket 间通过 IO 传输
    • 一般主动发起通信的应用程序属客户端,等待通信请求的为服务端;
    • 分类:
      • 流套接字(stream socket):使用 TCP 提供可依赖的字节流服务;
      • 数据报套接字(datagram socket):使用 UDP 提供“尽力而为”的数据报服务;

    Java 中,

    Socket 类的常用构造器

    • public Socket(InetAddress address,int port) 创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
    • public Socket(String host,int port) 创建一个流套接字并将其连接到指定主机上的指定端口号。

    Socket 类的常用方法

    • public InputStream getInputStream() 返回此套接字的输入流。可以用于接收网络消息
    • public OutputStream getOutputStream() 返回此套接字的输出流。可以用于发送网络消息
    • public InetAddress getInetAddress() 此套接字连接到的远程 IP 地址;如果套接字是未连接的,则返回 null
    • public InetAddress getLocalAddress() 获取套接字绑定的本地地址。 即本端的IP地址
    • public int getPort() 此套接字连接到的远程端口号;如果尚未连接套接字,则返回 0
    • public int getLocalPort() 返回此套接字绑定到的本地端口。 如果尚未绑定套接字,则返回 -1。即本端的端口号
    • public void close() 关闭此套接字。套接字被关闭后,便不可在以后的网络连接中使用(即无法重新连接或重新绑定)。需要创建新的套接字对象。 关闭此套接字也将会关闭该套接字的 InputStream 和OutputStream
    • public void shutdownInput() 如果在套接字上调用 shutdownInput() 后从套接字输入流读取内容,则流将返回 EOF(文件结束符)。 即不能在从此套接字的输入流中接收任何数据
    • public void shutdownOutput() 禁用此套接字的输出流。对于 TCP 套接字,任何以前写入的数据都将被发送,并且后跟 TCP 的正常连接终止序列。 如果在套接字上调用 shutdownOutput() 后写入套接字输出流,
      则该流将抛出 IOException。 即不能通过此套接字的输出流发送任何数据

    直观的理解 Socket

    • 把客户端和服务器工作想象成打电话,socket 就好比是我们的手机, connect 就好比是拿着手机拨号,服务器端的 bind 就好比是去电信公司开户,将号码和绑定,这样别人就可以通过号码联系你,listen 就好比是让手机处于可接听的状态,accept 就好比是被叫的一方拿起手机进行应答;
    • 然后对方拨通手机号建立通话(connect),拨打电话的人说(write):你好,接听电话的人听到(write):并回答(write)你好。这样,就等同进入了 read/write 的数据传输过程;
    • 最后,拨打电话的人完成了此次交流,挂上电话,对应的操作可以理解为 close,接听电话的人知道对方已挂机,也挂上电话,也是一次 close;
    • 在整个通话过程中,手机是我们可以和外面通信的设备,对应到网络编程的世界里,socket 也是我们可以和外界进行网络通信的途径。

    TCP 三次握手

    TCP三次握手过程:

    TCP三次握手过程

    其中

    • 确认ACK,仅当ACK=1时,确认号字段才有效。TCP规定,在连接建立后所有报文的传输都必须把ACK置1;
    • 同步SYN,在连接建立时用来同步序号。当SYN=1,ACK=0,表明是连接请求报文,若同意连接,则响应报文中应该使SYN=1,ACK=1;
    • 终止FIN,用来释放连接。当FIN=1,表明此报文的发送方的数据已经发送完毕,并且要求释放;
    • 复位RST,当RST=1,表明TCP连接中出现严重差错,必须释放连接,然后再重新建立连接;

    解读

    • 这里使用的网络编程模型都是阻塞式的。所谓阻塞式,就是调用发起后不会直接返回,由操作系统内核处理之后才会返回。相对的,还有一种叫做非阻塞式的。

    • 最初的过程:服务器端通过 socket,bind 和 listen 完成了被动套接字的准备工作,被动的意思就是等着别人来连接,然后调用 accept,就会阻塞在这里,等待客户端的连接来临;客户端通过调用 socket 和 connect 函数之后,也会阻塞。接下来的事情是由操作系统内核完成的,更具体一点的说,是操作系统内核的网络协议栈在工作。

    具体的过程

    • 客户端的协议栈向服务器端发送一个值为 j 的 SYN 包,客户端进入 SYN_SENT 状态;
    • 服务器端的协议栈收到 SYN 包之后,服务器端发送一个值为 j + 1 的 ACK 应答包和 一个值为 k 的 SYN 包,服务器端进入 SYN_RCVD 状态;
    • 客户端的协议栈收到 ACK+SYN 包之后,使得应用程序从 connect 调用返回,表示客户端到服务器端的单向连接建立成功,客户端进入 ESTABLISHED 状态,同时客户端协议栈向服务器端发送一个值为 k + 1 的 ACK 应答包;
    • 服务器端的协议栈收到 ACK 应答包之后,使得 accept 阻塞调用返回,表示服务器端到客户端的单向连接也建立成功,服务器端进入 ESTABLISHED 状态。

    形象的比喻

    • A 先对 B 说:“喂,你在么?我在的,我的口令是 j。”
    • B 收到之后大声回答:“我收到你的口令 j 并准备好了,你准备好了吗?我的口令是 k。”
    • A 收到之后也大声回答:“我收到你的口令 k 并准备好了,我们开始吧。”

    TCP 四次挥手

    TCP四次挥手过程:

    TCP四次挥手过程

    具体的过程

    • TCP 连接终止时,主机1 发送值为 m 的 FIN 包,主机1 进入 FIN_WAIT_1(终止等待1) 状态。
    • 主机2 收到 FIN 包后,发送值为 m + 1 的 ACK 应答包,主机2 进入 CLOSE_WAIT(关闭等待)状态。注意,此时处于半关闭的状态,主机1 到主机2 的方向释放了,但是主机2 到主机1 的方向还正常,即主机2 依然能向主机1 发送数据且主机1 能接收。主机1 接收到 ACK 应答包后,主机1 进入 FIN_WAIT_2(终止等待2) 状态。
    • 主机2 准备好关闭连接时,发送值为 n 的 FIN 包,主机2 进入 LAST_ACK(最后确认)状态,等待主机1 的确认。
    • 主机1 收到 FIN 包后,发送值为 n + 1 的 ACK 应答包,主机1 进入 TIME_WAIT(时间等待) 状态。注意,此时 TCP 连接还没有释放,必须经过 2MSL(Maximum Segment Lifetime,最长报文段寿命)的时间后,才进入 CLOSED(关闭)状态。
    • 主机2 接收到 ACK 应答包后,进入 CLOSED(关闭)状态。

    TCP 网络编程

    TCP协议:

    • 使用 TCP协议前,须先建立TCP连接,形成传输数据通道
    • 传输前,采用“三次握手”方式,点对点通信,是可靠的
    • TCP 协议进行通信的两个应用进程:客户端、服务端。
    • 在连接中可进行大数据量的传输
    • 传输完毕,需释放已建立的连接,效率低

    客户端的工作过程包含以下四个基本的步骤:

    • 创建 Socket:根据指定服务端的 IP 地址或端口号构造 Socket 类对象。若服务器端响应,则建立客户端到服务器的通信线路。若连接失败,会出现异常。
      • 客户端程序可以使用 Socket 类创建对象,创建的同时会自动向服务器方发起连接。Socket的构造器是:
        • Socket(String host,int port)throws UnknownHostException,IOException:向服务器 (域名是 host。端口号为 port) 发起 TCP 连接,若成功,则创建Socket对象,否则抛出异常。
        • Socket(InetAddress address,int port)throws IOException:根据 InetAddress 对象所表示的 IP地址 以及 端口号port 发起连接。
    • 打开连接到 Socket 的输入/输出流: 使用 getInputStream()方法获得输入流,使用 getOutputStream() 方法获得输出流,进行数据传输。
    • 按照一定的协议对 Socket 进行读/写操作:通过输入流读取服务器放入线路的信息(但不能读取自己放入线路的信息),通过输出流将信息写入线程。
    • 关闭 Socket:断开客户端到服务器的连接,释放线路。

    服务器的工作过程包含以下四个基本的步骤:

    • 调用 ServerSocket(int port):创建一个服务器端套接字,并绑定到指定端口上。用于监听客户端的请求。
      • ServerSocket 对象负责等待客户端请求建立套接字连接,类似邮局某个窗口中的业务员。也就是说,服务器必须事先建立一个等待客户请求建立套接字连接的 ServerSocket 对象。
      • 所谓“接收”客户的套接字请求,就是accept()方法会返回一个 Socket 对象。
    • 调用 accept():监听连接请求,如果客户端请求连接,则接受连接,返回通信套接字对象。
    • 调用 该Socket类对象的 getOutputStream() 和 getInputStream ():获取输出流和输入流,开始网络数据的发送和接收。
    • 关闭 ServerSocket 和 Socket 对象:客户端访问结束,关闭通信套接字。
    package parzulpan.com.java;
    
    import org.junit.Test;
    
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.InetAddress;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * @Author : parzulpan
     * @Time : 2020-11-28
     * @Desc : TCP 网络编程
     * 例子1:客户端发送消息给服务端,服务端将数据显示在控制台上
     */
    
    public class TCPTest {
    
        // 客户端
        @Test
        public void client() {
            Socket socket = null;
            OutputStream os = null;
            try {
                // 1. 创建 Socket 对象,指明服务器端的 IP 和 Port
                InetAddress inet = InetAddress.getByName("127.0.0.1");
                socket = new Socket(inet, 28888);
    
                // 2. 获取一个输出流,用于输出数据
                os = socket.getOutputStream();
    
                // 3. 写出数据
                os.write("我是客户端".getBytes());
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 4. 关闭资源
                try {
                    if (os != null) {
                        os.close();
                    }
                    if (socket != null) {
                        socket.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
    
        }
    
        // 服务端
        @Test
        public void server() {
            ServerSocket ss = null;
            Socket socket = null;
            InputStream is = null;
            ByteArrayOutputStream baos = null;
            try {
                // 1. 创建服务器端的 ServerSocket,指明自己的端口号
                ss = new ServerSocket(28888);
    
                // 2. 调用 accept() 表示接收到来自于客户端的 Socket
                socket = ss.accept();
    
                // 3. 获取一个输入流,用于输入数据
                is = socket.getInputStream();
    
                // 4. 读取数据
                baos = new ByteArrayOutputStream();
                byte[] buffer = new byte[5];
                int data;
                while ((data = is.read(buffer)) != -1) {
                    baos.write(buffer, 0, data);
                }
                System.out.println(baos.toString());
                System.out.println("数据来自于:" + socket.getInetAddress().getHostAddress());
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 5. 关闭资源
                try {
                    if (baos != null) {
                        baos.close();
                    }
                    if (is != null) {
                        is.close();
                    }
                    if (socket != null) {
                        socket.close();
                    }
                    if (ss != null) {
                        ss.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    package parzulpan.com.java;
    
    import org.junit.Test;
    
    import java.io.*;
    import java.net.InetAddress;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * @Author : parzulpan
     * @Time : 2020-11-28
     * @Desc : TCP 网络编程
     * 例子2:客户端发送文件给服务端,服务端将文件保存在本地。
     */
    
    public class TCPTest1 {
    
        @Test
        public void client() {
            Socket socket = null;
            OutputStream os = null;
            BufferedInputStream bis = null;
            try {
                // 1.
                socket = new Socket(InetAddress.getByName("127.0.0.1"), 29999);
    
                // 2.
                os = socket.getOutputStream();
    
                // 3.
                bis = new BufferedInputStream(new FileInputStream(new File("tcp.png")));
    
                // 4.
                byte[] buffer = new byte[1024];
                int data;h 
                while ((data = bis.read(buffer)) != -1) {
                    os.write(buffer, 0, data);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 5.
                try {
                    if (bis != null) {
                        bis.close();
                    }
                    if (os != null) {
                        os.close();
                    }
                    if (socket != null) {
                        socket.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        @Test
        public void server() {
            ServerSocket ss = null;
            Socket socket = null;
            InputStream is = null;
            BufferedOutputStream bos = null;
            try {
                // 1.
                ss = new ServerSocket(29999);
    
                //2.
                socket = ss.accept();
    
                // 3.
                is = socket.getInputStream();
    
                // 4.
                bos = new BufferedOutputStream(new FileOutputStream(new File("tcpServer.png")));
                byte[] buffer = new byte[1024];
                int data;
                while ((data = is.read(buffer)) != -1) {
                    bos.write(buffer, 0, data);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 5.
                try {
                    if (bos != null) {
                        bos.close();
                    }
                    if (is != null) {
                        is.close();
                    }
                    if (socket != null) {
                        socket.close();
                    }
                    if (ss != null) {
                        ss.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
    
        }
    }
    
    package parzulpan.com.java;
    
    import org.junit.Test;
    
    import java.io.*;
    import java.net.InetAddress;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * @Author : parzulpan
     * @Time : 2020-11-28
     * @Desc : TCP 网络编程
     * 例子3:从客户端发送文件给服务端,服务端保存到本地。并返回“发送成功”给客户端。并关闭相应的连接。
     */
    
    public class TCPTest2 {
    
        @Test
        public void client() {
            Socket socket = null;
            OutputStream os = null;
            InputStream is = null;
            BufferedInputStream bis = null;
            ByteArrayOutputStream baos = null;
            try {
                // 创建 Socket 对象,指明服务器端的 IP 和 Port
                socket = new Socket(InetAddress.getByName("127.0.0.1"), 29998);
    
                // 获取一个输出流,用于输出数据
                os = socket.getOutputStream();
                // 获取一个输入流,用于输入数据
                is = socket.getInputStream();
    
                // 写出和读入数据
                bis = new BufferedInputStream(new FileInputStream(new File("tcp.png")));
                baos = new ByteArrayOutputStream();
    
                //
                byte[] buffer1 = new byte[1024];
                int data1;
                while ((data1 = bis.read(buffer1)) != -1) {
                    os.write(buffer1, 0, data1);
                }
                // 关闭数据的输出,结束阻塞式的等待
                socket.shutdownOutput();
    
                byte[] buffer2 = new byte[1024];
                int data2;
                while ((data2 = is.read(buffer2)) != -1) {
                    baos.write(buffer2, 0, data2);
                }
                System.out.println(baos.toString());
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (baos != null) {
                        baos.close();
                    }
                    if (bis != null) {
                        bis.close();
                    }
                    if (is != null) {
                        is.close();
                    }
                    if (os != null) {
                        os.close();
                    }
                    if (socket != null) {
                        socket.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        @Test
        public void server() {
            ServerSocket ss = null;
            Socket socket = null;
            InputStream is = null;
            OutputStream os = null;
            BufferedOutputStream bos = null;
            try {
                ss = new ServerSocket(29998);
                socket = ss.accept();
    
                is = socket.getInputStream();
                os = socket.getOutputStream();
    
                bos = new BufferedOutputStream(new FileOutputStream(new File("tcpServer1.png")));
    
                byte[] buffer1 = new byte[1024];
                int data1;
                while ((data1 = is.read(buffer1)) != -1) {
                    bos.write(buffer1, 0, data1);
                }
    
                System.out.println("图片传输完成!");
                os.write("服务器端已经接收到客户端发送的图片!".getBytes());
    
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (bos != null) {
                        bos.close();
                    }
                    if (os != null) {
                        os.close();
                    }
                    if (is != null) {
                        is.close();
                    }
                    if (socket != null) {
                        socket.close();
                    }
                    if (ss != null) {
                        ss.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    UDP 网络编程

    UDP协议:

    • 将数据、源、目的封装成数据包,不需要建立连接
    • 每个数据报的大小限制在 64K
    • 发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
    • 可以广播发送
    • 发送数据结束时无需释放资源,开销小,速度快

    工作流程

    • 使用 DatagramSocket 与 DatagramPacket
    • 建立发送端,接收端
    • 建立数据包
    • 调用 Socket 的发送、接收方法
    • 关闭 Socket
    • 发送端与接收端是两个独立的运行程序
    package parzulpan.com.java;
    
    import org.junit.Test;
    
    import java.io.IOException;
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    import java.net.InetAddress;
    
    /**
     * @Author : parzulpan
     * @Time : 2020-11-28
     * @Desc : UDP 网络编程
     */
    
    public class UDPTest {
    
        // 发送端
        @Test
        public void sender() {
            DatagramSocket socket = null;
            try {
                socket = new DatagramSocket();
                byte[] str = "我是 UDP 方式发送的数据!".getBytes();
    
                DatagramPacket packet = new DatagramPacket(str, 0, str.length, InetAddress.getByName("127.0.0.1"), 28877);
                socket.send(packet);
    
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (socket != null) {
                    socket.close();
                }
            }
        }
    
        @Test
        public void receiver() {
            DatagramSocket socket = null;
            try {
                socket = new DatagramSocket(28877);
                byte[] buffer = new byte[1024];
    
                DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
                socket.receive(packet);
    
                String string = new String(packet.getData(), 0, packet.getLength());
                System.out.println(string + " -- " + packet.getAddress());
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (socket != null) {
                    socket.close();
                }
            }
    
    
        }
    }
    

    URL 网络编程

    URL(Uniform Resource Locator)即统一资源定位符,它表示 Internet 上某一资源的地址。不但可以用来标识一个资源,而且还指明了如何 locate 这个资源。

    URL 的基本结构由五部分组成:

    • <传输协议>://<主机名>:<端口号>/<文件名>#片段名?参数列表
    • 例如:http://192.168.1.100:8080/helloworld/index.jsp#a?username=shkstart&password=123
    package parzulpan.com.java;
    
    import java.net.MalformedURLException;
    import java.net.URL;
    
    /**
     * @Author : parzulpan
     * @Time : 2020-11-28
     * @Desc : URL 网络编程
     */
    
    public class URLTest {
        public static void main(String[] args) {
            URL url = null;
            try {
                url = new URL("http://localhost:8080/examples/tcp.png?username=tomcat");
            } catch (MalformedURLException e) {
                e.printStackTrace();
            }
            if (url != null) {
                System.out.println("getProtocol() : "+url.getProtocol());
                System.out.println("getHost() : "+url.getHost());
                System.out.println("getPort() : "+url.getPort());
                System.out.println("getPath() : "+url.getPath());
                System.out.println("getFile() : "+url.getFile());
                System.out.println("getQuery() : "+url.getQuery());
            }
        }
    }
    
    package parzulpan.com.java;
    
    import java.io.*;
    import java.net.HttpURLConnection;
    import java.net.URL;
    
    /**
     * @Author : parzulpan
     * @Time : 2020-11-28
     * @Desc : URL 网络编程,从 tomcat 下载数据保存到本地
     */
    
    public class URLTest1 {
        public static void main(String[] args) {
    
            HttpURLConnection urlConnection = null;
            InputStream is = null;
            BufferedOutputStream bos = null;
            try {
                URL url = new URL("http://localhost:8080/examples/cat.png?username=tomcat");
    
                urlConnection = (HttpURLConnection) url.openConnection();   // 针对 HTTP 协议的 URLConnection 类
    
                urlConnection.connect();
    
                is = urlConnection.getInputStream();
                bos = new BufferedOutputStream(new FileOutputStream(new File("ch12/tomcat.png")));
    
                byte[] buffer = new byte[1024];
                int data;
                while ((data = is.read(buffer)) != -1) {
                    bos.write(buffer, 0, data);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (bos != null) {
                        bos.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    if (is != null) {
                        is.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                if (urlConnection != null) {
                    urlConnection.disconnect();
                }
            }
        }
    }
    

    几个名词的区别:

    • URI,是 uniform resource identifier,统一资源标识符,用来唯一的标识一个资源。
    • URL,是 uniform resource locator,统一资源定位符,它是一种具体
      的 URI,即 URL 可以用来标识一个资源,而且还指明了如何 locate 这个资源。
    • URN,是 uniform resource name,统一资源命名,是通过名字来标识资源。

    练习和总结


    为什么要需要进行三次握手,不是两次或者四次呢?

    是为了确定双方都具备接收发送能力,为后续可靠性传输做准备。

    第一次握手,服务器端只能确定客户端的发送能力和服务器端的接收能力。

    第二次握手,客户端可以确定客户端和服务器端具备接收发送能力。

    第三次握手,服务器端就能确定客户端的接收能力和服务器端的发送能力。


    为什么要需要进行四次挥手,不是两次或者三次呢?

    因为 TCP 连接是双向传输的对等模式,关闭连接时,当收到对方的 FIN 报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方是否现在关闭发送数据通道,需要上层应用来决定,因此,己方 ACK 和 FIN 一般都会分开发送,这里就相对于三次握手多了一次。


    三次握手连接阶段,最后一次ACK包丢失会进入什么样的一个状态?

    对于服务器端,此时的状态为 SYN_RECV,它会根据 TCP 的超时重传机制,会等待3秒、6秒、12秒后重新发送 SYN+ACK 包,以便让客户端重新发送ACK包。如果重发指定次数之后,仍然未收到客户端的应答,那么一段时间后,服务器端自动关闭这个连接。

    对于客户端,此时的状态为 ESTABLISHED,如果客户端向服务器端发生数据,服务器端将以 RST 包响应,客户端感知到错误。


  • 相关阅读:
    Spring Data JPA 的作用.
    JavaEE 规范和 SSH 三大框架的关系
    一些疑惑
    Linux学习总结(21)——CentOS7环境下FTP服务器的安装和配置
    Docker学习总结(18)——阿里超大规模Docker化之路
    Maven学习总结(32)——Maven项目部署到Tomcat8中
    Maven学习总结(31)——Maven坐标详解
    猎豹CEO傅盛:与周鸿祎、雷军、马化腾、马云的的相爱相杀
    Java Web学习总结(32)——Java程序员最亲睐的Web框架
    VMWare学习总结(1)——Centos7安装完毕后无法联网的解决方法
  • 原文地址:https://www.cnblogs.com/parzulpan/p/14131719.html
Copyright © 2020-2023  润新知