JavaEE初阶7.0

目录

一、网络初识

[1.0 网络初识](#1.0 网络初识)

[(1) 局域网广域网](#(1) 局域网广域网)

[(2) 路由器和交换机](#(2) 路由器和交换机)

[(3) IP地址和端口号](#(3) IP地址和端口号)

(4)协议

(5)五元组

[(6) 协议分层](#(6) 协议分层)

(6)OSI七层网络模型

[(7) 网络设备所对应的分层](#(7) 网络设备所对应的分层)

(8)传输层的两个核心协议

[2.0 网络数据通信的基本流程](#2.0 网络数据通信的基本流程)

(1)理解网络基本原理

(2)逆向

二、网络编程

[1.0 DatagramSocket](#1.0 DatagramSocket)

(1)概述

(2)方法

[2.0 Udp服务器代码示例](#2.0 Udp服务器代码示例)

[(1) 代码示例](#(1) 代码示例)

(2)代码解析

(3)一些细节问题

[3.0 Tcp服务器代码示例](#3.0 Tcp服务器代码示例)

(1)代码示例

(2)代码解析

(3)一些细节问题


一、网络初识

1.0 网络初识
(1) 局域网广域网

穿插网络发展历程介绍局域网和广域网

国内2000年前后 才真正开始进入到 网络时代~

2000年前 网络稀罕物 有的家庭已经有电脑了 但是没有网~

2000年之后 网络就开始逐渐多了 网吧~

当时有同学就把家里的一些游戏拷贝到学校机房 就可以联机对战~

局域网(LAN Local Area NetWork )

是指在有效的地理范围内。由计算机、服务器、交换机、路由器等设备组成的私有高速通信网路

(只能在一个机房里联机 两个设备连接到同一个路由器)
广域网(WAN Wide Area NetWork)

是指跨越较大的地理范围 通过公共或专用通信链路连接多个局域网、城域网或其他网路的大规模通信网络。

(把很多局域网连到一起就是 全世界最大的广域网 因特网 The Internet)

2007年 乔布斯发布苹果手机 标志着 移动互联网的时代 正式拉开序幕~~

差不多2011 2012年左右 真神Iphone4出现 使得诺基亚等传统手机厂商一夜之间就G了

2017年左右 有一波风口 VR/AR 但是当时的条件不足以把这个事情做好 客观的硬件设备

总结:单机时代 局域网时代 广域网时代 移动互联网时代

(2) 路由器和交换机

路由器 交换机(组建网络重要的核心设备)

路由器:用于连接不同网络(如LAN或WAN)并根据IP地址转发数据包

路由器有五个口 能接入的设备有限 进行组建局域网

交换机:用于同一网络内的设备互联 通过MAC地址转发数据帧

交换机上面有很多接口 可以扩展路由器

(3) IP地址和端口号

ip地址:标识网路上一台设备所在的地址 标识主机的地址

端口号:用来区分一台主机上多个应用程序的

cmd里面输入ipconfig也能看到看到ip地址

一台主机上可能有多个程序同时使用网络

(4)协议

网路通信中约定的规则

就是约定 双方都按照协议运行

主机设备 多个主机都能认同并遵守同一套协议 此时的通信才有意义的~

例如我们都是中国人 用的普通话协议 所以都听懂对方说话哈哈

(5)五元组

进行一次网络通信 涉及到的5个非常关键的信息

源IP 源端口 目的IP 目的端口 协议类型

理解: 贫僧东土大唐而来,到西方拜佛求经而去

贫僧 源端口 拜佛 目的端口 东土大唐 源IP 西方 目的IP

(6) 协议分层

网络通信 非常复杂

如果我们设计一个协议 完成网络通信中方方面面的问题

势必会使这个协议非常复杂 非常庞大 所以需要拆分

把一个大的协议 拆分成若干个小的 功能单一的协议

只有相邻的两层协议之间可以进行交互 不能跳级交互

淘宝买东西到你手上

用户关心的是床刷子买到之后如何使用-->应用层 卖家只要关心,收件人信息-->传输层

物流公司则关心,包裹怎样路径传输的-->网络层 快递小哥/货车司机,考虑的是相邻节点-->数据链路层

真实的互联网世界 具体是怎么分层的呢?

(6)OSI七层网络模型

这种是教科书上的分层 实际上真实的网络分层方式更简化

TCP/IP 五层(四层) 协议模型

随着学习过程的展开 会慢慢细化学习这部分内容

这部分内容是需要大家背下来的

七层模型和五层模型的对应:

(7) 网络设备所对应的分层

但是真实的路由器交换机等 功能更丰富 更强大

又的路由器甚至开了什么模式 能达到交换机的效果

你通过wx发一个数据 (数据属于 应用层) 数据经过某个运营商的路由器

有的能给你解析出来你发的内容 (此时 路由器相当于工作在应用层)

(8)传输层的两个核心协议

TCP协议和UDP协议

差别非常大,编写代码的时候,也是不同的风格~(因此socket api提供了两套)

TCP 有连接 可靠传输 面向字节流 全双工

UDP 无连接 不可靠传输 面向数据报 全双工

连接:抽象的连接 对于TCP协议来说,就保存了对端的信息

传输:网络上的数据是非常容易出现丢失的情况(丢包) (光电信号可能受到外界干扰)

可靠传输的意思是 不是保证数包100%到达,而是尽可能的提高传输成功的概率

如果出现了丢包,能感知到

不可靠传输 只是把数据发了,就不管了~

面向字节流:

读写数据时候,是以字节为单位 支持任意长度 容易出现粘包问题

面向数据报:

读写数据的时候,以一个数据报为单位(不是字符) 一次必须读写一个UDP数据报 不能是 半个~~ 不存在粘包问题

全双工:一个通信链路 支持 双向通信 (能读也能写)

半双工:只支持单向通信(要么读 要么写)

2.0 网络数据通信的基本流程
(1)理解网络基本原理

例子:用协议的角度看 我通过qq 发送hello给对方

1应用程序 获取用户输入 构造一个应用层的数据包

(网络传输的数据 本质上都是"字符串" 或者 "二进制的bit流")

2应用程序调用传输层 提供的接口(API) 把数据交给传输层,传输层拿到数据构造传输层数据包

3传输层调用网络层API 把传输层的数据包交给网络层 网络层继续进行处理

网络层最主要的协议是IP协议 IP协议继续对上述的数据进行加工(也就是拼接上IP报头)

4 IP协议继续调用 数据链路层, 把IP数据包交给数据链路包

数据链路层,核心协议"以太网" 以太网这个协议,也会在网络层数据包的基础上进一步加工

5以太网继续将这样的数据交给硬件设备(网卡)

网卡会把上述二进制数据,最终以光信号/电信号/电磁波信号 传播出去了

(数据终于出门了呜呜呜)

整个过程五元组的信息都会出现 从上层到下层 数据都要进一步加工

(添加报头)和 封装(和面向对象的封装不是一个封装)

接收方是从下到上依次解析,分用~ (封装的逆向过程)

(2)逆向

为了更好的理解 简单描述一下哈

1.数据到达接收方的网卡 光电信号 网卡把光电信号还原成二进制0101

把二进制数据交给上层数据链路层

2.数据链路层按照以太网协议进行解析

把报头和报尾取出来,剩下的载荷,往上传递给网络层

3.网络层拿到这个数据之后,按照IP协议的格式解析,再把载荷数据交给传输层

4.传输层拿到数据之后,也是类似,按照TCP协议来解析,取出载荷,交给应用层

5.QQ应用程序,解析应用层数据,拿到关键信息,展示到界面上,给出提示

(虽然上述这些过程 听起来是复杂的 但是对于计算机来说 是极快的过程)

二、网络编程

1.0 DatagramSocket
(1)概述

操作系统提供的一组api--->socket api(传输层给应用层提供)

api:理解为软件和硬件间的一座桥梁就行了

socket api进行网络编程

前面我们讲到 计算机的文件通常是一个广义的概念 文件也可以代指一些硬件设备

例如 我们可以把网卡抽象成为socket文件 操作网卡的时候 流程和操作普通文件差不多

操作网卡转换成操作socket文件 socket文件就相当于"网卡的遥控器"

(2)方法

DatagramSocket:UDP通信的核心类

构造方法:打开文件

//套接字 是网络通信的基石 可以理解为网络数据传输的"端点" 它应用程序通过网路协议于其他程序交换数据的 编程接口

DatagramSocket():创建一个UDP数据报套接字的Socket 绑定到本机任意一个随机端口

DatagramSocket(int port):创建一个UDP数据报套接字的Socket 绑定到本机指定的端口

**receive (DatagramPacket p)**从此套接字接收数据报

(如果没有接收数据报 该方法会阻塞等待)

这里也是把参数作为输出的结果 是输出型参数

(调用之前 先构造空的(不是null) 把对象传递到receive里面 receiver就会把数据从网卡读出来

**send(DatagramPacket p)**从此套接字发送数据报(不会阻塞等待 直接发送)

close() 关闭此数据报套接字

**DatagramPacket()**数据报:

2.0 Udp服务器代码示例

UDP服务器 UdpEchoClinet回显服务器

//回显服务器 客户端给服务器发一个数据(请求) 服务器给客户端返回一个数据(响应)

真实的服务器,请求和响应式不一样的~~

(1) 代码示例
java 复制代码
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class UdpEchoServer {

    //这个代码是自己搭建一个回显服务器

    private DatagramSocket socket = null;

    public UdpEchoServer(int port) throws SocketException {
        //指定了一个端口号  让服务器来使用
        socket = new DatagramSocket(port);
    }

    public void start() throws IOException {
        //启动服务器
        System.out.println("服务器启动");

        while(true){
            //循环一次  就相当于处理一次请求
            //处理请求的过程  典型的服务器都是分成三个步骤

            //1.读取请求并解析
              //DatagramPacket表示一个UDP数据报 此处传入的字节数组,就保存UDP载荷部分
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(requestPacket);
              //把读取到的二进制数据,转换成字符串 只是构造有效的部分
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());

            //2.根据请求 计算响应(回显服务器最关键的逻辑)
              //但是此处写的是回显服务器,这个环节相当于省略了
            String response = process(request);    //请求等于回应

            //3.把响应返回给客户端
              //根据response构造DatagramPacket,发送客户端
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
                    requestPacket.getSocketAddress());
              //此处还不能直接发送  UDP协议自身没有保存对方信息(不知道发给谁  需要指定目的ip和目的端口)
            socket.send(responsePacket);

            //4.打印一个日志
            System.out.printf("[%s:%d] req: %s,rep: %s\n",requestPacket.getAddress().toString(),
                    requestPacket.getPort(),request,response);
        }
    }

    //后续要写别的服务器  只修改这个地方就好了
    private String process(String request){
        return request;
    }
}

下面是客户端代码

java 复制代码
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;

public class UdpEchoClient {
    private DatagramSocket socket = null;

    //Udp本身不保存对端信息,就自己的代码中保存一下
    private String serverIP;
    private int serverPort;

    //和服务器不同 此处的构造方法是要指定访问的服务器的地址
    public UdpEchoClient(String serverIP,int serverPort) throws SocketException {
        this.serverIP=serverIP;
        this.serverPort=serverPort;
        socket = new DatagramSocket();//为什么这里不能指定端口号呢 那个肉汤的例子
    }

    public void start() throws IOException{
        Scanner scanner = new Scanner(System.in);

        while(true){
            //1.从控制台读取用户输入的内容
            System.out.println("请输入要发送的内容:");

            if(!scanner.hasNext()){
                break;  //这个if用来终止客户端
            }
            String request = scanner.next();

            //2.把请求发送给服务器 需要构造DatagramPacket对象
            //构造过程中  不光要构造载荷 还要设置服务器的IP和端口号
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
                       InetAddress.getByName(serverIP),serverPort);//这里的服务器的IP和端口号要包装一下 要不datagram方法里面没有解析这个的
            //3.发送数据报
            socket.send(requestPacket);

            //4.接收服务器的响应
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096] , 4096);
            socket.receive(responsePacket);

            //5.从服务器读取的数据进行解析 打印出来
            String response = new String (responsePacket.getData(),0,responsePacket.getLength());
            System.out.println(response);
        }
    }

    public static void main(String[] args) throws IOException {
        UdpEchoClient client = new UdpEchoClient("47.108.28.88",9090);
        client.start();
    }
}

效果图片:

(2)代码解析

1.socket对象代表网卡文件 读这个文件相当于从网卡收集数据,写这个文件相等于让网卡发数据

socket对象创建的时候 为什么要指定一个端口号?

指定端口号的核心原因是 标识应用程序的通信端点 确保数据能准确送达目标程序

2.while循环中做三件事(注释里面讲解) 这三件事是一个服务器通常的流程

3.读取请求并解析

*构造DatagramPacket对象 DatagramPacket就代表UDP数据包 报头+载荷new字节数组保存

*调用receive 需要理解输出型参数:调用时传入一个空的(非null)DatagramPacket对象,方法 内部会将接收到的数据填充到对象中)

*把udp数据包载荷取出来 构造一个String

通过requestPacke.getData() 拿到DatagramPacket中的字节数组~

requestPacket.getLength() 拿到有效数据的长度

根据字节数组 构造出一个string

4.根据请求计算响应 把请求扔给回应(回显服务器嘛)

5.把响应返回到客户端

response.getByte() 拿到字符串中的字节数组~

resopnse.getByte().length 拿到字节数组的长度,而不是使用字符串长度(单位 字符)使用自己 的个数作为参数

requestPacket.getSocketAddress() 拿到客户端的IP和端口号~

new DatagramPacket是要干啥?

复制代码
DatagramPacket responsePacket = new      DatagramPacket(response.getBytes(),response.getBytes().length,
        requestPacket.getSocketAddress());

构造响应数据报 上面的是请求数据报 这里是响应数据报

6.socket.send(responsePacket); 把构造好的数据包发送出去 前提是数据包中包含了目的IP和目 的IP和目的端口(前面responsePacket的时候 就刻意指定了IP和端口)

(3)一些细节问题

输出型参数的理解(又一个文件IO的例子)

socket不用colose的嘛?

文件要关闭 考虑清楚这个文件对象的生命周期是怎样的~

此处的socket对象 伴随着整个udp服务器 自始至终 提前关闭服务器运行的也没有意义

服务器关闭(进程结束) 进程结束时就会自动释放PCB的文件描述附表的所有资源

当前服务器启动了,启动了之后,客户端还没有呢,当然也没有请求发来啦

在客户端请求发过来之前 服务器里面的逻辑都在干啥呢?

receive会触发阻塞行为 客户端请求发过拉il receive才会返回 客户端的请求没来,receive就一直阻塞了

3.0 Tcp服务器代码示例
(1)代码示例
java 复制代码
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class TcpEchoServer {
    private ServerSocket serverSocket = null;

    //这里和UDP服务器类似 也是在构造对象的时候 绑定端口号
    public TcpEchoServer(int port) throws IOException{
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("启动服务器");

        while(true) {//服务器7*24运行
            //tcp来说  需要先处理客户端发来的连接
            // 通过读写clientSocket 和客户端进行通信
            //如果没有客户端发起连接  此时accpet就会阻塞
            Socket clientSocket = serverSocket.accept();
            processConnection(clientSocket);//因为代码逻辑比较复杂 单独包装成一个方法
        }
    }
    //处理一个客户端的连接
    //真实情况是可能要涉及到多个客户端的请求和响应
    private void processConnection(Socket clientSocket) throws IOException {

        System.out.printf("[%s:%d] 客户端上线!\n",clientSocket.getInetAddress(),clientSocket.getPort());

        //获取客户端的输入流和输出流
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()){
            //为什么套层? 在Java网络编程中,i和o是字节流,它们直接操作原始字节(byte数组)
            //而Scanner和PrintWriter是字符流(Character Stream) 可以更加方便的处理文本数据(String)
            //如果是二进制传输的话(如文件 图片) 直接使用i和o  如果是文本数据传输(Http 聊天消息)推荐使用Scanner PrintWriter
            //针对InputStream套了一层
            Scanner scanner = new Scanner(inputStream);//直接用scanner读取请求里面的内容
            //针对OutputStream套了一层
            PrintWriter writer = new PrintWriter(outputStream);
            //分成三个步骤
            while(true){
                //1.读取请求并解析  可以直接read 也可以借助
                if(!scanner.hasNext()){//这个表明条件如果是没有下个数据可以了
                    //连接断开了
                    //也打印一个日志吧
                    System.out.printf("[%s:%d] 客户端上线!\n",clientSocket.getInetAddress(),clientSocket.getPort());
                    break;
                }
               String request = scanner.next();
                //2.根据请求计算响应
                String response = process(request);
                //3.返回响应到客户端
                //outputStream.write(response.getBytes());
                writer.println(response);
                //这里省略了具体实现 直接读取请求  然后返回原样

                //打印日志
                System.out.printf("[%s:%d] req: %s,rep: %s\n",clientSocket.getInetAddress(),clientSocket.getPort(),request,response);

            }

        }catch(IOException e){
            throw new RuntimeException(e)
        }
    }

    private String process(String request) {
        return request;//这里还是讲解的回显服务器  所以requst和response相同
    }
}

客户端代码:

java 复制代码
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class TcpEchoClient {
    private Socket socket = null;

    public TcpEchoClient(String serverIp, int serverPort) throws IOException {
        //这里直接就可以把字符串的IP地址和端口设置进来
        //127.0.0.1 这种字符串
        socket = new Socket(serverIp,serverPort);
    }

    public void start() throws IOException {
        Scanner scanner = new Scanner(System.in);
        try(InputStream inputStream = socket.getInputStream();
        OutputStream outputStream= socket.getOutputStream()){
            //为了使用方便  套壳操作
            Scanner scannerNet = new Scanner(inputStream);
            PrintWriter writer = new PrintWriter(outputStream);

            //从控制台读取请求,发送给服务器
            while(true){
                //1.从控制台读取用户输入
                String request = scanner.next();
                //2.发送请求给服务器
                writer.println(request);
                //3.读取服务器返回的响应
                String response = scannerNet.next();
                //4.打印到控制台
                System.out.println(response);

            }
        }catch (IOException e){
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) throws IOException {
        TcpEchoClient client = new TcpEchoClient("127.0.0.1",9090);
        client.start();
    }
}

效果图:

Socket clientSocket = serverSocket.accept();

private Socket socket = null;

这两socket对象 绝对不是同一个对象(分别在不同进程中 甚至在不同主机上)

可以理解为两部电话 A和B能打电话 但是A和B不是同一个电话

(2)代码解析

连接

连接相当于打电话 建立对端信息(例如ip 端口) Tcp是面向连接的协议,客户端和服务器端建立一次连接后,可以在该连接上多次发送和接收数据(即多次请求-响应交互) ,而不需要每次都重新建立连接

accpet方法

阻塞等待客户端的连接请求 ,当有客户端连接时,返回一个代表连接的socket对象

如何理解ServerSocket和socket的关系?

销售并不是一个人完成的

serverSocket相当于售楼处 外部的连接 accpet方法是接待客户

socket相当于客户 比较具体 具体谈业务还是要在socket里面

Scanner

scanner 构造方法 填入的其实是一个InputStream对象 也就是说它需要从某个输入流读取数据,而InputStream 是Java中所有字节输入流的基类

outputStream.write(response.getBytes())这句代码的作用是:将一个字符串(response)转换为字节数组(byte)并通过outputStream写入到某个输出目标

为什么套层? 在Java网络编程中,i和o是字节流,它们直接操作原始字节(byte数组)

而Scanner和PrintWriter是字符流(Character Stream) 可以更加方便的处理文本数据(String)

如果是二进制传输的话(如文件 图片) 直接使用i和o 如果是文本数据传输(Http 聊天消息)推荐使用Scanner PrintWriter

PrintWriter writer = new PrintWriter(outputStream);

这行代码的作用是:创建一个PrinterWriter对象 并将其绑定到一个outputStream(字节输出流) 以便以字符形式(文本) 更方便的写入数据 PrintWriter是Java提供的字符流 用于格式化输出文本

(3)一些细节问题

缓冲区

wirte.println(request) 这个操作只是把数据放到"发送缓存区"中 还没有真正写入到网卡里面

使用flush方法来"冲刷缓冲区" write.flush();

实际开发中,广泛使用缓冲区这样的概念 flush这个操作是很关键的~

println

write.println(request) 行为是自动加上一个\n 如果不加\n只是print呢 这样是不行的

print数据是发过去了 服务器收到了 但是服务器没有真正判断 服务器里面有个hashNext()

遇到空白符 才认为是一个"完整的 next" 在遇到之前,都会阻塞

使用println就是在暗暗约定 一个请求/响应 使用\n作为结束标记

对端读的时候,也是读到\n就结束(认为是读到一个完整的请求了)

serverSocket clientSocket的生命周期

clientSocket 每个客户端连接 都会创建一个新的 每个客户端断开连接 这个对象可以补药了

一个服务器要能够给多个客户端提供服务

代码Tcp服务器也能够做到处理多个吗?

Idea默认只允许启动一个进程 如果你启动第二个进程 它会把第一个进程关闭 需要我们手动设置

问题是只能同时启动一个客户端

启动二个客户端 第三个客户端没有反应

为什么第二个客户端不能运行 运行代码发现关掉第一个客户端的时候 第二个客户端立马行了

关键的逻辑在while循环里面

处理客户端一的请求的时候 卡在了! scanner.hasNext()里面

如果客户端没发请求的时候 就会阻塞在了hasNext里面 意味着accpet执行不到

循环退出(客户端退出)第二个客户端才能登场

总结:等待用户发送请求的时候,没法等accpet 这个时候 有新的客户端连过来了 也无法接通电话

怎么解决这个代码不合理的问题 怎么样同时调用accept 同时处理hasNext?

兵分两路 多线程

多线程的最初诞生就是为了这个场景 最初是多进程 本质上都是处理服务器开发的问题

主线程负责进行accpet 每次accpet到一个客户端 就创建一个线程 由新线程负责处理客户端的请求 (类似那个例子 外场揽客 后场销售)

如果单个线程 如果访问量激增 会出现第一种那样的报错吗?

这个也是线程池的诞生场景

多线程 还是线程池 都意味着 一个客户端对应一个线程 一个主机上创建的线程数目是否有上限呢

有的 一个主机创建几千个线程 就已经很不容易了

网络初始+网络编程完结 接下来的内容是网络原理~

感谢大家的支持

更多内容还在加载中...........

如有问题欢迎批评指正,祝大家生活愉快、学习顺利!!!

相关推荐
天若有情6734 天前
Spring MVC文件上传与下载全面详解:从原理到实战
java·spring·mvc·springmvc·javaee·multipart
朝新_5 天前
【EE初阶 - 网络原理】传输层协议
java·开发语言·网络·笔记·javaee
朝新_19 天前
【EE初阶 - 网络原理】网络通信
java·开发语言·网络·php·javaee
Brookty25 天前
【Java学习】定时器Timer(源码详解)
java·开发语言·学习·多线程·javaee
脑子慢且灵1 个月前
【JavaWeb】一个简单的Web浏览服务程序
java·前端·后端·servlet·tomcat·web·javaee
天若有情6732 个月前
《JAVA EE企业级应用开发》第一课笔记
java·笔记·后端·java-ee·javaee
我爱996!3 个月前
SpringMVC——建立连接
javaee
coderlwz4 个月前
高级 JAVA 工程师卷 1
javaee
朝新_5 个月前
【多线程初阶】阻塞队列 & 生产者消费者模型
java·开发语言·javaee