Java笔记16 网络编程

16.1 网络相关概念

16.1.1 网络通信

  1. 网络通信是通过网络将各个孤立的设备进行连接,通过信息交换实现人与人,人与计算机,计算机与计算机之间的通信
  2. 即将数据通过网络从一台设备传输到另一台设备的过程
  3. java.net包下提供了一系列的类或者接口,供程序员完成网络通信

16.1.2 网络

  1. 计算机网络(简称为网络)由若干**结点(node)和连接这些结点的链路(link)**组成

  2. 网络中的结点可以是计算机、集线器、交换机或路由器等

  3. 网络之间还可以通过路由器互连起来,这就构成了一个覆盖范围更大的计算机网络,这样的网络称为互联网(internetwork或internet)。即互联网是“网络的网络”

16.1.3 互联网的组成

  1. 互联网从其工作方式看,可以分为以下两大块:

    1. 边缘部分:由所有连接在互联网上的主机组成。这部分是用户直接使用的,用来进行通信(传输数据、音频或视频)和资源共享
    2. 核心部分:由大量网络和连接这些网络的路由器组成。这部分是为边缘部分提供服务的(提供连通性和交换)
  2. 互联网的边缘部分:处在互联网边缘的部分就是连接在互联网上的所有主机,这些主机又被称为端系统。我们常说的“主机 A 和主机 B 进行通信”实际指“主机 A 的某个进程和主机 B 的另一个进程进行通信”。在网络边缘的端系统之间的通信方式通常可以分为两类:

    1. 客户-服务器方式(C/S 方式):客户是服务请求方,服务器是服务提供方,这里的客户和服务器都指的是计算机进程(软件)
    2. 对等方式(P2P 方式):指两台主机在通信时不区分哪一个是服务请求方哪一个是服务提供方。只要两台主机都运行了对等连接软件(P2P 软件),他们就可以进行平等的、对等连接通信
  3. 网络的核心部分:在网络核心部分起特殊作用的是路由器(router),它是一种专用计算机(但不叫主机)。路由器是实现**分组交换(packet switching)**的关键构件,其任务是转发收到的分组。这是网络核心部分最重要的功能

16.1.4 互联网的分类

  1. 按照网络的作用范围进行分类(由大到小):

    1. 广域网 WAN(Wide Area NetWork):广域网的作用范围通常为几十到几千公里,因而有时也称为远程网。广域网是互联网的核心部分,其任务是通过长距离运送主机所发送的数据。
    2. 城域网 MAN(Metropolitan Area Network):城域网的作用范围一般是一个城市,可跨越几个街区甚至整个城市,其作用距离约为 5 ~ 50 km。城域网可以为一个或几个单位所拥有,但也可以是一种公用设施,用来将多个局域网进行互联。目前很多城域网采用的是以太网技术
    3. 局域网 LAN(Local Area Network):局域网一般用微型计算机或工作站通过高速通信线路相连(速率通常在 10 Mbit/s 以上),但地理上则局限在较小的范围(如 1 km 左右)
    4. 个人区域网 PAN(Personal Area Network):个人区域网就是在个人工作的地方把属于个人使用的电子设备用无线技术连接起来的网络,因此也常称为无线个人区域网WPAN(Wireless PAN),其作用范围很小,大约在 10 m 左右
  2. 按照网络的使用者进行分类

    1. 公用网(public network):这里是指电信公司(国有或私有)出资建造的大型网络。“公用”的意思是所有愿意按照电信公司的规定交纳费用的人都可以使用这种网络
    2. 专用网(private network):这是某个部门为满足本单位的特殊业务工作的需要而建造的网络。这种网络不向本单位以外的个人提供服务。例如,军队、铁路、银行、电力等系统均有本系统的专用网

16.1.5 计算机网络的性能指标

  1. 速率:网络技术中的速率指的是数据的传输速率,它也称为数据率(data rate)或比特率(bit rate)。速率是计算机网络中最重要的一个性能指标,它的单位是 bit/s(比特每秒)(或 b/s ,有时也写作 bps),当数据率较高时,就常常在 bit/s 前加上一个字母。如,k = 10³ = 千,M = 10⁶ = 兆,G = 10⁹ = 吉,T = 10¹² = 太,P = 10¹⁵ = 拍,E = 10¹⁸ = 艾,Z = 10²¹ = 泽,Y = 10²⁴ = 尧。这样 4 x 10¹⁰ bit/s 的数据率就记为 40 Gbit/s

  2. 带宽:在计算机网络中,带宽用来表示网络中某通道传送数据的能力,因此网络带宽表示在单位时间内网络中的某信道所能通过的“最高数据率”,所以带宽的单位就是数据率的单位 bit/s,即“比特每秒”

  3. 吞吐量:表示在单位时间内通过某个网络(或信道、接口)的实际的数据量。可以理解为带宽为某带宽为 1 Gbit/s 的通道的额定速率为 1 Gbit/s,即 每秒最多通过 1 Gbit 的数据,而吞吐量为某一秒内实际通过的数据量,也就是说,1 Gbit/s 的通道吞吐量可能只有 100 Mbit/s

  4. 时延:指数据(一个报文或分组,甚至比特)从网络(或链路)的一端传送到另一端所需的时间,有时也称为延迟或迟延。网络中的时延由以下几部分组成:

    1. 发送时延:主机或路由器发送数据帧所需要的时间,也就是从发送数据帧的第一个比特算起,到帧的最后一个比特发送完毕所需的时间,因此也叫做传输时延。计算公式为:
      发 送 时 延 = 数 据 帧 长 度 ( b i t ) 发 送 速 率 ( b i t / s ) 发送时延=\frac{数据帧长度(bit)}{发送速率(bit/s)} =(bit/s)(bit)

    2. 传播时延:电磁波在信道中传播一定的距离需要花费的时间。计算公式为:
      传 播 时 延 = 信 道 长 度 ( m ) 电 磁 波 在 信 道 上 的 传 播 速 率 ( m / s ) 传播时延=\frac{信道长度(m)}{电磁波在信道上的传播速率(m/s)} =(m/s)(m)

      [^]: 电磁波在铜线电缆中的传播速度约为 3.0 x 10⁵ km/s,在光纤中的传播速率约为 2.0 x 10⁵ km/s

    3. 处理时延:主机或路由器在收到分组时需要花费一定的时间进行处理,例如分析分组的首部、从分组中提取数据部分、进行差错校验或查找适当的路由等,这就产生了处理时延

    4. 排队时延:分组在经过网络传输时,要经过许多路由器。分组在进入路由器后要先在输入队列中排队等待处理,在路由器确定了转发接口后,还要在输出队列中排队等待转发,这就产生了排队时延

    5. 总时延:数据在网络中经历的总时延就是以上四种时延之和:
      总 时 延 = 发 送 时 延 + 传 播 时 延 + 处 理 时 延 + 排 队 时 延 总时延=发送时延+传播时延+处理时延+排队时延 =+++

  5. 时延带宽积:将 传播时延 和 带宽 相乘就得到了传播时延带宽积,即:
    时 延 带 宽 积 = 传 播 时 延 × 带 宽 时延带宽积=传播时延 \times 带宽 =×
    例如,设某段链路的传播时延为 20 ms,带宽为 10 Mbit/s,可以算出:
    时 延 带 宽 积 = 20 × 1 0 − 3 × 10 × 1 0 6 = 2 × 1 0 5 b i t 时延带宽积=20\times10^{-3}\times10\times10^6=2\times10^5bit =20×103×10×106=2×105bit
    这就表明,若发送段连续发送数据,则在发送的第一个比特即将到达终点时,发送段就已经发送了 20 万个比特,而这 20 万个比特都正在链路上向前方移动。因此,链路的时延带宽积又称为以比特为单位的链路长度

  6. 往返时间 RTT:在计算机网络中,往返时间也是一个重要的性能指标。这是因为在许多情况下,互联网上的信息不仅仅单方向传输而是双向交互的,因此我们有时很需要知道双向交互一次所需的时间。例如,A 向 B 发送数据,。如果数据长度为 100 MB,发送速率为 100 Mbit/s,那么:
    发 送 时 间 = 数 据 长 度 发 送 速 率 = 100 × 2 20 × 8 100 × 1 0 6 ≈ 8.39 s 发送时间=\frac{数据长度}{发送速率}=\frac{100\times2^{20}\times8}{100\times10^6}\approx8.39s ==100×106100×220×88.39s
    如果 B 正确收完 100 MB 的数据后,就立即向 A 发送确认。假定 A 只有收到 B 的确认信息后,才能继续向 B 发送数据。显然,这里需要等待一个往返时间RTT(这里假定确认消息很短,可忽略 B 发送确认的时间)。如果 RTT = 2 s,那么可以算出 A 向 B 发送数据的有效数据率:
    有 效 数 据 率 = 数 据 长 度 发 送 时 间 + R T T = 100 × 2 20 × 8 8.39 + 2 ≈ 80.7 M b i t / s 有效数据率=\frac{数据长度}{发送时间+RTT}=\frac{100\times2^{20}\times8}{8.39+2}\approx80.7Mbit/s =+RTT=8.39+2100×220×880.7Mbit/s

  7. 利用率:利用率有 信道利用率 和 网络利用率 两种

    1. 信道利用率指出某信道有百分之几的时间是被利用的(有数据通过),完全空闲的信道的利用率为零
    2. 网络利用率则是全网络的信道利用率的加权平均值
    3. 信道利用率并非越高越好,信道或网络的利用率过高会产生非常大的时延

16.1.6 IP地址

  1. 概念:用于唯一标识网络中的每台主机
  2. Windows查看ip地址命令:ipconfig,Linux查看ip地址命令:ifconfig
  3. ip地址的表示形式:点分十进制 xx.xx.xx.xx
  4. 每一个十进制数的范围是:0 ~ 255
  5. ip地址的组成 = 网络地址 + 主机地址。比如:192.168.16.69
  6. IPV6是互联网工程任务组设计的用于替换IPV4的下一代IP地址,其地址数量号称可以为全世界的每一粒沙子编上一个地址
  7. 由于IPV4的最大问题在于网络地址资源有限,严重制约了互联网的应用和发展。IPV6的使用,不仅能解决网络地址资源数量的问题,而且也解决了多种接入设备接入互联网的障碍
  8. IPV4地址的分类:在这里插入图片描述
    在这里插入图片描述

16.1.7 域名和端口号

  1. 域名
    1. 如 www.baidu.com
    2. 好处:方便记忆,解决记ip地址的困难
    3. 概念:将ip地址映射成域名(通过HTTP协议)
  2. 端口号
    1. 概念:用于标识计算机上某个特定的网络程序
    2. 表示形式:以整数形式,端口范围是 0 ~ 65535【0 ~ 2^16 -1】
    3. 0 ~ 1024已经被占用,如 ssh 22、ftp 21、smtp 25、http 80等
    4. 常见的网络程序默认端口号:
      1. tomcat:8080
      2. mysql:3306
      3. oracle:1521
      4. sqlserver:1433

16.1.8 网络通信协议

在这里插入图片描述

TCP/IP(Transmission Control Protocol/Internet Protocol)中文译名为传输控制协议/因特网互联协议,又叫网络通讯协议,这个协议是Internet最基本的协议、Internet国际互联网的基础,简单地说,就是由网络层的IP协议和传输层的TCP协议组成的

OSI模型TCP/IP模型TCP/IP模型各层对应协议
应用层应用层HTTP、FTP、telnet、DNS...
表示层
会话层
传输层传输层(TCP)TCP、UDP...
网络层网络层(IP)IP、ICMP、ARP...
数据链路层物理+数据链路层Link
物理层
  1. TCP协议:传输控制协议
    1. 使用TCP协议前,须先建立TCP连接,形成传输数据通道
    2. 传输前,采用“三次握手”方式,是可靠的
    3. TCP协议进行通信的两个应用程序:客户端、服务端
    4. 在连接中可进行大数据量的传输
    5. 传输完毕,需释放已建立的连接,效率低
  2. UDP协议:用户数据协议
    1. 将数据、源、目的封装成数据包,不需要建立连接
    2. 每个数据包的大小限制在64K内,不适合传输大量数据
    3. 因无需连接,故是不可靠的
    4. 发送数据结束时无需释放资源(因为不是面向连接的),速度快

16.2 InetAddress类

16.2.1 相关方法

  1. getLocalHost:获取本机 InetAddress 对象
  2. getByName:根据指定主机名/域名获取 ip 地址对象
  3. getHostName:获取 InetAddress 对象的主机名
  4. getHostAddress:获取 InetAddress 对象的地址

16.2.2 示例

public class InetAddressDemo {
    public static void main(String[] args) throws UnknownHostException {
        //获取本机的 InetAddress 对象
        InetAddress localHost = InetAddress.getLocalHost();
        System.out.println(localHost);  //LAPTOP-57CI10E5/192.168.1.103

        //根据指定主机名获取 InetAddress 对象
        InetAddress host1 = InetAddress.getByName("LAPTOP-57CI10E5");
        System.out.println("host1 name="+host1.getHostName());//host1=LAPTOP-57CI10E5

        //根据域名返回 InetAddress 对象
        InetAddress host2 = InetAddress.getByName("www.baidu.com");
        System.out.println("host2 address="+host2.getHostAddress());//host2=39.156.66.18
    }
}

16.3 Socket

14.3.1 介绍

  1. 套接字(Socket)开发网络应用程序被广泛采用,以至于成为事实上的标准
  2. 网络的两端都要有 Socket,是两台主机间通信的端点
  3. 网络通信其实就是 Socket 间的通信
  4. Socket 允许程序把网络连接当成一个流,数据在两个 Socket 间通过 I/O 传输
  5. 一般主动发起通信的应用程序为客户端,等待通信请求的是服务端
  6. 两端的 Socket 都有getOutputStream()方法和getInputStream()方法进行读写数据
  7. 基于 Socket 可以进行 TCP 编程 和 UDP 编程

【主机/客户端 发起连接[Socket]】 <—数据通道—> 【[Socket]主机/服务端 接收请求连接(监听)】

16.4 TCP 网络通信编程

16.4.1 介绍

  1. 基于客户端—服务端的网络通信
  2. 底层使用的是 TCP/IP 协议
  3. 应用场景举例:客户端发送数据,服务端接收并显示在控制台
  4. 基于 Socket 的 TCP 编程

在这里插入图片描述

16.4.2 示例1 使用字节流

  1. 编写一个服务器端和一个客户端
  2. 服务器端在 9999 端口监听
  3. 客户端连接到服务器端,发送“hello,server”,然后退出
  4. 服务端连接到客户端发送的消息,输出并退出
//服务端
public class SocketTCP01Server {
    public static void main(String[] args) throws IOException {
        //在本机的 9999 端口监听,等待连接
        //要求在服务端主机 9999 端口没有被占用
        //ServerSocket 可以通过 accept() 返回多个 Socket【多个客户端连接服务器的并发】
        ServerSocket serverSocket = new ServerSocket(9999);
        System.out.println("服务端在 9999 端口监听,等待连接");
        //当没有客户端连接 9999 端口时,程序会阻塞,等待连接
        //如果有客户端连接,则会返回一个 Socket 对象,程序继续执行
        Socket socket = serverSocket.accept();
        System.out.println("服务器端 socket = "+socket.getClass());
        //通过 socket.getInputStream() 读取客户端写入到数据通道的数据
        InputStream inputStream = socket.getInputStream();
        byte[] buf = new byte[1024];
        int readLen = 0;
        while ((readLen = inputStream.read(buf)) != -1) {
            System.out.println(new String(buf,0,readLen));
        }
        //关闭流和 Socket
        inputStream.close();
        socket.close();
        serverSocket.close();
        System.out.println("服务端退出了...");
    }
}
//客户端
public class SocketTCP01Client {
    public static void main(String[] args) throws IOException {
        //连接服务端(ip,端口)
        //连接 InetAddress.getLocalHost() 也就是本机的 9999 端口
        //连接上后,生成 Socket 对象
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
        System.out.println("客户端socket = "+socket.getClass());
        //通过 socket.getOutputStream() 获得和 Socket 对象关联的输出流
        OutputStream outputStream = socket.getOutputStream();
        //通过输出流写入数据到数据通道
        outputStream.write("hello,server".getBytes());
        //关闭流对象和 Socket
        outputStream.close();
        socket.close();
        System.out.println("客户端退出了...");
    }
}

16.4.3 示例2 使用字节流

  1. 编写一个服务端和客户端
  2. 服务端在 9999 端口监听
  3. 客户端连接到服务端后,发送“hello,server”,并接收到服务端回发的“hello,client”,再退出
  4. 服务端接收到客户端发送的信息,输出,并发送“hello,client”再退出
//服务端
public class SocketTCP02Server {
    public static void main(String[] args) throws IOException {
        //在本机的 9999 端口监听,等待连接
        //要求在服务端主机 9999 端口没有被占用
        //ServerSocket 可以通过 accept() 返回多个 Socket【多个客户端连接服务器的并发】
        ServerSocket serverSocket = new ServerSocket(9999);
        System.out.println("服务端在 9999 端口监听,等待连接");
        //当没有客户端连接 9999 端口时,程序会阻塞,等待连接
        //如果有客户端连接,则会返回一个 Socket 对象,程序继续执行
        Socket socket = serverSocket.accept();
        System.out.println("服务器端 socket = "+socket.getClass());
        //通过 socket.getInputStream() 读取客户端写入到数据通道的数据
        InputStream inputStream = socket.getInputStream();
        byte[] buf = new byte[1024];
        int readLen = 0;
        while ((readLen = inputStream.read(buf)) != -1) {
            System.out.println(new String(buf,0,readLen));
        }
        //通过 socket.getOutputStream() 获取输出流,写入数据到数据通道
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("hello,client".getBytes());
        //设置一个输出结束标记
        socket.shutdownOutput();
        //关闭流和 Socket
        inputStream.close();
        outputStream.close();
        socket.close();
        serverSocket.close();
        System.out.println("服务端退出了...");
    }
}
//客户端
public class SocketTCP02Client {
    public static void main(String[] args) throws IOException {
        //连接服务端(ip,端口)
        //连接 InetAddress.getLocalHost() 也就是本机的 9999 端口
        //连接上后,生成 Socket 对象
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
        System.out.println("客户端socket = "+socket.getClass());
        //通过 socket.getOutputStream() 获得和 Socket 对象关联的输出流
        OutputStream outputStream = socket.getOutputStream();
        //通过输出流写入数据到数据通道
        outputStream.write("hello,server".getBytes());
        //设置一个输出结束标记
        socket.shutdownOutput();
        //通过 socket.getInputStream() 获取输入流,读取数据通道的数据并显示
        InputStream inputStream = socket.getInputStream();
        byte[] buf = new byte[1024];
        int readLen = 0;
        while ((readLen = inputStream.read(buf)) != -1) {
            System.out.println(new String(buf,0,readLen));
        }
        //关闭流对象和 Socket
        inputStream.close();
        outputStream.close();
        socket.close();
        System.out.println("客户端退出了...");
    }
}

16.4.4 示例3 使用字符流

  1. 编写一个服务端和客户端
  2. 服务端在 9999 端口监听
  3. 客户端连接到服务端后,发送“hello,server”,并接收到服务端回发的“hello,client”,再退出
  4. 服务端接收到客户端发送的信息,输出,并发送“hello,client”再退出
//服务端
public class SocketTCP03Server {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(9999);
        System.out.println("服务端在 9999 端口监听,等待连接");
        Socket socket = serverSocket.accept();
        
        //接收数据
        InputStream inputStream = socket.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
        String line = br.readLine();
        System.out.println(line);
        
        //发送数据
        OutputStream outputStream = socket.getOutputStream();
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
        bw.write("hello,client 字符流");
        bw.newLine();//插入换行符,表示写入内容结束
        bw.flush();//字符流需要手动刷新,否则不会写入数据通道
        socket.shutdownOutput();//对方使用循环读取需要设置结束标记,若调用一次readLine(),不需要此标记
        
        //关闭
        bw.close();
        br.close();
        socket.close();
        serverSocket.close();
    }
}
//客户端
public class SocketTCP03Client {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
        //发送数据
        OutputStream outputStream = socket.getOutputStream();
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
        bw.write("hello,server 字符流");
        bw.newLine();//插入换行符,表示写入内容结束,要求对方用readLine()读
        bw.flush();//字符流需要手动刷新,否则不会写入数据通道

        //接收数据
        InputStream inputStream = socket.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
        String line = null;
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }
        
        //关闭
        br.close();
        bw.close();
        socket.close();
    }
}

16.4.5 示例4 上传文件

  1. 编写一个服务端和一个客户端
  2. 服务端在 8888 端口监听
  3. 客户端连接到服务端,发送一张图片
  4. 服务端接收到客户端发送的图片后,保存在 src 下,发送“收到图片”再退出
  5. 客户端接收到服务端发送的“收到图片”,再退出
  6. 该程序要求使用 StreamUtils.java 工具类
/**
 * 工具类
 */
public class StreamUtils {
    /**
     * 功能:将输入流转换成byte[], 即可以把文件的内容读入到byte[]
     *
     * @param is
     * @return
     * @throws IOException
     */
    public static byte[] streamToByteArray(InputStream is) throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();//创建输出流对象
        byte[] b = new byte[1024];//字节数组
        int len;
        while ((len = is.read(b)) != -1) {//循环读取
            bos.write(b, 0, len);//把读取到的数据,写入bos   
        }
        byte[] array = bos.toByteArray();//然后将bos 转成字节数组
        bos.close();
        return array;
    }

    /**
     * 功能:将InputStream转换成String
     *
     * @param is
     * @return
     * @throws IOException
     */

    public static String streamToString(InputStream is) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        StringBuilder builder = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            builder.append(line + "\r\n");
        }
        return builder.toString();

    }

}
//服务端
public class TCPFileUploadServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8888);
        System.out.println("监听 8888 端口,等待客户端连接...");
        Socket socket = serverSocket.accept();
        //读取客户端发送的存放图片数据的字节数组
        InputStream inputStream = socket.getInputStream();
        BufferedInputStream bis = new BufferedInputStream(inputStream);
        byte[] picData = StreamUtils.streamToByteArray(bis);
        //将得到的字节数组写入指定路径,就得到一个文件
        String destFilePath = "src\\bg.png";
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFilePath));
        bos.write(picData);
        bos.close();
        //回发消息
        OutputStream outputStream = socket.getOutputStream();
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream));
        bw.write("服务端收到图片");
        bw.flush();
        socket.shutdownOutput();//设置写入结束标记
        //使用结束标记而非换行符标记写入结束,客户端可以使用字节流接收

        //释放资源
        bw.close();
        bis.close();
        socket.close();
        serverSocket.close();
    }
}
//客户端
public class TCPFileUploadClient {
    public static void main(String[] args) throws IOException {
        //客户端连接服务端,得到 Socket 对象
        Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
        //读取图片转换为字节数组
        String filePath = "C:\\Users\\ZHF\\Desktop\\bg.png";
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath));
        byte[] picData = StreamUtils.streamToByteArray(bis);
        bis.close();
        //获取 socket 对应的输出流,通过输出流发送数据
        OutputStream outputStream = socket.getOutputStream();
        BufferedOutputStream bos = new BufferedOutputStream(outputStream);
        bos.write(picData);
        socket.shutdownOutput();

        //接收服务端回复的消息
        InputStream inputStream = socket.getInputStream();
        String mes = StreamUtils.streamToString(inputStream);
        System.out.println(mes);

        //释放资源
        bos.close();
        socket.close();
    }
}

16.4.6 示例5 下载文件

  1. 编写客户端和服务端程序
  2. 客户端输入一个音乐文件名,比如 高山流水
  3. 服务端收到音乐名后,判断有没有这个文件,有就返回这个文件,没有则返回一个默认文件
  4. 客户端收到文件后,保存到本地
  5. 可以使用工具类 StreamUtils.java

工具类文件 StreamUtils.java 同上

//服务端
public class TCPFileDownloadServer {
    public static void main(String[] args) throws IOException {
        //监听 8888 端口
        ServerSocket serverSocket = new ServerSocket(8888);
        System.out.println("正在等待客户端请求...");
        Socket socket = serverSocket.accept();
        //获取音乐名
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String musicName = br.readLine();
        System.out.println("要下载的音乐为:" + musicName);
        //判断是否有该音乐
        File file = new File("Resource\\" + musicName + ".mp3");
        if (!file.exists())
            file = new File("Resource\\无名.mp3");
        //读取音乐文件
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
        byte[] musicData = StreamUtils.streamToByteArray(bis);
        //回发给客户端
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
        bos.write(musicData);
        socket.shutdownOutput();
        //释放资源
        bos.close();
        bis.close();
        br.close();
        socket.close();
        serverSocket.close();
    }
}
//客户端
public class TCPFileDownloadClient {
    public static void main(String[] args) throws IOException {
        //用户输入
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入音乐名:");
        String musicName = scanner.next();
        //将音乐名发送到客户端
        Socket socket = new Socket(InetAddress.getByName("192.168.1.104"), 8888);
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        bw.write(musicName);
        bw.newLine();
        bw.flush();
        //接收音乐文件
        InputStream inputStream = socket.getInputStream();
        byte[] musicData = StreamUtils.streamToByteArray(inputStream);
        //写入到本地
        String filePath = "C:\\Users\\ZHF\\Desktop\\" + musicName + ".mp3";
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath));
        bos.write(musicData);
        System.out.println("下载完成");
        //释放资源
        bos.close();
        inputStream.close();
        bw.close();
        socket.close();
        scanner.close();
    }
}

16.4.7 netstat 命令

  1. netstat -an可以查看当前主机网络情况,包括端口监听情况和网络连接状况
  2. netstat -an | more可以分页显示
  3. Listening 表示某个端口正在监听
  4. ESTABLISHED表示连接上了

在这里插入图片描述

在这里插入图片描述

16.5 UDP 网络通信编程

16.5.1 介绍

  1. 类DatagramSocket 和DatagramPacket[数据包/数据报]实现了基于UDP 协议网络程序。
  2. UDP数据报通过数据报套接字DatagramSocket发送和接收,系统不保证UDP 数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。
  3. DatagramPacket对象封装了UDP数据报,在数据报中包含了发送端的IP地址和 端口号以及接收端的IP地址和端口号。
  4. UDP协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和接收方 的连接

16.5.2 基本流程

  1. 核心的两个类/对象DatagramSocket与DatagramPacket
  2. 建立发送端,接收端(没有服务端和客户端概念)
  3. 发送数据前,建立数据包/报DatagramPacket对象
  4. 调用DatagramSocket的发送、接收方法
  5. 关闭DatagramSocket

16.5.3 示例

  1. 编写一个接收端A,和一个发送端B
  2. 接收端A在9999端口等待接收数据(receive)
  3. 发送端B向接收端A发送数据"hello,明天吃火锅~"
  4. 接收端A接收到发送端B发送的数据,回复“好的,明天见",再退出
  5. 发送端接收回复的数据,再退出
public class UDPReceiverA {
    public static void main(String[] args) throws IOException {
        //创建 DatagramSocket 对象,监听 9999 端口
        DatagramSocket datagramSocket = new DatagramSocket(9999);
        //构建一个 DatagramPacket 对象,准备接收数据
        //UDP数据包最大 64K,这里仅接收一句话 1K足够
        byte[] buf = new byte[1024];
        DatagramPacket datagramPacket = new DatagramPacket(buf, buf.length);
        //调用接受方法,将接收到的 DatagramPacket 对象填充到 datagramPacket
        System.out.println("接收端A正在等待接收数据...");
        datagramSocket.receive(datagramPacket);
        //将数据包拆包,取出数据并显示
        int packetLength = datagramPacket.getLength();
        byte[] packetData = datagramPacket.getData();
        String s = new String(packetData, 0, packetLength);
        System.out.println(s);

        //构建回发消息数据包
        byte[] bytes = "好的,明天见".getBytes(StandardCharsets.UTF_8);
        DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("192.168.1.104"), 9998);
        //回发消息
        datagramSocket.send(packet);
        //释放资源
        datagramSocket.close();
    }
}
public class UDPSenderB {
    public static void main(String[] args) throws IOException {
        //创建 DatagramSocket 对象,监听 9998 端口
        DatagramSocket datagramSocket = new DatagramSocket(9998);
        //构建数据包
        byte[] sendData = "hello,明天吃火锅~".getBytes(StandardCharsets.UTF_8);
        DatagramPacket datagramPacket = new DatagramPacket(sendData, sendData.length, InetAddress.getByName("192.168.1.104"), 9999);
        //发送数据包
        datagramSocket.send(datagramPacket);

        //接受回发消息
        byte[] buf = new byte[1024];
        DatagramPacket receivePacket = new DatagramPacket(buf, buf.length);
        datagramSocket.receive(receivePacket);
        //取出数据
        String s = new String(receivePacket.getData(), 0, receivePacket.getLength());
        System.out.println(s);
        //释放资源
        datagramSocket.close();
    }
}

16.6 练习

16.6.1 TCP练习

  1. 使用字符流的方式,编写一个客户端程序和服务器端程序,
  2. 客户端发送"name",服务器端接收到后,返回“我是nova", nova是你自己的名字.
  3. 客户端发送"hobby",服务器端接收到后,返回“编写java程序"
  4. 不是这两个问题,回复“你说啥呢”
  5. 问题:目前,我们只能问一次,就退出了,怎么可以问多次?->while->聊天
public class TCPServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8888);
        Socket socket = serverSocket.accept();
        //接收
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String readLine = br.readLine();
        System.out.println(readLine);
        //回发
        String rep = null;
        if ("name".equals(readLine))
            rep = "我是 nova";
        else if ("hobby".equals(readLine))
            rep = "编写Java程序";
        else
            rep = "你说啥呢";
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        bw.write(rep);
        bw.newLine();
        bw.flush();
        //释放资源
        bw.close();
        br.close();
        socket.close();
        serverSocket.close();
    }
public class TCPClient {
    public static void main(String[] args) throws IOException {
        //输入
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入:");
        String next = scanner.next();
        //发送
        Socket socket = new Socket(InetAddress.getByName("192.168.1.104"), 8888);
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        bw.write(next);
        bw.newLine();
        bw.flush();
        //接收回发
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String readLine = br.readLine();
        System.out.println(readLine);
        //释放资源
        br.close();
        bw.close();
        socket.close();
    }
}

16.6.2 UDP练习

  1. 编写一个接收端A,和一个发送端B,使用UDP协议完成
  2. 接收端在8888端口等待接收数据(receive)
  3. 发送端向接收端发送数据“四大名著是哪些”
  4. 接收端接收到发送端发送的问题后,返回"四大名著是《红楼梦》…",否则返回 what?
  5. 接收端和发送端程序退出
public class UDPA {
    public static void main(String[] args) throws IOException {
        //监听8888端口
        DatagramSocket datagramSocket = new DatagramSocket(8888);
        //接收数据包
        byte[] buf = new byte[1024];
        DatagramPacket receivePacket = new DatagramPacket(buf, buf.length);
        datagramSocket.receive(receivePacket);
        //取出数据
        String receiveStr = new String(receivePacket.getData(), 0, receivePacket.getLength());
        System.out.println(receiveStr);
        //回发消息
        String rep = null;
        if ("四大名著是哪些".equals(receiveStr))
            rep = "四大名著是《红楼梦》、《西游记》、《水浒传》和《三国演义》";
        else
            rep = "what?";
        byte[] repBytes = rep.getBytes(StandardCharsets.UTF_8);
        DatagramPacket repPacket = new DatagramPacket(repBytes, repBytes.length, InetAddress.getByName("192.168.1.104"), 9999);
        datagramSocket.send(repPacket);
        //释放资源
        datagramSocket.close();
    }
}
public class UDPB {
    public static void main(String[] args) throws IOException {
        //获取输入
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入:");
        String next = scanner.next();
        //监听 9999 端口
        DatagramSocket datagramSocket = new DatagramSocket(9999);
        //发送数据
        byte[] bytes = next.getBytes(StandardCharsets.UTF_8);
        DatagramPacket sendPacket = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("192.168.1.104"), 8888);
        datagramSocket.send(sendPacket);
        System.out.println("发送完毕");
        //接收回发数据
        byte[] buf = new byte[1024];
        DatagramPacket receivePacket = new DatagramPacket(buf, buf.length);
        datagramSocket.receive(receivePacket);
        String receiveStr = new String(receivePacket.getData(), 0, receivePacket.getLength());
        System.out.println(receiveStr);
        //释放资源
        datagramSocket.close();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值