网络原理初识

网络原理

1.网络原理初识

随着时代的发展,越来越需要计算机之间互相通信,共享软件和数据,即以多个计算机协同工作来完成

业务,就有了网络互连。

网络互连:将多台计算机连接在一起,完成数据共享。

数据共享本质是网络数据传输,即计算机之间通过网络来传输数据,也称为网络通信

根据网络互连的规模不同,可以划分为局域网和广域网。

局域网LAN

局域网,即 Local Area Network,简称LAN。

Local 即标识了局域网是本地,局部组建的一种私有网络。局域网内的主机之间能方便的进行网络通信,又称为内网;局域网和局域网之间在没有连接的情况下,是无法通信的。

广域网WAN

广域网,即 Wide Area Network,简称WAN。

通过路由器,将多个局域网连接起来,在物理上组成很大范围的网络,就形成了广域网。广域网内部的局域网都属于其子网。

路由器上,有lan口和wan口

虽然只有4个lan口,实际上组建的局域网可以有更多设备,通过交换机

交换机的口不分lan,wan

AP相当于一个不用插网线的小交换机(提供无限热点扩容能力)


网络通信基础

网络互连的目的是进行网络通信,也即是网络数据传输,更具体一点,是网络主机中的不同进程间,基

于网络传输数据。

那么,在组建的网络中,如何判断到底是从哪台主机,将数据传输到那台主机呢?这就需要使用IP地址

来标识。

电脑种类很多,设备厂商也有很多,必须有一份统一的协议标准来研发设备,大家搞出来的都可以在一起互相通信了

TCP/IP五层(或四层)模型

TCP/IP是一组协议的代名词,它还包括许多协议,组成了TCP/IP协议簇。

TCP/IP通讯协议采用了5层的层级结构,每一层都呼叫它的下一层所提供的网络来完成自己的需求。

应用层:负责应用程序间沟通,如简单电子邮件传输(SMTP)、文件传输协议(FTP)、网络远程

访问协议(Telnet)等。我们的网络编程主要就是针对应用层。

传输层:负责两台主机之间的数据传输。如传输控制协议 (TCP),能够确保数据可靠的从源主机发

送到目标主机。

网络层:负责地址管理和路由选择。例如在IP协议中,通过IP地址来标识一台主机,并通过路由表

的方式规划出两台主机之间的数据传输的线路(路由)。路由器(Router)工作在网路层。

数据链路层:负责设备之间的数据帧的传送和识别。例如网卡设备的驱动、帧同步(就是说从网线上

检测到什么信号算作新帧的开始)、冲突检测(如果检测到冲突就自动重发)、数据差错校验等工作。

有以太网、令牌环网,无线LAN等标准。交换机(Switch)工作在数据链路层。

物理层:负责光/电信号的传递方式。比如现在以太网通用的网线(双绞 线)、早期以太网采用的的同

轴电缆(现在主要用于有线电视)、光纤,现在的wifi无线网使用电磁波等都属于物理层的概念。物理

层的能力决定了最大传输速率、传输距离、抗干扰性等。集线器(Hub)工作在物理层。

五元组

在TCP/IP协议中,用五元组来标识一个网络通信:

  1. 源IP:标识源主机

  2. 源端口号:标识源主机中该次通信发送数据的进程

  3. 目的IP:标识目的主机

  4. 目的端口号:标识目的主机中该次通信接收数据的进程

  5. 协议号:标识发送进程和接收进程双方约定的数据格式


A把消息通过QQ发给主机B

发生方情况:

1.应用层

qq应用程序,从输入框中获取你要输入的消息。构造成应用层数据报(根据应用层协议)

构造…过程就是按照一定格式进行字符串拼接 发送方和接收方需达成一致

应用程序就会调用传输层提供的接口,把上述数据交给传输层进行处理~~

2.传输层

UDP不关心应用层数据,里面都有什么

只是把应用层数据当作一个字符串,构造出一个UDP数据报

3.网络层

网络最主要协议是IP协议

IP协议也会根据自己格式,构造出IP数据报

4.数据链路层

以太网,针对IP数据报,进行下一步分装,再添上数据头和数据尾

5.物理层

硬件设备(网卡)

本质上都是二进制的数据

硬件设备就需要对上述数据进行转换了~~光信号/电信号/电磁波

到这里完成了发送过程

接受方的情况(主机B):

1.物理层(硬件设备,网卡),收到光信号/电信号/电磁波

需要把收到的信号进行解调得到一串0101二进制数据序列,也就是以太网数据帧

这个数据就要被交给上一层,数据链路层

调制:把你要传输的信息放到光电信号中

解调:把光电信号中把信息取出来

2.数据链路层

数据链路层的以太网协议,就会针对这个数据进行解析

此时把载荷部分取出来,交给上层(IP协议)

3.网络层

IP协议针对这个数据报进行解析,去掉IP报头,取出载荷,进一步交给传输层

4.传输层

根据IP报头中的字段,就知道当前这个载荷是一个UDP数据报,交给UDP处理

UDP也是要针对数据报进行解析,去掉报头,去出载荷,进一步的交给应用程序

5.应用层

UDP报头中,有一个字段,目的端口。根据目的端口找到关联的应用程序,就交给这个程序即可

qq收到了这个数据,就会按照QQ的应用层协议,进行解析


主机A,从上到下,依次添加报头的过程,称为封装(打包快递)

主机B,从下到上,依次解析报头的过程,称为分用(拆快递)


2.进行具体的网络编程

写一个应用程序,让这个程序可以使用网络通信(需要调用传输层提供的api)

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

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

无连接相当于 发微信/短信,不需要建立连接,就能进行通信~~

可靠传输的传输效率降低了,不可靠传输效率更高

全双工:一个通道,可以双向通信

半双工:一个通道,只能单向通信


UDP的socket api(按字节来处理)

两个核心的类:

1.DatagramSocket

​ 是一个Socket对象(要进行网络通信,必须得先有socket对象)

DatagramSocket() 在客户端这边使用(客户端使用哪个端口,系统自动分配)

DatagramSocket(int port) 在服务器这边使用(手动指定)

2.DatagramPacket

表示了一个UDP数据报,代表了系统中设定得UDP数据报的二进制结构

手写UDP客户端服务器

//服务器代码

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

//UDP的回显服务器
//客户端发的请求是啥,服务器返回的响应就是啥
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 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.把响应写回给客户端
            //此时需告知网卡,要发的内容是啥,要发给谁
            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();
    }
}
//客户端代码

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

public class UdpEchoClient {
    private DatagramSocket socket = null;
    private String serverIp;
    private int serverPort;

    //参数是服务器的ip 和 服务器的端口
    public UdpEchoClient(String ip,int port) throws SocketException {
        serverIp = ip;
        serverPort = port;
        //这个new操作,就不再指定端口了,让系统自动分配一个空闲端口
        socket = new DatagramSocket();
    }

    //让这个客户端反复的从控制台读取用户输入的内容,把这个内容构造成UDP请求,发给服务器,再读取服务器返回的UDP响应,最终显示在客户端的屏幕上
    public void start() throws IOException {
        Scanner scanner = new Scanner(System.in);
        System.out.println("客户端启动!");
        while (true) {
            //1.从控制台读取用户输入内容
            System.out.print("-> ");
            String request = scanner.next();

            //2.构造请求对象,并发给服务器
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
                    InetAddress.getByName(serverIp),serverPort);
            socket.send(requestPacket);

            //3.读取服务器的响应,并解析出响应内容
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(responsePacket);
            String response = new String(responsePacket.getData(),0,responsePacket.getLength());

            //4.显示到屏幕上
            System.out.println(response);
        }
    }

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

更多的时候,期望,服务器有业务,解决实际问题

如翻译服务器(cat => 猫)

import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;

public class UdpDictServer extends UdpEchoServer{
    private Map<String,String> dict = new HashMap<>();
    public UdpDictServer(int port) throws SocketException {
        super(port);

        dict.put("cat","小猫");
        dict.put("dog","小狗");
        dict.put("fuck","卧槽");
        //可以在这里添加千千万万个单词,使每个单词都有一个对应的翻译
    }

    //是要复用之前的代码,但是又要做出调整
    @Override
    public String process(String request){
        //把对应请求的单词的翻译,给返回回去
        return dict.getOrDefault(request,"该词没有查询到");
    }

    public static void main(String[] args) throws IOException {
        UdpDictServer server = new UdpDictServer(9090);
        //start不需要重新再写一遍了,直接就复用了之前的start
        server.start();
    }
}

TCP提供的api也是主要有两个类。

ServerSocket

Socket

字节流,一个字节一个字节进行传输的

一个tcp数据报,就是一个字节数组 byte[]

TCP版本的回显服务器

进入循环后,要做的事情不是读取客户端的请求,而是先处理客户端的“连接”

客户端代码

1.从控制台读取用户输入

2.把输入的内容构造成请求并发送给服务器

3.从服务器读取响应

4.把响应显示在控制台上

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;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TcpEchoServer {
    private ServerSocket serverSocket = null;

    //此处不应该创建固定线程数目的线程池
    private ExecutorService service = Executors.newCachedThreadPool();

    //这个操作就会绑定端口号
    public TcpEchoServer(int port) throws IOException{
        serverSocket = new ServerSocket(port);
    }

    //启动服务器
    public void start() throws IOException {
        System.out.println("服务器启动!");
        while (true){
            Socket clientSocket = serverSocket.accept();
            //单个线程,不太方便完成这里的一边拉客,一边介绍,需要多线程。主线程专门负责拉客,每次有一个客户端,都创建一个新的线程去负责处理客户端的各种请求
    //     Thread t = new Thread(() -> {
    //           processConnection(clientSocket);
    //       });
    //       t.start();
            //使用线程池解决上述问题
            service.submit(new Runnable(){
                @Override
                public void run(){
                    processConnection(clientSocket);
                }
            });
        }

    }

    //通过这个方法来处理一个连接的逻辑
    private void processConnection(Socket clientSocket){
        System.out.printf("[%s:%d] 客户端上线!\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
        //接下来读取请求,根据请求计算响应,返回响应三步走了
        //Socket 对象内部包含了两个字节流对象,可以把这两字节流对象获取到,完成后续的读写工作
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()){
            //一次连接中,可能会涉及到多次请求/响应
            while (true){
                //1.读取请求并解析,为了读取方便,直接使用Scanner
                Scanner scanner = new Scanner(inputStream);
                if(!scanner.hasNext()){
                    //读取完毕,客户端下线
                    System.out.printf("[%s:%d] 客户端下线!\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
                    break;
                }
                //这个代码暗含一个约定,客户端发来的请求,得是文本数据,同时,还得有空白符作为分割
                String request = scanner.next();

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

                //3.把响应写回给客户端,把OutputStream 使用 PrinterWriter 包裹一下,方便进行发数据
                PrintWriter writer = new PrintWriter(outputStream);
                //  使用 PrintWriter 的 println 方法,把响应返回给客户端
                //  此处使用println,而不是 print 就是为了在结尾加上 \n ,方便客户端读取响应,使用 scanner.next 读取
                writer.println(response);
                //  这里还需要加一个“刷新缓冲区” 操作
                writer.flush();

                //日志,打印当前的请求详情
                System.out.printf("[%s:%d] req: %s, resp: %s\n",clientSocket.getInetAddress().toString(),clientSocket.getPort(),
                        request,response);
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            //在finally中加上close操作,确保当前socket被及时关闭
            try {
                clientSocket.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }

    public String process(String request){
        return request;
    }

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

    }
}
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 {
        socket = new Socket(serverIp,serverPort);
    }

    public void start(){
        System.out.println("客户端启动");

        Scanner scannerConsole = new Scanner(System.in);

        try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()){
            while(true){
                //1.从控制台输入字符串
                System.out.print("-> ");
                String request = scannerConsole.next();

                //2.把请求发给服务器
                PrintWriter printWriter = new PrintWriter(outputStream);
                //   使用println带上换行,后续服务器读取请求,就可以使用 scanner.next来获取了
                printWriter.println(request);
                // 不要忘记flush,确保数据是真的发送出去了
                printWriter.flush();

                //3.从服务器读取响应
                Scanner scannerNetwork = new Scanner(inputStream);
                String response = scannerNetwork.next();

                //4.把响应打印出来
                System.out.println(response);
            }
        } catch(IOException e){
            e.printStackTrace();
        }

    }

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

3.数据组织格式

实际上,上述这样的格式约定,咋样都行。任意进行约定的,只要保证,客户端和服务器遵守同一个约定即可

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lzlm97l6-1692173077216)(网络原理.assets/image-20230816095608741.png)]

1.xml

是以成对的标签,来表示“键值对"信息,同时标签支持嵌套,就可以构成一些更复杂的树形结构数据

响应:

<response>

​    <shops>

​           <name>魏家凉皮</name>

​           <image>1.jpg</image>

​           <distance>1km</distance>

​           <rate>96%</rate>

​           <star>4.7</star>

​     </shop>

​     <shops>

​           <name>肯德基</name>

​           <image>2.jpg</image>

​           <distance>1km</distance>

​           <rate>96%</rate>

​           <star>4.7</star>

​     </shop>

</response>

优点:xml非常清晰的把结构化数据表示出来了

缺点:表示数据需要引入大量的标签,看起来很繁琐,同时也会占用不少的网络带宽

2. json(最流行的一种数据组织格式)

本质也是键值对,看起来,比xml要干净不少 。可读性非常好。方便观察中间结果,好调试问题

终究是需要花费一定的带宽来传输key的名字的

使用 { } 表示 键值对,使用 [ ] 表示 数组。数组的每个元素,可以是数字,可以是字符串,还可以是其他的{ } 或 [ ]

未来在实际开发中会经常用到json格式的数据

//请求
{
  userld:1234
  position:"100 80"
}


//响应
[
  {
    name:'魏家凉皮',
    image:'1.jpg',
    distance:'1km',
    rate:96%,
    star:4.7
  },
  {
    name:'肯德基',
    image:'2.jpg',
    distance:'1km',
    rate:96%,
    star:4.7
  }
]

json对于换行并不敏感,如果这些内容全放在同一行,也是完全合法的

一般网络传输的时候,会对json进行压缩(去掉不必要的空格和换行),同时把所有数据放到一行去。整体占用的带宽就更降低了(影响可读性)

有很多现成的json格式化工具

3.protobuffer(主要用于,对于性能要求更高的场景,牺牲了开发效率,换来运行效率)

谷歌提出的一套,二进制的数据序列化方式

使用二进制的方式,约定某几个字节,表示哪个属性…

最大程度的节省空间(不必传输key,根据位置和长度,区分每个属性的)

二进制数据,无法肉眼直接观察,不方便调试。使用起来比较复杂


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值