• 网络编程


    1 网络编程入门

    1.1 概述

    • 计算机网络
      是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统
    • 网络编程
      在网络通信协议下,不同计算机上运行的程序,可以进行数据传输

    1.2 网络编程三要素

    • IP地址
      计算机的标识号
    • 端口
      应用程序的标识号
    • 协议
      规则

    1.3 IP地址

    IP地址:是网络中设备的唯一标识

    • IP地址分为两大类
      • IPV4:是给每个连接在网络上的主机分配一个32bit地址。按照TCP/IP规定,IP地址用二进制来表示,每个IP地址长32bit,也就是4个字节。例如一个采用二进制形式的IP地址是“11000000 10101000 00000001 01000010”,这么长的地址,处理起来也太费劲了。为了方便使用,IP地址经常被写成十进制的形式,中间使用符号“.”分隔不同的字节。于是,上面的IP地址可以表示为“192.168.1.66”。IP地址的这种表示法叫做“点分十进制表示法”,这显然比1和0容易记忆得多
      • IPV6:是给每个连接在网络上的主机分配一个32bit地址。按照TCP/IP规定,IP地址用二进制来表示,每个IP地址长32bit,也就是4个字节。例如一个采用二进制形式的IP地址是“11000000 10101000 00000001 01000010”,这么长的地址,处理起来也太费劲了。为了方便使用,IP地址经常被写成十进制的形式,中间使用符号“.”分隔不同的字节。于是,上面的IP地址可以表示为“192.168.1.66”。IP地址的这种表示法叫做“点分十进制表示法”,这显然比1和0容易记忆得多
    • DOS常用命令:
      • ipconfig:查看本机IP地址
      • ping IP地址:检查网络是否连通
    • 特殊IP地址:
      • 127.0.0.1:是回送地址,可以代表本机地址,一般用来测试使用

    1.4 InetAddress

    InetAddress:此类表示Internet协议(IP)地址

    • 相关方法
    方法名说明
    static InetAddress getByName(String host)确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址
    String getHostName()获取此IP地址的主机名
    String getHostAddress()返回文本显示中的IP地址字符串
    • 代码演示
    public class Demo {
        public static void main(String[] args) throws UnknownHostException {
            InetAddress address = InetAddress.getByName("LAPTOP-VB1NTR4K");
    
            String name = address.getHostName();
    
            String ip = address.getHostAddress();
    
            System.out.println("主机名:" + name);
            System.out.println("IP地址:" + ip);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 控制台展示
    • 在这里插入图片描述

    1.5 端口和协议

    • 端口
      • 设备上应用程序的唯一标识
    • 端口号
      • 用两个字节表示的整数,范围在0 ~ 65535。0 ~ 1023用在一些知名的网络服务和应用,1024以上用于普通程序
    • 协议
      • 连接和通信的规则
    • UDP协议
      • 用户数据报协议(User Datagram Protocol)
      • 无连接通信协议
      • 消耗系统资源小,通信效率高,通常用于音频、视频和普通数据的传输
      • UPD传输数据时,不能保证数据完整性,所以传输重要文件,不建议使用UDP协议
        在这里插入图片描述
    • TCP协议
      • 传输控制协议(Transmission Control Protocol)
      • TCP是面向连接的通信协议,每次连接的创建都需要经过 “三次握手”
      • 三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠
        第一次握手,客户端向服务器端发出连接请求,等待服务器确认
        第二次握手,服务器向客户端回送一个响应,通知客户端收到了连接请求
        第三次握手,客户端再次向服务器发送确认信息,确认连接
      • 完成三次握手,连接建立,客户端和服务器就可以开始进行数据传输。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛。例如上传文件、下载文件、浏览网页

    2 UDP通信程序

    2.1 UDP发送数据

    • Java中的UDP通信
      • UDP协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket对象,但是这两个Socket只是发送,接收数据的对象,因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念
      • Java提供了DatagramSocket类作为基于UDP协议的Socket
    • 构造方法
    方法名说明
    DatagramSocket()创建数据报套接字并将其绑定到本机地址上的任何可用端口
    DatagramPacket(byte[ ] bys,int len,InetAddress add,int port)创建数据包,发送长度为len的数据包到指定主机的指定端口
    • 相关方法
    方法名说明
    void send(DatagramPacket p)发送数据报包
    void close()关闭数据报套接字
    void receive(DatagramPacket p)关闭数据报套接字
    • 发送数据的步骤
        • 创建发送端的Socket对象(DatagramSocket)
      • 创建数据,并把数据打包
      • 调用DatagramSocket对象的方法发送数据
      • 关闭发送端
    • 代码演示
    public class SendDemo {
        public static void main(String[] args) throws IOException {
            //创建发送端的Socket对象(DatagramSocket)
            // DatagramSocket() 构造数据报套接字并将其绑定到本地主机上的任何可用端口
            DatagramSocket ds = new DatagramSocket();
    
            //创建数据,并把数据打包
            //DatagramPacket(byte[] buf, int length, InetAddress address, int port)
            //构造一个数据包,发送长度为 length的数据包到指定主机上的指定端口号。
            byte[] bys = "hello,udp,我来了".getBytes();
    
            DatagramPacket dp = new DatagramPacket(bys,bys.length,InetAddress.getByName("127.0.0.1"),10086);
    
            //调用DatagramSocket对象的方法发送数据
            //void send(DatagramPacket p) 从此套接字发送数据报包
            ds.send(dp);
    
            //关闭发送端
            //void close() 关闭此数据报套接字
            ds.close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    2.2 UDP 接收数据

    • 接收数据步骤
      • 创建接收端的Socket对象(DatagramSocket)
      • 创建一个数据包,用于接收数据
      • 调用DatagramSocket对象的方法接收数据
      • 解析数据包,并把数据在控制台显示
      • 关闭接收端
    • 构造方法
    方法名说明
    DatagramPacket(byte[] buf, int len)创建一个DatagramPacket用于接收长度为len的数据包
    • 相关方法
    方法名说明
    byte[] getData()返回数据缓冲区
    int getLength()返回要发送的数据的长度或接收的数据的长度
    • 代码演示
    public class ReceiveDemo {
        public static void main(String[] args) throws IOException {
          	//创建接收端的Socket对象(DatagramSocket)
          	DatagramSocket ds = new DatagramSocket(12345);
    
          	//创建一个数据包,用于接收数据
          	byte[] bys = new byte[1024];
          	DatagramPacket dp = new DatagramPacket(bys, bys.length);
    
          	//调用DatagramSocket对象的方法接收数据
          	ds.receive(dp);
    
          	//解析数据包,并把数据在控制台显示
          	System.out.println("数据是:" + new String(dp.getData(), 0,                                             dp.getLength()));
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2.3 UDP三种通讯方式

    • 单播
      单播用于两个主机之间的端与端通信
    • 组播
      组播用于对一组特定的主机进行通信
    • 广播
      广播用于一个主机对整个局域网上所有主机上的数据通信

    3 TCP通信程序

    3.1 TCP发送数据

    • Java中的TCP通信
      • Java对基于TCP协议提供了良好的封装,使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信
      • Java为客户端提供了Socket类,为服务器端提供了ServerSocket类
    • 构造方法
    方法名说明
    Socket(InetAddress address,int port)创建流套接字并将其连接到指定IP指定端口号
    Socket(String host, int port)创建流套接字并将其连接到指定主机上的指定端口号
    • 相关方法
    方法名说明
    InputStream getInputStream()返回此套接字的输入流
    OutputStream getOutputStream()返回此套接字的输出流
    • 示例代码
    public class ClientDemo {
        public static void main(String[] args) throws IOException {
            //创建客户端的Socket对象(Socket)
            //Socket(String host, int port) 创建流套接字并将其连接到指定主机上的指定端口号
            Socket s = new Socket("127.0.0.1",10000);
    
            //获取输出流,写数据
            //OutputStream getOutputStream() 返回此套接字的输出流
            OutputStream os = s.getOutputStream();
            os.write("hello,tcp,我来了".getBytes());
    
            //释放资源
            s.close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3.2 TCP接收数据

    • 构造方法
    方法名说明
    ServletSocket(int port)创建绑定到指定端口的服务器套接字
    • 相关方法
    方法名说明
    Socket accept()监听要连接到此的套接字并接受它

    注意事项

    1. accept方法是阻塞的,作用就是等待客户端连接
    2. 客户端创建对象并连接服务器,此时是通过三次握手协议,保证跟服务器之间的连接
    3. 针对客户端来讲,是往外写的,所以是输出流
      针对服务器来讲,是往里读的,所以是输入流
    4. read方法也是阻塞的
    5. 客户端在关流的时候,还多了一个往服务器写结束标记的动作
    6. 最后一步断开连接,通过四次挥手协议保证连接终止
    • 三次握手和四次挥手
      • 三次握手
        在这里插入图片描述
      • 四次握手
        在这里插入图片描述
    • 示例代码
    public class ServerDemo {
        public static void main(String[] args) throws IOException {
            //创建服务器端的Socket对象(ServerSocket)
            //ServerSocket(int port) 创建绑定到指定端口的服务器套接字
            ServerSocket ss = new ServerSocket(10000);
    
            //Socket accept() 侦听要连接到此套接字并接受它
            Socket s = ss.accept();
    
            //获取输入流,读数据,并把数据显示在控制台
            InputStream is = s.getInputStream();
            byte[] bys = new byte[1024];
            int len = is.read(bys);
            String data = new String(bys,0,len);
            System.out.println("数据是:" + data);
    
            //释放资源
            s.close();
            ss.close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    4 NIO

    4.1 概述

    • BIO
      Blocking IO,阻塞型IO
    • NIO
      No Blocking IO,非阻塞型IO
    • 阻塞IO的弊端
      在等待的过程中,什么事也做不了
    • 非阻塞IO的好处
      不需要一直等待,当一切就绪了再去做

    4.2 NIO与BIO的区别

    • 区别一
      BIO是阻塞的,NIO是非阻塞的
    • 区别二
      BIO是面向流的,NIO是面型缓冲区的
      BIO中数据传输是单向的,NIO中的缓冲区是双向的

    NIO中的三大模块

    • 缓冲区
      用来存储数据
    • 通道
      用来建立连接和传输数据
    • 选择器
      监事通道状态
      在这里插入图片描述

    NIO创建缓冲区对象

    • 方法介绍
    方法名说明
    static ByteBuffer allocate(长度)创建byte类型的缓冲区
    static ByteBuffer wrap(byte[] array)创建一个有内容的byte类型缓冲区
    public class Demo1 {
        public static void main(String[] args) {
    //        method1();
    
    //        method2();
    
            ByteBuffer wrap = ByteBuffer.wrap("aaa".getBytes());
            for (int i = 0; i < 3; i++) {
                System.out.println(wrap.get());
            }
        }
    
        private static void method2() {
            byte [] bytes = {97,98,99};
            ByteBuffer byteBuffer2 = ByteBuffer.wrap(bytes);
            //缓冲区的长度3
            //缓冲区里面的内容就是字节数组的内容.
            for (int i = 0; i < 3; i++) {
                System.out.println(byteBuffer2.get());
            }
            System.out.println(byteBuffer2.get());
        }
    
        private static void method1() {
            ByteBuffer byteBuffer1 = ByteBuffer.allocate(5);
            //get
            for (int i = 0; i < 5; i++) {
                System.out.println(byteBuffer1.get());
            }
            System.out.println(byteBuffer1.get());
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    4.5 NIO缓冲区添加数据

    在这里插入图片描述

    public class ByteBufferDemo2 {
        public static void main(String[] args) {
    //        int position()		  当前要操作的索引
    //        int limit() 		  最多能操作到哪个索引
    //        int capacity()		  缓冲区的总长度
            ByteBuffer byteBuffer = ByteBuffer.allocate(10);
            System.out.println(byteBuffer.position());//0
            System.out.println(byteBuffer.limit());//10
            System.out.println(byteBuffer.capacity());//10
    
    //        put(byte b)		  一次添加一个字节
    //        byteBuffer.put((byte) 97);
    //        System.out.println(byteBuffer.position());
    //        System.out.println(byteBuffer.limit());
    //        System.out.println(byteBuffer.capacity());
    
    //        put(byte[] src)		 一次添加一个字节数组
    //        byteBuffer.put("aaa".getBytes());
    //        System.out.println(byteBuffer.position());//3
    //        System.out.println(byteBuffer.limit());//10
    //        System.out.println(byteBuffer.capacity());//10
    
    //        position(int newPosition) 修改position
    //        byteBuffer.position(1);
    
    //        limit(int newLimit)	  修改limit
    //        byteBuffer.limit(5);
    //        System.out.println(byteBuffer.position());
    //        System.out.println(byteBuffer.limit());
    //        System.out.println(byteBuffer.capacity());
    
    //        int remaining()		  还有多少能操作
    //        boolean hasRemaining()	  是否还有能操作的
    
            byteBuffer.put("0123456789".getBytes());
            System.out.println(byteBuffer.remaining());
            System.out.println(byteBuffer.hasRemaining());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    4.6 NIO缓冲区获取数据

    • 方法介绍
      在这里插入图片描述
    • 代码示例
    public class ByteBufferDemo3 {
        public static void main(String[] args) {
            ByteBuffer byteBuffer = ByteBuffer.allocate(10);
            byteBuffer.put("abc".getBytes());
    
    //        flip()  切换读写模式(写读)
            byteBuffer.flip();
    //        get()   读一个字节
    //        while(byteBuffer.limit() != byteBuffer.position()){
    //            System.out.println((char) byteBuffer.get());
    //        }
    
            for (int i = 0; i < byteBuffer.limit(); i++) {
                System.out.println((char) byteBuffer.get());
            }
    
    //        get(byte[] dst) 读多个字节
    //        byte [] bytes = new byte[byteBuffer.limit()];
    //        byteBuffer.get(bytes);
    //        System.out.println(new String(bytes));
    
    //        get(int index)  读指定索引的字节
    //        System.out.println((char) byteBuffer.get(0));
    
    //        rewind()    将position设置为0,可以重复读
    //        byteBuffer.rewind();
    //        for (int i = 0; i < byteBuffer.limit(); i++) {
    //            System.out.println((char) byteBuffer.get());
    //        }
    
    //        clear()     数据读写完毕(读->写)
            byteBuffer.clear();
            byteBuffer.put("qqq".getBytes());
    //        array()     将缓冲区转换成字节数组返回
    
            byte[] bytes = byteBuffer.array();
            System.out.println(new String(bytes));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    4.7 小结

    1. 需求:我要把数据写到缓冲区中。
      数据是从外面进入到缓冲区的,所以缓冲区在做读数据的操作。
    2. 需求:我要把数据从缓冲区中读出来。
      数据是从缓冲区里面到外面的。所以缓冲区在做写数据的操作。
    3. capacity:容量(长度)
      limit: 界限(最多能读/写到哪里)
      posotion:位置(读/写哪个索引)
    4. 获取缓冲区里面数据之前,需要调用flip方法
    5. 再次写数据之前,需要调用clear方法,
      但是数据还未消失,等再次写入数据,被覆盖了才会消失。
  • 相关阅读:
    ARouter原理解析之自定义路由框架DXRouter
    技术干货|昇思MindSpore 1.5版本中的亲和算法库——MindSpore Boost
    提升市场调研和竞品分析效率:利用Appium实现App数据爬取
    vRealize Operations Manager 安全补丁修复
    JavaScript - 好玩的打字动画
    “当下的力量”9月读书笔记
    CommunityToolkit.Mvvm8.1 viewmodel源生成器写法(3)
    代码随想录第五十六天
    二叉树的基本性质与遍历
    【嵌入式】嵌入式开发为什么要跑操作系统?
  • 原文地址:https://blog.csdn.net/m0_59620032/article/details/127528172