1.网络编程基础知识 - 基础概念、TCP网络通信、UDP网络通信

网络编程

文章目录

  • 网络编程
  • 一、概念
    • 1.1 网络
    • 1.2 IP地址
      • 1.2.1 IPv4 介绍
      • 1.2.2 IPv6 介绍
      • 1.2.3 查看IP地址
    • 1.3 域名和端口
    • 1.4 网络协议
    • 1.5 TCP与UDP
    • 1.6 InetAddress类
    • 1.7 Socket
  • 二、TCP网络通信编程
    • 2.1 介绍
    • 2.2 案例
      • 2.2.1 字节流编程案例1
      • 2.2.2 字节流编程案例2
      • 2.2.3 字符流编程案例1
    • 2.3 网络文件上传
      • 2.3.1 网络文件上传案例
    • 2.4 netstat 指令
    • 2.5 TCP网络通信编程说明
  • 三、UDP网络通信编程(了解)
    • 3.1 基本流程
    • 3.2 案例
      • 3.2.1 应用案例 1

一、概念

1.1 网络

  • 网络

    两台或多台设备通过一定物理设备连接起来构成了网络

    根据网络的覆盖范围不同,对网络进行了如下分类

    • 局域网

      覆盖范围最小,仅仅覆盖一个教室或机房或者一个公司

    • 城域网

      覆盖范围较大,可以覆盖一个城市

    • 广域网

      覆盖范围最大,可以覆盖全国甚至全球,万维网是广域网的代表

image-20231123142227043

  • 网络通信

    将数据通过网络从一台设备传输到另一台设备

    两台设备之间通过网络实现数据传输

    java.net包下提供了一系列的类或接口,供程序员使用,完成网络通信

image-20231123141507882

主机H1和H2之间通信需要中间很多其他的设备来完成,并且需要通过许多的网络才能到达H2所在的局域网

1.2 IP地址

用于唯一标识网络中的每台计算机/主机

主机A向主机B发送信息的时候,我们首先应该确认的就是主机B在哪里

image-20231123145332581

  • IP地址的表示形式

点分十进制 xxx.xxx.xxx.xxx

每一个十进制数的范围:0~255

对于IPV4来说,需要四个字节,一个字节有8位,所以四个字节有32位,每个字节表示的范围是0~255

image-20231123150331132

  • IP地址的组成=网络地址+主机地址

    比如:192.168.16.69,其中192.168.16代表网络地址,69表示主机地址,相当于先标识是哪一个网,再标识是哪一个主机

    简单理解就是网络地址类似山东省济南市…小区2号楼2单元,主机地址表示1101房间

1.2.1 IPv4 介绍

根据IP地址的组成=网络地址+主机地址,对IPv4进行分类

  • IP地址分类

    ABC类比较常见,DE类比较特殊

    A类表示的主机要多一点,因为有24位主机号,可以表示主机 0~(2^24)-1个主机

    B类用16位也就是2个字节来表示网络号,并且前面两位固定10;也用2个字节来表示主机号

    C类用24位也就是3个字节表示网络号,并且前面三位固定号码110;主机号用8位也就是1个字节来表示,可以表示0~255个主机

    D类多播组号类似广播组号

    E类保留起来后面使用

    image-20231123155335187

根据上面划分的规则,可以计算出IPv4各个类型的表示范围

特殊:127.0.0.1表示本机地址

image-20231123155357111

1.2.2 IPv6 介绍

  • IPv6

    IPv6使用128位标识一个地址,一共有16个字节(128/8=16,一个字节有8位),相当于是Ipv4的4倍(IPv4有4个字节)

    IPv4不够用了,出来了IPv6,现在IPv6还没有完全替代IPv4,需要一个过度的过程

    IPv6是互联网工程任务组设计的用于替代IPv4的下一代IP协议,其地址数量号称可以为全世界的每一粒沙子编上一个地址

    由于IPv4最大的问题在于网络地址资源有限,严重制约了互联网的应用和发展。IPv6的使用,不仅能解决网络地址数量的问题,而且也能解决多种接入设备连入互联网的障碍

    当时IPv4的设计者考虑的是将来我们连接到网络的设备都是主机/电脑,而且设计者还认为主机/电脑不会有太多,所以4个字节就够用了

    image-20231123152529694

    但是随着物联网的发展,不仅主机可以连接网络,一个基本的网络设备(比如手机/相机等)都可以连接到网络上,那此时我们就需要更多的能够唯一标识设备的IP地址,于是乎提出了IPv6的概念

    image-20231123152957082

  • IPv6的表示方法

    IPv6的地址长度为128位,是IPv4地址长度的4倍。

    于是IPv4点分十进制格式不再适用,采用十六进制表示

    IPv6使用128位标识一个地址,一共有16个字节

    128/8=16,一个字节有8位

    因为是十六进制表示,X:X:X:X:X:X:X:X中一个X代表两个字节

    IPv6有3种表示方法

    • 冒分十六进制表示法

      格式为X:X:X:X:X:X:X:X,其中每个X表示地址中的16b,以十六进制表示,例如:

      ABCD:EF01:2345:6789:ABCD:EF01:2345:6789

      这种表示法中,每个X的前导0是可以省略的,例如:

      2001:0DB8:0000:0023:0008:0800:200C:417A → 2001:DB8:0:23:8:800:200C:417A

    • 0位压缩表示法

      在某些情况下,一个IPv6地址中间可能包含很长的一段0,可以把连续的一段0压缩为“::”。但为保证地址解析的唯一性,地址中”::”只能出现一次,例如:

      FF01:0:0:0:0:0:0:1101 → FF01::1101

      0:0:0:0:0:0:0:1 → ::1

      0:0:0:0:0:0:0:0 → ::

    • 内嵌IPv4地址表示法

      为了实现IPv4-IPv6互通,IPv4地址会嵌入IPv6地址中,此时地址常表示为:X:X:X:X:X:X:d.d.d.d,前96b采用冒分十六进制表示,而最后32b地址则使用IPv4的点分十进制表示

      例如::192.168.0.1与::FFFF:192.168.0.1就是两个典型的例子,注意在前96b中,压缩0位的方法依旧适用

1.2.3 查看IP地址

  • 命令行方式查看ip地址
ipconfig

我使用的WIFI,所以查看一下“无线局域网适配器 WLAN”

image-20231123145524298

  • 适配器选项查看IP地址
  1. 找到“适配器选项”

image-20231123160905403

  1. 可以选择“WLAN”,右键点击并选择“属性”

image-20231123160939880

  1. 选择“Internet 协议版本4(TCP/IPv4)”,双击

image-20231123161027468

  1. 查看Internet 协议版本4(TCP/IPv4)属性,并且我们发现IP地址是自动获得的

    这说明我们每次连接这个网络的话,都会自动给我们分配一个IP地址,并且每一次分配的IP是不一样的,由我们底层的一个协议分配的IP地址

    什么时候我们的IP是固定的?

    我们买的服务器,他的IP就是固定的

image-20231123161123668

1.3 域名和端口

  • 域名

    概念:将ip地址映射成域名

    为了方便记忆,解决记ip的困难,比如www.baidu.com

    域名通过底层的一套机制可以将域名映射到IP地址

    image-20231123163816579

  • 端口号

    概念:用于标识计算机上某个特定的网络程序

    表示形式:以整数形式,范围0~65535

    为什么是0~65535?

    端口占用两个字节,也就是16位,所以最大是【0~(2^16)-1】

    0~1024已经被占用,比如ssh 22,ftp 21,smtp 25,http 80

    并且在以后的开发中也不要使用0~1024中的端口号,因为大概率被一些系统服务或者一些知名的服务占用

    我们浏览器或某个程序要去找网站服务,其实是ip加一个端口,他就能定位到这个网站服务

image-20231123165130446

两个不同的服务监听同一个端口会出问题。比如说网站服务和邮件服务都监听80端口,那我们访问ip+80端口具体访问的是网站服务还是邮件服务呢?

如果一个端口被监听/占用,其他服务是不能再去监听这个端口的,会报错“端口被占用”

  • 常见的网络程序端口号

    Tomcat:8080

    Mysql:3306

    Oracle:1521

    SQLserver:1433

1.4 网络协议

在网络编程中数据的组成形式就是协议

  • 对网络协议的理解

在网络编程中,数据的组织形式就是协议

并且双方互发数据时要按照规定好的协议方式来进行

image-20231123172940769

  • 协议(TCP/IP)

    在网络里面有一个特别重要的协议就是TCP/IP协议

    TCP/IP(Transmission ControlProtocol/Internet Protocol)

    中文名传输控制协议/因特网互联协议,又叫网络通讯协议,这个协议是Internet最基本的协议、Internet国际互联网络的基础

    简单地说,就是由传输层的TCP协议和网络层的IP协议组成的

    • 发送数据过程
    1. “用户数据"经过"应用程序"加了一个"Appl头”,代表是哪个程序发出来的;

    2. 经过"TCP"传输层又加了"TCP首部",保证这一层的数据能够发送到对方去

    3. 经过"IP"层加了一个"IP首部"(相当于把IP地址加进去了),表示将这条信息发送到哪条电脑上去

    4. 经过"以太网驱动程序"加了一个"以太网首部"和"以太网尾部",到了此时就变成一个以太网帧数据了(二进制的数据),就可以通过物理网络发送到对方去

    • 接收数据过程

      此时对方(叫张三吧)拿到的数据就是“发送数据过程”中最后形成的数据,也就是以太网帧

    1. 对方机器拿到后开始解包。先去掉"以太网首部"和"以太网尾部"(去帧),就是下图中自下而上的过程,首先得到的数据是"IP、TCP、 应用数据"
    2. 再经过张三电脑的"IP"层,将“IP首部去除掉”,进一步拿到了“TCP、应用数据”
    3. 再经过“TCP”层,我们可以获取到"App头和用户数据"
    4. 那电脑怎么知道把数据给哪个程序呢?电脑知道App头之后就可以知道将用户数据送到哪个应用程序里面

    类似一个打包和拆包的过程

image-20231123170926867

  • 网络通信协议

    TCP/IP协议有两个模型

    一种是OSI模型,这种模型是一种理论性的东西,理论模型,现实的计算机里面并没有这个模型。因为在实际应用中这几层的意义分的太细

    对OSI模型做了一个简化,就有了TCP/IP模型

    将“应用层、表示层、会话层”统称为“应用层”,

    “传输层”与“网络层”依然对照“传输层”与“网络层”,

    传输层就是刚刚分析的TCP的那一层,数据经过TCP会打一个TCP的那一个包

    网络层就是刚刚分析的IP层

    传输层和网络层是最重要的

    “数据链路层和物理层”称为“物理+数据链路层”

    这一层主要是加帧头和帧尾的

image-20231123180509415

1.5 TCP与UDP

TCP与UDP是网络协议中TCP/IP模型的传输层中的重要协议

  • TCP协议
  1. 使用TCP协议前,须先建立TCP连接,形成传输数据通道
  2. 传输前,采用“三次握手”方式,是可靠的

第一次:客户端client给服务端server发一个包

image-20231123201322759

第二次:服务端server给客户端client回了一个包

image-20231123201340286

第三次:客户端client给服务端server回一个包

image-20231123201348011

三次握手之后,客户端client就会给服务端server发送大量的数据,因为客户端client认为三次握手结束以后就可以确认服务端server一定可以接收到我们后面发送的数据

上面的模式类似下面的流程

image-20231123201352869

  1. TCP协议进行通信的两个应用进程:客户端、服务端

  2. 在连接中可进行大数据量的传输

  3. 传输完毕,须释放已经建立的连接,效率低

    不释放连接有什么后果?

    此时又来了一个韩老师,韩老师也想给kim打电话,是打不进去的,所以此时tom和kim之间的连接要断掉,这才能释放连接

    image-20231123201544427

  • UDP协议
  1. 将数据、源、目的封装成数据包,不需要建立连接

  2. 每个数据报的大小限制在64K内,不适合传输大量数据

  3. 因无需连接,故是不可靠的

    为什么是不可靠的呢?

    因为根本不确定kim能否收到tom的消息

  4. 发送数据结束时无需释放资源(因为不是面向连接的),速度快

  5. 举例: 厕所通知、发短信

    image-20231123201359413

1.6 InetAddress类

重要方法

image-20231123204205383

  • 获取本机InetAddress对象 getLocalHost

    //TODO 1.获取本机的InetAddress对象
    InetAddress localHost = InetAddress.getLocalHost();
    
    //会输出本机的主机名 、IP地址 zhangjingqi-PC/192.168.101.1
    System.out.println(localHost);
    
  • 根据指定主机名/域名获取ip地址对象 getByName

    //TODO 2.根据指定主机名获取InetAddress
    InetAddress byName = InetAddress.getByName("zhangjingqi-PC");
    
    System.out.println(byName);//zhangjingqi-PC/192.168.101.1
    
//      TODO 3.根据指定域名获取ip地址对象
        InetAddress inetAddress = InetAddress.getByName("www.baidu.com");

//      www.baidu.com/110.242.68.4
        System.out.println(inetAddress);
  • 获取InetAddress对象的主机名 getHostName

    //TODO 4.通过InetAddress对象,获取对应的主机名
    String hostName = inetAddress.getHostName();
    
    //www.baidu.com
    System.out.println(hostName);
    
  • 获取InetAddress对象的地址 getHostAddress

    //TODO 5.通过InetAddress对象,获取对应的主机地址
    String hostAddress = inetAddress.getHostAddress();
    
    //110.242.68.3
    System.out.println(hostAddress);
    

1.7 Socket

Socket有两种编程方式,TCP网络通信编程和UDP网络通信编程

TCP编程可靠,UDP编程不可靠

  • 套接字(Socket)开发网络应用程序被广泛采用,以至于成为事实上的标准

  • 通信的两端都要有Socket,是两台机器间通信的端点

    可以理解为主机两端的插头

    image-20231123205527865

  • 网络通信其实就是Socket间通信

Socket编程其实就是通过Socket拿到管道的输入流和输出流,然后就可以进行数据的读写操作了

  • Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输

客户端主机和服务器主机之前通信需要建立一个连接(数据通道)

当我们需要通讯(读写数据)时,可以使用socket.getOutputStream()获得输出流,可以使用socket.getInputStream()获得输入流

客户端主机的socket可以使用输出流向数据通道中写入数据

服务器端主机的socket可以使用输入流从数据通道中读取数据

image-20231123205904355

  • 一般主动发起通信的应用程序属客户端,等待通信请求的为服务端

二、TCP网络通信编程

2.1 介绍

  • 基于客户端-服务端的网络通信

  • 底层使用TCP/IP协议

  • 应用场景举例:客户端发送数据,服务端接收并显示

  • 基于Socket的TCP编程

服务器会先进行监听,等待别人的连接

连接之后,客户端Client就可以得到一个输出流,就会把数据发送给服务端Server

服务端Server要想知道客户端Client发送的什么内容,需要先获取输入流,利用输入流从数据通道中读取数据

最后执行完成后一定要关闭Socket,因为太多Client连接Server不关闭之后,后续的客户端Client就会连接不上服务端Server。简单说就是Socket的连接数是有限的

image-20231123210455812

2.2 案例

2.2.1 字节流编程案例1

IO + File 详细基础知识_我爱布朗熊的博客-CSDN博客

应用案例

  • 编写一个服务端和一个客户端

  • 服务器端在9999端口监听

  • 客户端连接到服务器端,发送“hello,server”,然后退出

  • 服务器端接收到客户端发送的信息,输出,并退出

image-20231123211719504

ServerSocket和Socket有什么区别

ServerSocket可以对应创建很多Socket,只要有一个accept就会返回一个socket,可以处理多个客户端连接服务器时的并发问题

image-20231123220458272

服务端代码

/**
 * 服务端
 */
public class SocketTCP01Server {
    public static void main(String[] args) throws IOException {
        //TODO 1.在本机的9999端口监听,等待连接
        //一定要确认9999端口没有被其他服务在监听或占用
        ServerSocket serverSocket = new ServerSocket(9999);
        System.out.println("服务端,在9999端口监听,等待连接....");

        //TODO 2.当没有客户端连接9999端口时,程序会堵塞,等待连接
        //如果有客户端连接,则会返回一个socket对象,程序继续
        Socket socket = serverSocket.accept();
        System.out.println("服务器端 socket=" + socket.getClass());

        //TODO 3.通过socket.getInputStream()读取客户端写入到数据端通道的数据
        InputStream inputStream = socket.getInputStream();

        //缓冲
        byte[] bytes = new byte[512 * 2];
        int readCount = 0;
        while ((readCount = inputStream.read(bytes)) != -1) {
            System.out.println(new String(bytes, 0, readCount));
        }

        //TODO 4.关闭流和socket
        inputStream.close();
        socket.close();
        serverSocket.close();//多了一个这个
    }
}

image-20231123220850928

客户端代码

public class SocketTCP01Client {
    public static void main(String[] args) throws IOException {
        //TODO 1.连接服务端(ip,端口)
        //TODO 2.连接上后,生成Socket
        //连接本机9999端口,如果连接成功的话会返回一个socket
        Socket socket = new Socket(InetAddress.getLocalHost(),9999);
        //Socket socket = new Socket("127.0.0.1",9999);
        System.out.println("客户端 socket返回="+socket.getClass());

        //TODO 3.得到和socket关联的socket.getOutputStream输出流对象
        OutputStream outputStream = socket.getOutputStream();

        //TODO 4.通过输出流,写入数据到数据通道
        outputStream.write("hello,server".getBytes());
        outputStream.fulsh;
        //TODO 关闭流对象和socket对象,避免资源浪费
        outputStream.close();
        socket.close();

        System.out.println("客户端退出了");
    }
}

image-20231123220913617

2.2.2 字节流编程案例2

案例说明

  • 编写一个服务器端,和一个客户端

  • 服务器端在 9999端口监听

  • 客户端连接到服务器端,发送“hello,server”,并接收服务器端回发的"hello,client”再退出

  • 服务器端接收到 客户端发送的 信息,输出,并发送“hello,client”,再退出

image-20231123222731450

在下面编程的时候可能遇到这么一种情况

服务端一直卡在这里,不会向客户端响应信息

image-20231123223434807

客户端没有接收到服务端发送过来的消息

image-20231123223444735

原因

我们客户端"hello,server"消息发送,服务端接收到信息之后,服务端并不知道什么时候代表结束

同样的道理,我们服务端给客户端回传"hello,client",客户端读取之后,也并不知道什么时候代表结束,也就是客户端并不知道服务端有没有说完,服务端发送消息的时候没有一个结束标记

所以我们应该在客户端发送完消息后写一个结束标记,表示客户端已经说完了,服务端就不用再等着读取了

同理服务端也要设置一下结束标记

image-20231123224204593

怎么设置写入结束标记?

socket.shutdownOutput();

服务端代码

public class SocketTCP02Server {
    public static void main(String[] args) throws IOException {
        //TODO 1.在本机的9999端口监听,等待连接
        //一定要确认9999端口没有被其他服务在监听或占用
        ServerSocket serverSocket = new ServerSocket(9999);
        System.out.println("服务端,在9999端口监听,等待连接....");

        //TODO 2.当没有客户端连接9999端口时,程序会堵塞,等待连接
        //如果有客户端连接,则会返回一个socket对象,程序继续
        Socket socket = serverSocket.accept();
        System.out.println("服务器端 socket=" + socket.getClass());

        //TODO 3.通过socket.getInputStream()读取客户端写入到数据端通道的数据
        InputStream inputStream = socket.getInputStream();

        //缓冲
        byte[] bytes = new byte[512 * 2];
        int readCount = 0;
        while ((readCount = inputStream.read(bytes)) != -1) {
            System.out.println(new String(bytes, 0, readCount));
        }

        //TODO 4.获取socket相关联输出流
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("hello,client".getBytes());
        outputStream.flush();
        //设置结束标记
        socket.shutdownOutput();

        //TODO 5.关闭流和socket
        inputStream.close();//先打开的流后关闭
        outputStream.close();
        
        socket.close();
        serverSocket.close();//多了一个这个
    }
}

image-20231123225112375

客户端代码

public class SocketTCP02Client {
    public static void main(String[] args) throws IOException {
        //TODO 1.连接服务端(ip,端口)
        //TODO 2.连接上后,生成Socket
        //连接本机9999端口,如果连接成功的话会返回一个socket
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
        //Socket socket = new Socket("127.0.0.1",9999);
        System.out.println("客户端 socket返回=" + socket.getClass());

        //TODO 3.得到和socket关联的socket.getOutputStream输出流对象
        OutputStream outputStream = socket.getOutputStream();

        //TODO 4.通过输出流,写入数据到数据通道
        outputStream.write("hello,server".getBytes());
        outputStream.flush();
        
        //设置结束标记
        socket.shutdownOutput();

        //TODO 5.获取输入流,读取数据
        InputStream inputStream = socket.getInputStream();

        //缓冲
        byte[] bytes = new byte[512 * 2];
        int readCount = 0;
        while ((readCount = inputStream.read(bytes)) != -1) {
            System.out.println(new String(bytes, 0, readCount));
        }

        //TODO 6.关闭流对象和socket对象,避免资源浪费
        outputStream.close();//先打开的流后关闭
        inputStream.close();
        socket.close();

        System.out.println("客户端退出了");
    }
}

image-20231123225127373

2.2.3 字符流编程案例1

IO + File 详细基础知识_我爱布朗熊的博客-CSDN博客

在网络通信时使用文本的形式更方便一点

  • 编写一个服务端和一个客户端
  • 服务端在9999端口监听
  • 客户端连接到服务端,发送“hello,server”,并接收服务端返回的“hello,client”,再退出
  • 服务端接收到客户端发送的信息,输出,并发送“hello,client”

image-20231124150610383

设置结束标记

  • 方式1
socket.shutdownOutput();
  • 方式2

    这个地方很特殊,一行要使用readLine的方式来读取,另一方使用writer.newLine方法作为结束标记才有效

writer.newLine();//一个换行符

server服务端

public class SocketTCP03Server {
    public static void main(String[] args) throws IOException {
        //TODO 1.在本机的9999端口监听,等待连接
        //一定要确认9999端口没有被其他服务在监听或占用
        ServerSocket serverSocket = new ServerSocket(9999);
        System.out.println("服务端,在9999端口监听,等待连接....");

        //TODO 2.当没有客户端连接9999端口时,程序会堵塞,等待连接
        //如果有客户端连接,则会返回一个socket对象,程序继续
        Socket socket = serverSocket.accept();
        System.out.println("服务器端 socket=" + socket.getClass());

        //TODO 3.获取socket.getInputStream()字节流,并利用转换流将其转换成字符流
        InputStream inputStream = socket.getInputStream();
        InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

        //TODO 4.读取客户端发送过来的数据
        //一次性最多读取1K的内容,因为char类型占用2个字节,一共有512*2=1024个byte,也就是1k
        char[] chars = new char[512];
        int readCount = 0;
        while ((readCount = bufferedReader.read(chars)) != -1) {
            System.out.println(new String(chars, 0, readCount));
        }

        //TODO 5.获取socket相关联输出流,并利用转换流将其转换成字符流
        OutputStream outputStream = socket.getOutputStream();
        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
        //BufferedWriter是字符缓冲流
        BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
        //TODO 6.通过输出流,写入数据到数据通道
        bufferedWriter.write("hello,client,字符流");
        bufferedWriter.flush();
        //设置结束标记
        socket.shutdownOutput();



        //TODO 7.关闭流和socket
        bufferedReader.close();//关闭外层流即可,先打开的流后关闭
        bufferedWriter.close();
        socket.close();
        serverSocket.close();//多了一个这个
    }
}

image-20231124154614929

client客户端

public class SocketTCP03Client {
    public static void main(String[] args) throws IOException {
        //TODO 1.连接服务端(ip,端口)
        //TODO 2.连接上后,生成Socket
        //连接本机9999端口,如果连接成功的话会返回一个socket
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
        //Socket socket = new Socket("127.0.0.1",9999);
        System.out.println("客户端 socket返回=" + socket.getClass());

        //TODO 3.得到和socket关联的socket.getOutputStream输出流对象
        OutputStream outputStream = socket.getOutputStream();
        //TODO 4.利用转换流将字节流转换成字符流
        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
        //BufferedWriter是字符缓冲流
        BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
        //TODO 5.通过输出流,写入数据到数据通道
        bufferedWriter.write("hello,server,字符流");
        bufferedWriter.flush();//如果使用的字符流,需要手动刷新一下,否则数据不会写入通道中
        //设置结束标记
        socket.shutdownOutput();


        //TODO 5.获取输入流,并将字节输入流转换成字符输入流
        //InputStream是字节流,InputStreamReader是字符流,BufferedReader是字符缓冲流
        InputStream inputStream = socket.getInputStream();
        InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

        //TODO 6.读取客户端发送过来的数据
        //一次性最多读取1K的内容,因为char类型占用2个字节,一共有512*2=1024个byte,也就是1k
        char[] chars = new char[512];         int readCount = 0;
        while ((readCount = bufferedReader.read(chars)) != -1) {
            System.out.println(new String(chars, 0, readCount));
        }


        //TODO 7.关闭流对象和socket对象,避免资源浪费
        bufferedWriter.close(); //关闭外层流即可,先打开的流后关闭
        bufferedReader.close();
        socket.close();
        serverSocket.close();//多了一个这个

        System.out.println("客户端退出了");
    }
}

image-20231124154603874

2.3 网络文件上传

2.3.1 网络文件上传案例

案例需求

  • 编写一个服务端,一个客户端
  • 服务端在9999端口监听
  • 客户端连接到服务端,发送一张图片
  • 服务器端接收到客户端发送的图片保存到src下,发送“收到图片”再退出
  • 客户端接收到服务端发送的“收到图片”,再退出

简单来说是将客户端的图片,通过网络拷贝到服务器,并且服务器要给客户端回复一条消息

说明:使用BufferedInputStream和BufferedOutputStream字节流

示意图

image-20231124172438082

服务端

/**
 * 文件上传的服务端
 */
public class TCPFileUploadServer {
    public static void main(String[] args) throws IOException {

        //TODO 1.服务端在本机监听8888端口
        ServerSocket serverSocket = new ServerSocket(8888);
        System.out.println("服务端在8888端口监听....");

        //TODO 2.等待连接
        Socket socket = serverSocket.accept();

        //TODO 3.读取客户端发送的数据
        //通过socket得到输入流
        BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
        //读取客户端传输过来的二进制数据数组
        byte[] fileBytesArray = StreamUtils.streamToByteArray(bis);

        //TODO 4.将得到的bytes数组写入到指定的路径,就会得到一个文件
        String filePath = "src/qie2.jpg";
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath));
        bos.write(fileBytesArray);
        bos.flush();


        //TODO 5.服务端向客户端发送“收到图片”再退出
        //使用字符缓冲流BufferedWriter,OutputStreamWriter是转换流,将字节流转换成字符流
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        bw.write("收到图片");

        bw.flush();
        //写出结束标记
        socket.shutdownOutput();

        //TODO 6.关闭流
        bw.close();
        bos.close();
        bis.close();
        socket.close();
        serverSocket.close();
    }
}

image-20231129153931318

客户端

/**
 * 文件上传的客户端
 */
public class TCPFileUploadClient {
    public static void main(String[] args) throws IOException {

        //TODO 1.客户端连接服务端8888,得到Socket对象
        Socket socket = new Socket(InetAddress.getLocalHost(), 8888);

        //TODO 2.把磁盘上的图片读取到文件字节数组中
        //2.1 创建读取磁盘文件的输入流(字节流)
        String filePath = "C:\\Users\\jd\\Desktop\\jpg1.jpg";
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath));

        //fileBytesArray数组就是filePath所对应的字节数组
        byte[] fileBytesArray = StreamUtils.streamToByteArray(bis);


        //TODO 3.利用IO流将字节数组放入Socket通道中,使服务端可以读取到(将二进制数组数据发送给客户端)
        //借助socket获取字节流
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
        // byte[] byteArray = new byte[1024];//一次性读取1024个byte,也就是1k,但是这里并不需要,直接写字节数组fileBytesArray即可
        bos.write(fileBytesArray);
        bos.flush();
        //设置写入数据的结束标记
        socket.shutdownOutput();

        //TODO 4.读取客户端返回过来的数据
        BufferedReader bw = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        char[] charArray = new char[512];  //一次性读取512个字符,一个字符是两个字节,也就是512*2=1024字节,1k
        int len = 0;
        StringBuilder stringBuilder = new StringBuilder();
        while ( (len = bw.read(charArray))!=-1){
            stringBuilder.append(new String(charArray, 0, len));
        }
        System.out.println(stringBuilder);

        //或者使用工具类中的方法,都是可以的
//        String resultFromServer = StreamUtils.streamToString(socket.getInputStream());
//        System.out.println(resultFromServer);

        //TODO 关闭流
        bw.close();
        bis.close();
        bos.close();
        socket.close();

    }
}

image-20231129153909485

工具类

/**
 * IO流工具类
 */
public class StreamUtils {

    /**
     * 将输入流转换成byte[]
     */
    public static byte[] streamToByteArray(InputStream is) throws IOException {
        //1.创建输出流对象
        ByteArrayOutputStream bos = new ByteArrayOutputStream();

        //2.创建字节数组,一次可以读取1024个字节,也就是1k
        byte[] b = new byte[1024];
        int len;
        //每次最多读取b单位长度的数据
        while ((len = is.read(b)) != -1) {
            //把读取到的数据写入bos流
            bos.write(b, 0, len);
        }
        bos.flush();
        //3.循环读取,此时array就是我们读取的文件的所有二进制的内容(将文件一次性转成二进制)
        byte[] array = bos.toByteArray();
        bos.close();

        //4.返回文件的二进制形式
        return array;
    }

    /**
     * 将输入流的数据直接转换成一个字符串
     * InputStream字节流
     */
    public static String streamToString(InputStream is) throws IOException {
        //BufferedReader字符缓冲流
        //InputStreamReader 转换流,可以将字节流转换成字符流
        //InputStream字节流
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        StringBuilder builder = new StringBuilder();
        String line;
        //一次性读一行
        while ( (line=reader.readLine())!=null){
            builder.append(line+"\r\n");
        }
        reader.close();
        return builder.toString();
    }

}

2.4 netstat 指令

可以查看协议、本地地址、外部地址(有没有外部的连接连到本地)、状态(LISTENING正在监听、ESTABLISHED连接上了)

  • netstat -an 可以查看当前主机网络情况,包括端口监听情况和网络连接情况

使用netstat也可以查看当前主机网络情况,只不过查询到的信息不够全

netstat

image-20231129152559537

netstat -an

image-20231129152804538

  • netstat -an | more 可以分页显示

    并且左下角会有一个光标在不停的闪烁,如果想看下一页的内容,在此光标处点击空格键即可

image-20231129153000873

  • 要求在dos控制台下执行

  • 说明

    • Listening:表示某个端口在监听
    • 如果有一个外部程序(客户端)连接到该端口,就会显示一条连接信息

举例说明

客户端

Socket socket = new Socket(InetAddress.getLocalHost(), 8888);

服务端

服务端监听本机8888端口

ServerSocket serverSocket = new ServerSocket(8888);

目前有一个TCP协议的方式,有一个程序在本机的8888端口处于监听的状态(此时还没有连接来连到客户端)

image-20231129154117671

假如客户端和服务端连接成功后,状态会变为“ESTABLISHED”

2.5 TCP网络通信编程说明

  • 当客户端连接到服务端后,实际上客户端也是通过一个端口和服务端进行通讯的,这个端口是随机的,由TCP/IP来分配的

当我们的客户端和服务端连接成功之后,客户端的socket数据通道也会有一个端口,并且端口号是由TCP/IP协议随机分配的

也就是说客户端会有一个端口对应服务端进行通信

image-20231129172102712

验证

借助netstat命令

使用网络文件上传案例中的代码,将上传的文件由图片改成大一点的视频

服务端会监听8888端口

首先启动服务端,如下所示,并且没有外部地址对应本地8888端口

也就是说8888端口已经在监听,但是没有客户端连接过来

image-20231129172758408

下面启动客户端开始向服务端发送视频

也就是说我们服务端的端口是60285,然后客户端的192.168.12.1:60285与服务端192.168.12.1:8888建立了一个数据通道(或者说是一个网络连接)

当在服务端输入netstat时才会出现下面标红的数据

image-20231129173152288

当在客户端输入netstat命令时会出现上图表中数据的下一条数据

上面标红的数据紧挨这一条数据的原因:

因为客户端和服务端都是在本地启动的原因,所以也会显示本地的60285端口与外部地址的8888建立了连接

当我们客户端与服务端的传输结束之后,客户端的60285端口就会被释放

总结:客户端也是通过一个端口来通讯的,但是客户端端口是不确定的,而服务端的端口是固定的

三、UDP网络通信编程(了解)

在实际开发中使用量比较少,大体知道怎么开发即可

  • DatagramSocket(叫数据报套接字)类和DatagramPacket(叫数据报/数据包)类实现了基于UDP协议网络程序

    两个核心对象类

  • UDP数据报通过数据报套接字DatagramSocket发送和接受,系统不保证UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达

  • DatagramPacket对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号以及接口端的IP地址和端口号

  • UDP协议中每个数据报都给出了完整的地址信息,因此无需建立发送方和接收方的连接

3.1 基本流程

  1. 核心对象DatagramSocket与DatagramPacket

  2. 建立发送端、接收端(现在没有服务端和客户端这个概念)

  3. 发送数据前,建立数据包/报 DatagramPacket

    其实从下面DatagramPacket的构造方法中就可以看出来哪些是接收端,哪些是服务端,在案例中会进行说明

    image-20231129221530704

  4. 调用DatagramSocket的发送、接收方法

  5. 关闭DatagramSocket

image-20231129221212854

3.2 案例

3.2.1 应用案例 1

需求说明

  • 编写一个接收端A和一个发送端B
  • 接收端A在9999端口等待接收数据receive
  • 发送端B向接收端A发送数据“hello,明天吃火锅~”
  • 接收端A接收到发送端B发送的数据,回复”好的,明天见“,再退出
  • 发送端接收到回复的数据再退出

image-20231129225722127

发送端

/**
 * UDP发送端
 */
public class UDPSenderB {
    public static void main(String[] args) throws IOException {
       //TODO 1.创建DatagramSocket对象,准备发送和接受数据
        DatagramSocket socket = new DatagramSocket(9998);

        //TODO 2.将需要发送的数据封装到DatagramPacket对象中
        byte[] bytes = "hello 明天吃火锅~".getBytes();
        //参数1:要发送的数据
        //参数2、3:发送哪一段的数据,0-bytes.length就是发送数组的全部数据
        //参数4:主机,客户端和接收端不在一台电脑,InetAddress.getByName(IP)即可
        //参数5:端口
        DatagramPacket packet = new DatagramPacket(bytes,0,bytes.length,InetAddress.getByName("127.0.0.1"),9999);

        //TODO 3.发送数据
        socket.send(packet);

        //TODO 4.接收 接收端 回复的消息
        byte[] buf = new byte[1024*64];
        DatagramPacket packetReturn = new DatagramPacket(buf,buf.length);
        System.out.println("发送端B 等待接收数据....");
        socket.receive(packetReturn);
        int length = packetReturn.getLength();
        //实际接收到的数据
        byte[] data = packetReturn.getData();
        String s = new String(data, 0, length);
        System.out.println(s);

        //TODO 关闭资源
        socket.close();
    }
}

接收端

/**
 * UDP接收端
 */
public class UDPReceiverA {
    public static void main(String[] args) throws IOException {
        //TODO 1.创建一个DatagramSocket对象,准备在9999接收数据和发送数据
        DatagramSocket socket = new DatagramSocket(9999);
        //TODO 2.构建DatagramPacket对象,准备接收数据
        //UDP协议最大的包64k,不适合传输大量的数据
        byte[] buf = new byte[1024*64];
        //参数1:存储将要发送或接收的数据
        //参数2:指定要发送或接收的数据的长度
        //此时packet对象是空的,是什么也没有的
        DatagramPacket packet = new DatagramPacket(buf,buf.length);

        //TODO 3.调用接收方法接收数据,将通过网络传输的DatagramPacket对象填充到packet对象里,此时packet对象便不是空对象了,是有数据的
        //此方法表示会在9999端口等待,如果有一个数据报发送到了9999端口就会接收,没有发送到9999端口就会在此地方堵塞
        System.out.println("接收端A 等待接收数据....");
        socket.receive(packet);

        //TODO 4.对packet拆包,取出数据并显示
        //实际接收到的数据的长度,我们的byte数组大小是1024*64,但是不一定会有这么多的数据传输过来
        int length = packet.getLength();
        //实际接收到的数据
        byte[] data = packet.getData();
        String s = new String(data, 0, length);
        System.out.println(s);


        //TODO 5.给发送端回复消息
        byte[] bytes = "好的,明天见".getBytes();
        DatagramPacket packetReturn = new DatagramPacket(bytes,0,bytes.length, InetAddress.getByName("127.0.0.1"),9998);
        socket.send(packetReturn);

        //TODO 6.关闭资源
        socket.close();


    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/204159.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

防火墙之iptables

iptables概述 1.Linux 系统的防火墙 :IP信息包过滤系统,它实际上由两个组件netfilter 和 iptables组成。 2.主要工作在网络层,针对IP数据包。体现在对包内的IP地址、端口、协议等信息的处理上。 -netfilter/iptables关系: netfil…

docker-compose;私有镜像仓库harbor搭建;镜像推送到私有仓库harbor

docker-compose;私有镜像仓库harbor搭建;镜像推送到私有仓库harbor 文章目录 docker-compose;私有镜像仓库harbor搭建;镜像推送到私有仓库harbordocker-compose私有镜像仓库harbor搭建镜像推送到私有仓库harbor docker-compose D…

IC卡卡号,UID卡CUID卡物理卡号修改

普通M1卡的卡号是锁死的,不能修改,考勤工作证等使用的就是IC卡的物理卡号,读卡器读取后转换成10进制输出,出厂就固化了,因此用户拿到手的卡片卡号是不连续的,也是没有规律的,比如物理卡号8602A2…

Appium+python+unittest搭建UI自动化框架

阅读本小节,需要读者具备如下前提条件: 1. 掌握一种编程语言基础,如java、python等。 2. 掌握一种单元测试框架,如java语言的testng框架、python的unittest框架。 3. 掌握目前主流的UI测试框架,移动端APP测试框架…

【路径规划】move_base、路径规划算法、局部避障算法介绍

资料整理供个人学习使用。 文章目录 一、move_base1、move_base 包内容2、move_base 参数解析1. move_base 参数2. 全局代价地图参数3. 局部代价地图参数4. 全局规划器参数5. 局部规划器参数 二、路径规划1、Dijkstra2、最佳优先搜索3、A*4、A* 和 Dijkstra 比较 三、局部避障1…

基于SSM搭建系统

原理 SSM集成 SpringSpringMvcMybatis集成 框架集成核心,如果你的项目中,用到了Spring框架,那么其他框架主要就是和Spring集成; 和Spring集成的核心思路: 把当前框架的核心类,交给Spring管理&#xff08…

WP采集插件的进阶功能:输入关键词采集及定向采集实现精准筛选

WP采集插件教程:轻松实现全网文章采集 近年来,WordPress(简称WP)作为一款强大的网站建设工具,广受用户喜爱。然而,对于许多网站管理员来说,如何轻松而高效地获取全网各类文章内容成为了一个亟待…

AutoCAD 2024 中文

AutoCAD 2024是一款全球知名的计算机辅助设计软件,由Autodesk公司开发。它提供了丰富的绘图功能和工具,可以满足不同领域的需求,支持2D和3D绘图设计,包括平面图、立体图、剖面图等等。此外,AutoCAD 2024具备强大的数据…

明基|书客|松下护眼台灯值不值得买?爆款多维度测评揭晓!

随着近视率越来越高的现象,护眼台灯逐渐成为大多数家庭的日常所需,许多护眼台灯品牌为了降低价格吸引消费者而不惜大程度上降低材料品质,导致台灯寿命减短,光线变差等问题频发,这也让广大对于如何选择一款好的护眼台灯…

QListWidget中自定义widget大小自适应

背景: QListWidget中的item,可以添加自定义的widget。 但是怎么去调整widget的大小呢? 参考:QT QListWidget的添加与删除,滚动条显示或隐藏,判断是否滑到顶部或底部,并使QListWidgetItem自适…

vue3 input 上传 vuedraggable 实现拖拽排序

效果如下 input 实现上传功能 通过隐藏 input 元素&#xff0c;点击上传触发 input 点击事件&#xff0c;监听 input change 事件 accept 上传文件的类型 multiple 是否允许上传多个 <template><div class"cursor-pointer" click"submitUpload"&…

零代码集成自动化的实现逻辑是什么?

零代码的概念是什么&#xff1f; 零代码平台是一种软件开发工具或平台&#xff0c;非技术人员能够创建和部署应用程序&#xff0c;而无需编写任何代码。它提供了可视化的界面和拖拽式的操作&#xff0c;使用户能够通过简单的配置和组合&#xff0c;以图形化的方式构建应用程序…

vue2常见的语法糖

Vue.js 2 提供了一些语法糖&#xff08;syntactic sugar&#xff09;来简化常见的操作。以下是一些 Vue.js 2 中常用的语法糖&#xff1a; v-bind 简写&#xff1a; <!-- 完整语法 --> <a v-bind:href"url">Link</a><!-- 简写 --> <a :hr…

Jmeter进阶使用:BeanShell实现接口前置和后置操作!

一、背景 我们使用Jmeter做压力测试或者接口测试时&#xff0c;除了最简单的直接对接口发起请求&#xff0c;很多时候需要对接口进行一些前置操作&#xff1a;比如提前生成测试数据&#xff0c;以及一些后置操作&#xff1a;比如提取接口响应内容中的某个字段的值。举个最常用…

nexus制品库的介绍及详细部署使用

一、nexus 介绍 Nexus 是一个强大的仓库管理工具&#xff0c;用于管理和分发 Maven、npm、Docker 等软件包。它提供了一个集中的存储库&#xff0c;用于存储和管理软件包&#xff0c;并提供了版本控制、访问控制、构建和部署等功能。 Nexus 可以帮助开发团队提高软件包管理的效…

构建强大的接口自动化测试框架:Pytest实践指南!

一. 背景 Pytest目前已经成为Python系自动化测试必学必备的一个框架&#xff0c;网上也有很多的文章讲述相关的知识。最近自己也抽时间梳理了一份pytest接口自动化测试框架&#xff0c;因此准备写文章记录一下&#xff0c;做到尽量简单通俗易懂&#xff0c;当然前提是基本的py…

DevEco Studio设置每次进入 是否自动进入上一次的项目

首先 我们第一次创建项目 并不是这个界面 如果我们想在这个界面创建项目的话 可以 点击左上角 File 下的 New 下的 Create Project 这里 我们可以点击左上角 File 选择下面的 Settings… 这个界面就有非常多的配置 然后 我们选择到下图操作的位置 这里有一个Reopen projects…

适用于 Windows 和 Mac 电脑的最佳数据恢复软件

当我们的电脑上的文件被错误删除时&#xff0c;总是很难恢复该文件&#xff0c;或者除非您进行了系统还原&#xff0c;否则一切都会恢复到删除恢复的文件或文件夹之前的状态。 拥有合适的 PC 软件始终可以帮助您改善 PC 用户的体验&#xff0c;而适用于 Windows 10 和 11 的良…

SQL 金额数值转换成中文大写

需求&#xff1a;将金额转换成中文大写格式填入单据合计行&#xff1a; _佰_拾_万_仟_佰_拾_元_角_分 1234567.89 壹佰贰拾叁万肆仟伍佰陆拾柒元捌角玖分 1.函数转换 drop function n2C;CREATE FUNCTION n2C (num numeric(14,2)) RETURNS VARCHAR(20) AS BEGIN …

linux NAT网卡配置static

由于是内网&#xff0c;资料无法拷贝&#xff0c;借助参考资料&#xff0c;整理发出。 镜像安装 基本操作。 查看VM配置 图1&#xff0c;有几个信息。一个是NAT借用了网卡里的VMnet8适配器。 子网IP是从192.168.142.0 子网掩码255.255.255.255&#xff0c;对应下面配置的N…