Loading

【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 包响应,客户端感知到错误。


posted @ 2020-12-14 10:03  Parzulpan  阅读(89)  评论(0编辑  收藏  举报