3.16网络原理

一.发展历史

为了核弹

1)建立指挥机构

2)指挥机构和核弹头之间的通信链路

为了保证通信链路在核弹破坏的情况下,也能正常运作

1.局域网/广域网

1.1借助网线

1.2借助交换机/路由器

交换机上面的网口都是一样的口,这个效果就是把插在上面的设备建成一个局域网

局域网内部的主机就可以互相进行访问

交换机就是把若干个设备给组建到一个局域网

路由器有两类端口:

WAN口:通过wan口连接到另一个局域网

LAN口:在一个局域网中,通过wan口连接到另外一个局域网

路由器则是连接了两个局域网.lan口是一个.wan口是一个

可以视为A是B的局域网中一个设备

二.常见名词

1.Ip地址

描述了网络上一个主机的地址(收货地址

本质上是一个32位的整数,由于是32位的整数,不方便记忆.就按照每个字节(8位)进行点分割

2.端口号

描述了一个主机上的某个应用程序(收件人的电话)

端口号本质上是一个2个字节(16位)的无符号整数

0-65535

3306是mysql默认端口号

服务器程序在启动的时候就需要绑定上一个端口号.以便客户端程序来访问

3.协议

进行有效的通信的前提:能够明确通信协议

本质上就是约定:发出的数据是什么样的格式,接收方按照对应的格式进行解析

网络通信的本质就是传输的是光信号和电信号

通过光信号的频率(高频率/低频) 和电信号的电平(高电平/低电平)来表示0/1

学习网络原理,就是研究各种协议

4.协议分层

网络通信的过程很复杂.

如果只是通过一个协议,来约定所有细节,那么这个协议就会非常庞大

更好的方法就是把一个大的复杂的协议,拆成多个晓得,更简单的协议,每个协议就负责自身的工作.

好处一:每层协议不需要理解其他层的协议的细节(封装)

打电话的人不需要理解打电话的工作原理,制造电话的人也不用管打电话人说什么语言

好处二:把对应承担额协议换成其他协议也不影响其它层的(更好的解耦合)

打电话的人可以使用无线电也可以用有线电.也可以用英语也可以不用英语

三.网络模型

1.OSI七层网络

这种OSI七层模型只存在教科书中

实际生活中是OSI的简化版本.TCP/IP五层/四层网络模型

2.TCP/IP五层四层网络模型

1)物理层:

网络通信中的硬件设备

网线/网卡,针对硬件设备的约定,就是物理层协议所负责的范畴,需要保证所有的主机和网络设备之间,是相互匹配的

2)数据链路层

负责相邻两个设备之间的通信->一根网线相邻的两个设备

路由器和主机一是相邻的

主机1和主机2 就不是相邻的

3)网络层

负责点到点之间的通信

网络上的任意节点,到任意节点之间的通信,(不一定是相邻,更多的是指不相邻

网络层就负责在两个节点之间,规划处一个合格的路线

实际的网络环境结构非常复杂,两个点之间的路线不止一条,就需要规划最合适的一条

4)应用层

和应用程序密切相关,传输的数据用途

5)总结

①主机对应了物理层到应用层

②路由器主要就是对应物理层到网络层

③交换机主要对应的是物理层到数据链路层

网络分层重要概念就是封装和分用

不同的协议之间是如何相互配合的

3.从具体实例看每一层做了什么

假如利用qq给一个同学发送消息

用户A在键盘上输入一个"hello" 按下发送键

3.1应用层

根据用户输入的内容.把数据构成了一个应用层的协议报文

协议:是一种约定 报文:遵守了这个约定的一组数据

QQ的代码就会根据程序员设计的应用层协议,来构造一个应用层的数据报文

后端开发,工作中的一个很重要的内容,就是根据需要来设计应用层协议

应用层协议就调用操作系统提供的API(称为 socket API).把应用层的数据交给传输层就进入了操作系统内核 了

3.2传输层(操作系统内核

根据刚刚传过来的数据,基于当前使用的传输层协议,来构造出一个传输层的协议报文

传输层最典型的协议.UDP/TCP 以TCP为例

再次打包成一个TCP的数据报 = TCP报头+数据载荷(Payload,也就是一个完整的应用层数据

可以简单的把这个构造TCP报文的过程看成一个字符串拼接-->这里拼接的是二进制数据

TCP报头中含有很多重要信息最重要的就是源端口和目的端口

也就是发件人的电话和 收件人的电话

接下来就把传输层的数据报交给网络层

3.3网络层(操作系统内核

拿到了完整的传输层数据报,就会再根据当前使用的网络层协议(例如IP)再次封装

把TCP数据报构造成IP数据报

IP数据报=IP协议报头+载荷(完整的TCP/UDP的数据报)

IP协议爆头有很多重要信息

最重要的就是源IP和目的IP

相当于发现人的地址和收件人的地址

紧接着,当前的网络层协议.就会把这个IP数据报交给数据链路层

3.4数据链路层(驱动程序

把刚才的IP数据报基础上,根据当前使用的数据链路层协议,构造一个数据链路层数据报

典型的数据链路层的协议:叫做以太网.构造成一个以太网数据帧

以太网数据帧:=帧头+ip数据报_+帧尾

枕头最重要的信息:接下来传给设备的地址是什么

紧接着.数据链路层又把这个数据交给物理层

3.5物理层(硬件设备

根据刚刚的以太网数据层也就是0 1构成的数据

把这里的01变成高低电平 通过网线传输

或者把0 1变成高频/低频的电磁波,通过光纤/无限的方式传播

到了这一步.此时数据已经离开了主机,通往了下一个设备,下一个设备可能就是路由器/交换机/其他设备

从上到下,就是一个封装的过程,数据从上层协议交给下层协议

由下层协议进行封装:构成该层协议的报文

3.6物理层(硬件设备.网卡

主机B的网卡感知到了一组高低电平,然后通过这些电平翻译成0/1的一串数据

然后这一串数据0.1就是一串完整的以太网数据帧

物理层就把这个数据交给了数据链路层

3.7数据链路层(驱动

数据链路层就负责对这个数据进行解析,去掉帧头和帧尾.取出里面的IP数据报,交给网络层协议

3.8网络层(操作系统

3.9 传输层(操作系统内核

3.10应用层(应用程序

应用层就会调用Socket API从内核中读取到了这个应用层数据报

再按照应用层协议进行解析,根据解析结果给显示到窗口中

从下到上就是分用

分用是封装的逆过程

4,路途

四.网络编程套接字

是操作系统给应用程序提供的一组API 叫做socket API

socket 原义 插座

socket可以视为应用层和传输层之间的通信桥梁

1.UDP/TCP

传输层的核心协议有两种: TCP/UDP

socket API也有对应的两组

由于TCP和UDP协议差别很大.所以API也差别大

TCP.有链接,可靠传输,面向字节流.全双工

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

有连接:打电话得接通才能交互数据

无连接:发微信,不需要接通也可以发数据

可靠传输;发送放直到接收方有没有接收到数据

比如:已读功能,或者打电话

不可靠传输:传输过程,发送方不知道接收方有没有收到数据

比如微信

面向字节流:以字节为单位进行传输(就是文件操作,然后把读取到的字节包装起来打印

面向数据报:以数据报为单位进行传输:一个数据包都会明确大小

全双工:一条链路:双向通信

半双工:一条链路,单项通信

2.UDP

UDP 主要涉及到两个类

一个是DatagramSocket

Datagarm就是数据报的意思

一个datagramSocket对象,对应着操作系统中socket文件

操作系统的文件指的是硬件设备或者软件资源

socket文件就对应的是"网卡"这种硬件设备

从socket文件读数据,本质上就是控制网卡读数据

从socket文件写数据,本质上就是控制网卡在上面写数据

可以类比socket文件就是一个遥控器控制操作网卡

还有一个是DatagramSocket

代表一个UDP数据报,使用UDP传输数据的基本单位

三.UDP socket

两个核心类

DatagramSocket:Datagram就是数据报的意思

这一个DatagramSocket对象.就对应着操作系统中的一个socket文件

代表着网卡硬件设备的抽象体现

receive:接收数据

send:发送数据

close:释放资源

还有一个 DatagramPacket 表示一个UDP数据包

每次发送/接收数据,都在传输一个DatagramPacket对象

四.UDP-EchoServer 回显服务器

DatagramSocket

DatagramPacket

程序一启动就可以直接读写了

回显服务---Echo 回声.请求内容是什么,得到的回应是什么

1)构建构造函数

此处构造服务器的SOCKET对象的时候,就需要显式的绑定一个端口号

端口号是用来区分一个应用程序的.主机收到网卡上的数据的时候,这个数据该给哪个程序

构造失败的可能

1)端口号被占用,同一个主机的两个程序也不能有相同的端口号

2)每个进程打开的文件个数是有上限的

2)启动服务器方法

我们需要在这个方法完成三个任务

①读取客户端发来的请求

②根据请求计算响应

③把响应写回客户端

服务端的定义就是"被动接收请求"的这一方

主动发送请求的这一方叫客户端

就是把接收的信息放在字节数组里,但是接收信息就像食堂阿姨打饭一样要你需要一个空盘子

第一部分就是构造一个空盘.第二步把空盘子放在接收函数里接收

reserve方法是有可能阻塞的

假设这里的UDP数据报最长是1024.实际上数据可能不够1024

把接收的信息写在一个string类的数据上

第一个参数不再是一个空的字节数组,第二个参数是字节数组的长度

第三个参数就是这个数据报发给谁(需要地址+电话也就是IP+port)

在当前的场景下,哪个客户端发来的请求,就把数据返回给哪个客户端

所以根据需要找到对应的地址电话

然后就把这个回复发送回去

没有需求的时候就会阻塞

//站在服务器的角度-五元组
//1.源IP:服务器程序本机的IP
//2.源端口:服务器绑定的端口(此处需要手动指定
//3.目的IP:包含在收到的数据报中.(客户端的IP)
//4.目的端口:包含在收到的数据报中(客户端的端口)
//5.协议类型:UDP
public class UDPEchoServer {
    //进行网络编程,第一步就先需要准备好socket实例.这是应用层和传输层建立联机的必要条件.
    private DatagramSocket socket=null;

    //需要指定端口,让客户端能找到
    public UDPEchoServer(int port) throws SocketException {
        socket=new DatagramSocket(port);
    }
    //启动服务器
    public  void start() throws IOException{
        System.out.println("启动服务器");
        //UDP不用像TCP一样建立连接,直接接收从客户端来的数据即可
        while(true){
            //1.读取客户端发来的请求,为了接收数据就需要准备一个空的包
            DatagramPacket requestPacket =new DatagramPacket(new byte[1024],1024);
            socket.receive(requestPacket);//把接收的信息写入空的包里
            //把DatagramPacket解析成一个String
            String request =new String(requestPacket.getData(),0, requestPacket.getLength(),"UTF-8");

            //2.根据请求计算响应
            String response=process(request);

            //3.把响应写回客户端
            DatagramPacket responsePacket=new DatagramPacket(response.getBytes(),response.getBytes().length
                                    ,requestPacket.getSocketAddress());
            socket.send(responsePacket);
            System.out.printf("[%s:%d] req: %s,resp:%s\n",
                    requestPacket.getAddress().toString(),requestPacket.getPort(),request,response);
        }
    }

    //回显服务
    public String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        UDPEchoServer server=new UDPEchoServer(9090);
        server.start();
    }

}

五.UDPEchoClient udp回显客户端

通常写代码的时候,服务器都是手动指定的,客户端由系统自动指定(系统随机分配一个

对于服务器来说,必须要手动指定,后续客户端要根据这个端口才能来访问到服务器,

如果系统随机分配.客户端根本不知道服务器端口是什么,不能访问

对于客户端来说,如果手动指定,也可以,但是系统分配更好,因为自己指定可能会占用

1)构造方法

客户端有很多,一个服务器可以给很多客户端提供连接

服务器处理每个请求,都需要消耗一定的硬件资源(CPU,内存,磁盘,带宽)

能处理多少客户端,取决于,

1.处理一个请求,消耗多少资源

2.机器一共有多少资源能用

java中一般通过性能测试的方式去检测

idea默认一个程序只能启动一个实例,再次启动会干掉其他的实例,勾选这个选项就可以启动多个实例

//站在客户端的角度-五元组

//1.源IP 本机IP

//2.源端口:系统分配的端口

//3.目的IP :服务器的端口

//4.目的IP:服务器IP

//5.协议类型udp

public class UDPEchoClient {

private DatagramSocket socket=null;

private String serverIP;

private int serverPort;

public UDPEchoClient(String ip,int port) throws SocketException {

//此处port是服务器端口

//客户端启动的时候,不需要给socket指定端口,客户端的端口是系统自己分配的

//服务器需要找客户端端口的时候,直接调用getSocketAdress就可以

socket=new DatagramSocket();

serverIP=ip;

serverPort=port;

}

public void start() throws IOException{

Scanner scanner=new Scanner(System.in);

while(true){

//1.先从控制台读取用户输入的字符串

System.out.print("->");

String request=scanner.next();

//2.把这个用户输入的内容构造成一个UDP请求,并发song

// 构造的请求包含两部分信息

// 1)数据的内容.request字符串

// 2)数据要发给谁服务器的IP+端口

DatagramPacket requestPacket=new DatagramPacket(request.getBytes(),

request.length(), InetAddress.getByName(serverIP),serverPort);

socket.send(requestPacket);

//3.从服务器读取响应数据,并解析

DatagramPacket responsePacket=new DatagramPacket(new byte[1024],1024);

socket.receive(responsePacket);

String response=new String(responsePacket.getData(),0,responsePacket.getLength()

,"UTF-8");

//4.把响应结果写到控制台

System.out.printf("req:%s,resp:%s\n",request,response);

}

}

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

UDPEchoClient udpEchoClient=new UDPEchoClient("127.0.0.1",9090);

udpEchoClient.start();

}

}

六.带翻译的UDP服务器

客户端不变,把服务器代码进行调整

主要就是调整process方法

读取请求并解析,把响应写回客户端,

关键的逻辑,就是实现这里的

根据请求处理响应

public class UDPDictServer extends UDPEchoServer{
    private HashMap<String,String> dict=new HashMap<>();
    //private DatagramSocket socket=null;
    public UDPDictServer(int port) throws SocketException {
        super(port);
        //构造几个词
        dict.put("cat","猫");
    }

    @Override
    public String process(String request ) {
        return dict.getOrDefault(request,"没有");
    }

    public static void main(String[] args) throws IOException {
        UDPDictServer server=new UDPDictServer(8090);
        server.start();
    }
}

七 TCPEchoServer

tcp API也有两个核心的类

ServerSocket 专门给TCP服务器用的

Socket既要给服务器用,有需要给客户端用

主要通过这样的类,来描述一个socket文件.跟UDP不同,不需要用专门一个类表示传输的包

面向字节流,以字节为单位传输的

public class TCPEchoServer {

private ServerSocket serverSocket=null;

public TCPEchoServer(int port) throws IOException {

serverSocket=new ServerSocket(port);

}

public void start() throws IOException {

System.out.println("服务器启动");

while(true){

//由于TCP是由连接的,不可以一开始就可以读数据.所以需要先建立连接(打电话

//accept就是在"接电话".接电话的前提就是有人da,如果没有客户端尝试连接accept就会阻塞

//accept返回了一个socket对象.称为clientSocket.后续跟客户端之间的沟通就是通过client

//也就是说serverSocket就接电话

Thread t=new Thread(()->{

Socket clientSocket= null;

try {

clientSocket = serverSocket.accept();

} catch (IOException e) {

e.printStackTrace();

}

processConnection(clientSocket);

});

}

}

private void processConnection(Socket clientSocket) {

System.out.printf("[%s : %d] 客户端建立连接\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());

//接下来处理请求和响应

//TCP关于tcp socket的读写和文件读写的操作是一样的

try(InputStream inputStream=clientSocket.getInputStream()){

try (OutputStream outputStream=clientSocket.getOutputStream()){

//循环处理请求,分别返回响应

Scanner scanner=new Scanner(inputStream);

while(true){

//1.读取请求

if(!scanner.hasNext()){

System.out.printf("[%s : %d] 客户端断开连接~!\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());

break;

}

//此处用scanner更方便,如果不用也可以用inputstream的read也可以

String request=scanner.next();

//2.根据请求,计算响应

String response =process(request);

//3.把这份下响应返回给客户端

//为了方便期间,可以用PrintWriter把OutputStream包裹一下

PrintWriter printWriter=new PrintWriter(outputStream);

printWriter.println(response);

//上面这一步就是为了将读文件操作

printWriter.flush();

//刷新缓冲区,不刷新额度话,客户端不能第一时间看到响应结果

System.out.printf("[%s:%d] req : %s ,resp : %s\n"

,clientSocket.getInetAddress().toString(),clientSocket.getPort()

,request,response);

}

}

} catch (IOException e) {

e.printStackTrace();

}finally {

try {

//记得挂电话

clientSocket.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

private String process(String request) {

return request;

}

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

TCPEchoServer tcpEchoServer=new TCPEchoServer(9090);

tcpEchoServer.start();

}

}

public class TCPEchoClient {

//用普通的socket即可,不需要用serverSocket

private Socket socket=null;

public TCPEchoClient(String serverIP,int serverPort) throws IOException {

//调用这个方法相当于与服务器建立连接打电话

socket=new Socket(serverIP,serverPort);

}

public void start(){

System.out.println("与服务器连接成功");

Scanner scanner=new Scanner(System.in);

try (InputStream inputStream=socket.getInputStream()){

try (OutputStream outputStream=socket.getOutputStream()){

while (true){

//仍然是四个步骤,从控制台获取子串,根据子串构造请求,再读取响应,解析,最后把结果显示到控制台

//1.控制台读取子串

System.out.print("->");

String request=scanner.next();

//2.根据子串构造请求

PrintWriter printWriter=new PrintWriter(outputStream);

printWriter.println(request);

printWriter.flush();//不刷新可能不能很快得到数据

//.3.从服务器读取响应

Scanner respScanner=new Scanner(inputStream);

String response=respScanner.next();

//4.把结果写给控制台

System.out.printf("req: %s , resp: %s\n",request,response);

}

}

} catch (IOException e) {

e.printStackTrace();

}

}

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

TCPEchoClient tcpEchoClient=new TCPEchoClient("127.0.0.1",9090);

tcpEchoClient.start();

}

}

这边的发送和接收都使用printwrite包裹了一下

把请求和回复都包裹在outstream里

将request写进去

并用flush刷新

猜你喜欢

转载自blog.csdn.net/m0_72618437/article/details/129604140
今日推荐