【JAVA入门】Day49 - 网络编程

【JAVA入门】Day49 - 网络编程



        网络编程就是在网络通信协议下,不同计算机上运行的程序,进行的数据传输。
        Java 中可以使用 java.net 包下的技术轻松开发出常见的网络应用程序。要开发网络应用程序,我们需要先了解计算机常见的软件架构。
        计算机软件常见的软件架构是 C/S 和 B/S 架构。

  • C/S:Client/Server 客户端/服务器。这种架构需要用户在本地下载并安装客户端应用程序,在远程有一个服务端程序,它们共同工作。比如QQ、Wechat。
  • B/S:Browser/Server 浏览器/服务器。这种架构只需要一个浏览器,用户通过不同的网址就能访问不同的服务器。比如JD.com、Taobao.com。

        B/S 架构的优点是我们不需要开发客户端,只需要页面 + 服务端即可,用户不需要下载,打开浏览器就能用。但是如果应用过大,用户体验就会受到影响。
        C/S 架构的优点是软件的资源大部分存储在用户本地,画面可以做得很精美,用户体验好。但是 C/S 架构既要开发客户端,也要开发服务端。 服务端更新后,用户也要随之更新客户端。

一、网络编程三要素

        在网络编程过程中,我们需要知道三件事情,这三件事情是非常重要的。

  • 其一是确定对方电脑在互联网上的地址,这个地址一般被我们称作IP
  • 其二是确定对方电脑上接收数据的软件,这个软件一般是绑定一个端口号使用。
  • 其三是确定网络传输的规则,我们用户之间通过网络传递数据不能随便传输,而应该遵循一定的协议

        因此网络编程的三要素是:IP、端口号、协议。三者缺一不可。

  • IP:设备在网络中的地址,是唯一的标识。
  • 端口号:应用程序在设备中唯一的标识。
  • 协议:数据在网络中传输的规则,常见的协议有 UDP、TCP、http、https、FTP。

1.1 IP

        IP 的全称是 Internet Protocol,是互联网协议地址,也称 IP 地址,它是分配给上网设备的数字标签。通俗地说,它是上网设备在网络中的地址,是唯一的。
        常见的 IP 地址有两种,一种是 IPv4 ,另一种是 IPv6。

1.1.1 IPv4

        IPv4 全称是 Internet Protocol version 4。它是互联网通信协议的第四版。
        采用32位地址长度,分为四组(一组8bit)。32位,也就是32bit,一共4字节。
        由于它这 32bit 十分不好记,所以我们8位分为一组,一共四组,每一组转成十进制,中间用 “.” 表示,这就是点分十进制表示法。要注意每一组之间的数字是有取值范围的:0 ~ 255。因此,IPv4 能表示的 IP 地址值其实是有上限的,大概是43亿个。
在这里插入图片描述
        为了解决 IPv4 数量不够用的问题,IPv6 出现了。

1.1.2 IPv6

        IPv6 全称 Internet Protocol version 6,互联网通信协议第六版。它是为了解决 IPv4 不够用的问题而诞生的。它的全长达到了 128bit ,且被分成了 8 组(一组16bit)。它的分组方式也和 IPv4 不同,它采用的是“冒分十六进制”法。就是将这八组的每一组用冒号分隔开,然后转成十六进制表示。一般我们会把每一组前面多余的0省略。如果计算出的十六进制表示形式中间有多个连续0,我们还会采用“0位压缩表示法”简略书写。

在这里插入图片描述

1.1.3 IPv4 的地址分类形式

        IPv4 的数量既然告急,那在 IPv6 诞生之前我们是如何解决这个问题的呢?这就要谈到 IPv4 的地址分类形式。
        IPv4 实际上分为公网地址(万维网使用)私有地址(局域网使用)
        在 IPv4 中,192.168 开头的就是局域网 IP,它是私有地址,范围是:192.168.0.0 ~ 192.168.255.255。专门为组织机构内部使用,以此节省 IP。一般在使用时,往往是多台设备共享同一个公网 IP,是由路由器给每一台设备分配一个局域网 IP。
        在众多 IP 中有一个非常特殊的地址,它就是 127.0.0.1,也可以写作 localhost: ,它是回送地址,也称作本地回环地址,也称本机IP,它永远只会寻找当前所在的本机。在本机向本机传收数据时,我们一般都是用这个 IP。

1.1.4 InetAddress 类

        在 Java 当中,用来表示 IP 的类是 InetAddress。它有两个子类:Inet4Address,Inet6Address。我们在创建 InetAddress 对象时,实际上是根据你当前系统使用的 IP 协议,生成相对应的子类对象。InetAddress 没有对外提供构造方法,我们生成对象需要使用这个方法:

static InetAddress getByName(String host)	在给定主机名的情况下确定主机的IP地址

        代码实现如下所示。

package InternetCode;

import java.net.InetAddress;
import java.net.UnknownHostException;

public class MyInetAddressDemo1 {
    /*
        InetAddress类
     */

    public static void main(String[] args) throws UnknownHostException {

        //1.获取InetAddress对象(IP对象)
        InetAddress address = InetAddress.getByName("172.17.81.27");
        System.out.println(address);

        //2.返回主机名
        String name = address.getHostName();
        System.out.println(name);       //172.17.81.27

        //3.返回IP
        String ip = address.getHostAddress();
        System.out.println(ip);
        
    }
}

1.2 端口号

        端口号是应用程序在设备中的唯一标识。
        端口号是由两个字节表示的整数,它的取值范围是:0 ~ 65535。
        其中 0 ~ 1023 之间的端口号用于一些知名的网络服务或者应用。如果我们自己要使用端口,就必须使用 1024 及以上的端口号。但是要注意的是,一个端口号只能被一个应用程序使用。
        端口号就好比两台电脑中软件交换信息的出入口。
在这里插入图片描述

1.3 协议

        计算机网络中,连接和通信的规则被称为网络通信协议。
        目前国际上的通信标准协议是 TCP/IP 协议。它的前身是 OSI 参考模型,这是一个七层的模型,由于太过复杂没有被广泛推广,而 TCP/IP 协议是它的简化。
在这里插入图片描述
        TCP/IP 协议分为四层:应用层、传输层、网络层、物理链路层。它们每一层都分别有自己的协议。
在这里插入图片描述
        在这些协议中,传输层的两个协议 —— UDP、TCP 尤为重要,需要我们知道。

1.3.1 UDP协议和TCP协议的对比

UDP 协议:

  • 用户数据报协议(User Datagram Protocol)。
  • UDP 是面向无连接通信协议。它的特点是速度快,但是有大小限制(一次最多发送64KB),数据不安全,容易丢失数据。
  • 所谓面向无连接,就是无论两台设备的网路是否连接成功,UDP 协议都会将数据发送出去,不管目标设备是否能收到。

TCP 协议:

  • 传输控制协议(Transmission Control Protocol)。
  • TCP 协议是面向连接的通信协议。它的特点是速度慢,没有大小限制,数据安全。
  • 面向连接就是确保两台设备的网络畅通,确保连接成功后,才会发送数据。

1.3.2 UDP 通信程序的编写

1.3.2.1 发送数据

        UDP 通信程序的编写可以分为发送数据和接收数据两部分。如果把 UDP 发送数据比作发快递,那么可以按照以下步骤施行。

在这里插入图片描述
        代码实现:

package UDPProgram;

import javax.xml.crypto.Data;
import java.io.IOException;
import java.net.*;

public class SendMessageDemo {
    public static void main(String[] args) throws IOException {
        //发送数据

        //1.创建DatagramSocket对象(快递公司)
        //细节:
        //绑定端口:通过这个端口往外发送数据
        //空参:所有可用端口中随机使用一个
        //有参:指定端口号进行绑定
        DatagramSocket ds = new DatagramSocket();

        //2.打包数据
        String str = "你好丫!!!!";
        byte[] bytes = str.getBytes();      //发送数据时要转为字节数组

        InetAddress address = InetAddress.getByName("127.0.0.1");       //要发送给哪台电脑(IP)

        int port = 10086;                  //发送给哪个端口

        DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);

        //3.发送数据
        ds.send(dp);

        //4.释放资源
        ds.close();

    }
}
1.3.2.2 接收数据

        如果把 UDP 数据的接收比作收快递,那么可以按照以下步骤执行:
在这里插入图片描述
        代码实现如下:

package UDPProgram;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;

public class ReceiveMessageDemo {
    public static void main(String[] args) throws IOException {
        //接收数据

        //1.创建DatagramSocket对象(快递公司)
        //细节:
        //在接收的时候,一定要绑定端口
        //绑定的端口一定要和发送端口一致
        DatagramSocket ds = new DatagramSocket(10086);

        //2.接收数据包
        //创建数组用来存储接收的数据
        byte[] bytes = new byte[1024];
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length);

        //该方法是阻塞的
        //程序在执行到这一步的时候,会在这里死等
        ds.receive(dp);

        //3.解析数据包
        byte[] data = dp.getData();                     //获取数据,获取到的就是传递过来的整个字节数组
        int len = dp.getLength();                       //获取了多少个字节?
        InetAddress address = dp.getAddress();          //发送者的IP地址
        int port = dp.getPort();                        //发送者是从哪个端口发送来的数据

        System.out.println("接收到数据" + new String(data,0,len));
        System.out.println("该数据是从" + address + "这台电脑中的" + port + "这个端口发出的。");
    }
}

        需要注意的是,在测试数据发送和接收时,要先运行接收程序,再运行发送程序。接收程序运行后,代码执行到 ds.receive() 这一行时,程序会在这里死等,直到接收到数据后,才会执行下面的代码。

1.3.2.3 收发数据的合并 —— 简易聊天室

        使用网络编程和 UDP 协议,可以实现简单的聊天室系统:
UDP 发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束。
UDP 接收数据:接收端不知发送端何时停止发送,采用死循环接收。
        发送端的代码编写如下:

package UDPProgram;

import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class SendMessageDemo1 {
    public static void main(String[] args) throws IOException {
        /*
            发送数据,数据来自键盘录入,直到输入886,发送数据结束
         */

        //1.创建对象DatagramSocket对象
        DatagramSocket ds = new DatagramSocket();

        //2.打包数据
        Scanner sc = new Scanner(System.in);
        System.out.println("------------------输入要说的话---------------");


        while (true) {
            String str = sc.nextLine();
            if(str.equals("886")){
                break;
            }
            byte[] bytes = str.getBytes();

            InetAddress address = InetAddress.getByName("127.0.0.1");
            int port = 10086;

            DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);

            //3.发送数据
            ds.send(dp);
        }

        //4.释放资源
        ds.close();

    }
}

        思路:将接收键盘输入和打包发送放入死循环执行,当键盘接收到"886"时,跳出循环。
        接收端代码实现如下:

package UDPProgram;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class ReceiveMessageDemo1 {
    public static void main(String[] args) throws IOException {
        /*
            接收端
         */

        //1.创建DatagramSocket对象,端口要和发送端一致
        DatagramSocket ds = new DatagramSocket(10086);

        //2.接收数据包
        byte[] bytes = new byte[1024];
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length);

        while (true) {
            ds.receive(dp);

            //3.解析数据包
            byte[] data = dp.getData();                             //数据内容
            int len = dp.getLength();                               //数据长度
            String ip = dp.getAddress().getHostAddress();           //发送IP
            String name = dp.getAddress().getHostName();            //发送主机名

            //4.打印数据
            System.out.println("ip为:" + ip + ",主机名为:" + name + "的人,发送给了数据:" + new String(data,0,len));
        }

    }
}

        思路:将接收语句和数据包解析语句写入循环,无限执行即可。

1.3.2 UDP 的三种通信方式

        UDP 的通信方式一般有三种如下三图所示:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
        之前我们写的聊天室收发数据就属于一对一的单播
        组播是一次往一组电脑中发送数据,组播地址有取值范围:224.0.0.0 - 239.255.255.255,其中 224.0.0.0 - 224.0.0.255 为预留的组播地址。
        广播是往局域网里所有的电脑都发送数据,广播的地址很简单只有一个:255.255.255.255。
        由此可见,UDP 发送数据的方式可以根据你发送的地址来区分,组播的代码可以这样写:
        发送端要创建的是 MulticastSocket 对象,也就是组播对象。且发送到的 IP 地址应该在 224.0.0.0 ~ 224.0.0.255 之间,这一区间的 IP 是预留好的组播 IP 地址。

package UDPProgram;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;

public class SendGroupMessageDemo {
    public static void main(String[] args) throws IOException {
        /*
            组播发送代码
         */

        //1.创建MulticastSocket对象
        MulticastSocket ms = new MulticastSocket();

        //2.创建组播IP地址

        //在创建IP地址时,我们要创建组播地址,也就是224.0.0.0 ~ 224.0.0.255之间的地址
        InetAddress address = InetAddress.getByName("224.0.0.1");

        //3.创建DatagramPacket对象
        String s = "你好呀!!!";
        byte[] bytes = s.getBytes();
        int port = 10000;

        DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);

        //4.发送数据
        ms.send(dp);

        //5.释放资源
        ms.close();
    }
}

        接收端也要创建 MulticastSocket 对象,然后利用 ms.joinGroup() 方法把本机的 IP 加入到组播 IP 这一组当中,此时再接收数据才能收到。

package UDPProgram;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;

public class ReceiveGroupMessageDemo {
    public static void main(String[] args) throws IOException {
        /*
            组播接收端
         */

        //1.创建ms对象,注意写上端口
        MulticastSocket ms = new MulticastSocket(10000);

        //2.将当前本机的IP,添加到224.0.0.1这一组当中
        InetAddress address = InetAddress.getByName("224.0.0.1");
        ms.joinGroup(address);

        //3.创建DatagramPacket对象,此时的参数不需要写端口了
        byte[] bytes = new byte[1024];
        DatagramPacket dp = new DatagramPacket(bytes,bytes.length);

        //4.接收数据
        ms.receive(dp);

        //5.解析数据
        byte[] data = dp.getData();
        int len = dp.getLength();
        String ip = dp.getAddress().getHostAddress();
        String name = dp.getAddress().getHostName();

        System.out.println("ip为:" + ip + ",主机名为:" + name + "的人,发送了数据:" + new String(data,0,len));

        //6.释放资源
        ms.close();

    }
}

        如果不在这一组的机器,是接收不到这一组传来的消息的。

        而广播的代码就更简单了,只需要把单播代码里要发送的 IP 地址改成 255.255.255.255,就能实现广播。

1.3.3 TCP 通信程序的编写

        TCP 通信协议是一种可靠的网络协议,它在通信的两端分别各自建立一个套接字对象(Socket)。通信之前,一定要保证连接已建立。两台设备之间通过套接字产生IO流来进行网络通信。针对客户端而讲,它生成的是输出流,对服务器而言,它生成的是输入流。
在这里插入图片描述
        TCP 通信的流程一般如下:
客户端:

① 创建客户端的 Socket 对象与指定的服务端连接。

Socket(String host, int port)

② 获取输出流,写数据。

OutputStream getOutputStream()

③ 释放资源。

void close()

服务端:

① 创建服务端的 Socket 对象(ServerSocket)。

ServerSocket(int port)

② 监听客户端,返回一个 Socket 对象。

Socket accept()

③ 获取输入流,读数据,把数据显示在控制台。

InputStream getInputStream()

④ 释放资源。

void close()

以下是按照这个步骤写的客户端与服务端的代码实现:

package TCPProgram;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

public class Client {
    public static void main(String[] args) throws IOException {
        //TCP协议,发送数据

        //1.创建Socket对象
        //细节:创建对象同时会尝试连接服务端,如果连不上,代码会报错
        Socket socket = new Socket("127.0.0.1", 10000);

        //2.可以从连接通道中获取输出流
        OutputStream os = socket.getOutputStream();
        //写出数据
        os.write("aaa".getBytes());

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

package TCPProgram;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) throws IOException {
        //TCP协议,接收数据

        //1.创建对象ServerSocket,注意端口保持一致
        ServerSocket ss = new ServerSocket(10000);

        //2.监听客户端的连接,死等
        //如果有客户端来连,返回一个Socket对象
        Socket socket = ss.accept();

        //3.从连接通道中获取输入流
        InputStream is = socket.getInputStream();
        //读入数据
        int b;
        while((b = is.read()) != -1) {
            System.out.println((char) b);
        }

        //4.释放资源
        socket.close();         //断开跟客户端连接
        ss.close();             //关闭服务器

    }
}

细节:代码中的服务端是按照字节流来读取的,一个字节一个字节读取,一定读不了中文,在 UTF-8 编码中,中文汉字用 3 个字节存储,因此每次读取一个字节只是读了三分之一个汉字,势必会产生乱码。为了解决乱码问题,我们要把服务端的字节输入流改写成字符输入流。

字节输入流 ——> 字符输入流,我们可以使用转换流实现。

package TCPProgram;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) throws IOException {
        //TCP协议,接收数据

        //1.创建对象ServerSocket,注意端口保持一致
        ServerSocket ss = new ServerSocket(10000);

        //2.监听客户端的连接,死等
        //如果有客户端来连,返回一个Socket对象
        Socket socket = ss.accept();

        //3.从连接通道中获取输入流
        InputStream is = socket.getInputStream();
        //通过转换流把字节流转换为字符流
        InputStreamReader isr = new InputStreamReader(is);
        //用字符流读入数据
        int b;
        while((b = isr.read()) != -1) {
            System.out.print((char) b);
        }

        //4.释放资源
        socket.close();         //断开跟客户端连接
        ss.close();             //关闭服务器

    }
}

细节:通过字符流读取大量数据时,如果想要提高读取效率,我们可以再生成一个缓冲流,把字符流包装起来,这样读取效率会大大提高。

package TCPProgram;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) throws IOException {
        //TCP协议,接收数据

        //1.创建对象ServerSocket,注意端口保持一致
        ServerSocket ss = new ServerSocket(10000);

        //2.监听客户端的连接,死等
        //如果有客户端来连,返回一个Socket对象
        Socket socket = ss.accept();

        //3.从连接通道中获取输入流
        InputStream is = socket.getInputStream();
        //通过转换流把字节流转换为字符流
        InputStreamReader isr = new InputStreamReader(is);
        //通过缓冲流把字符流包装成缓冲流
        BufferedReader br = new BufferedReader(isr);
        //用字符流读入数据
        int b;
        while((b = br.read()) != -1) {
            System.out.print((char) b);
        }

        //4.释放资源
        socket.close();         //断开跟客户端连接
        ss.close();             //关闭服务器

    }
}

1.3.4 TCP通信——三次握手和四次挥手

        三次握手是为了保证连接的建立。它的三次体现在客户端与服务端实际上互通了三次消息。客户端发出连接请求到服务端,服务端返回响应到客户端,客户端发送确认信息到服务端。
在这里插入图片描述
        四次挥手是为了确保连接断开前,传输数据已经处理完毕。客户端向服务端发送断连请求,服务端返回响应到客户端,然后等到服务端处理完所有数据后,服务端再向客户端发出确认取消信息,客户端再返回确认消息到服务端。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值