回显服务器的制作方法

news2024/11/16 5:37:38

文章目录

    • 客户端和服务器
    • TCP和UDP的特点
    • UDP socket api的使用
      • DatagramSocket
      • DatagramPacket
      • InetSocketAddress API
    • 做一个简单的回显服务器
      • UDP版本的回显服务器
      • TCP版本的回显服务器

客户端和服务器

在网络中,主动发起通信的一方是客户端,被动接受的这一方叫做服务器。

  • 客户端给服务器发送的数据是请求(request)
  • 服务器给客户端返回的数据叫响应(response)

客户端和服务器的交互有多种模式

  • 一问一答: 一个请求对应一个响应 。多在网站开发中用到
  • 一问多答:一个请求对应多个相应。主要涉及“下载”的场景中
  • 多问一答:多个请求队对应一个响应 。涉及到“上传”的场景中
  • 多问多答:多个请求对应多个响应。用到“远程控制”和“远程桌面”中

TCP和UDP的特点

要想进行网络编程,需要使用系统的api,本质上是传输层提供的。传输层用到的协议主要是TCP和UDP。这两个协议的差别挺大,所提供的api也有一定的差别。先从本质上看看TCP和UDP的特点有哪些差别。

  • TCP的特点是 有连接,可靠传输,面向字节流,全双工。
  • UDP的特点是 无连接,不可靠传输,面向数据报,全双工。
  1. 有连接/无连接
    举个例子,打电话,需要对方同意后才能打通电话,打电话的过程需要对方确认接听或者不接听,连接的特点就是双方都能认同,而无连接的规则向发微信,自己只管发送,不用在意对方是否同意接收。同理,计算机中的网络连接,就是通信双方,各自保存对方的信息,客户端就有一些数据结构,记录了谁是自己的服务器,服务器也有一些数据结构,记录了谁是自己的客户端。

  2. 可靠传输/不可靠传输
    网络上存在异常情况是非常多的,无论使用什么硬件技术都无法100%保证数据能从A发送到B。此处所说的可靠传输是指尽可能的完成数据传输,即不管数据有没有传输到,A都能清楚的知道。

  3. 面向字节流/面向数据报
    此处提到的字节流和文件中的字节流是一样的。网络中传输数据的基本单位就是字节。
    面向数据报:每次传输的基本单位是一个数据报(有一系列字节构成的)特定的结构。

  4. 全双工/半双工
    一个信道可以双向通信,是全双工。只能单向通信是半双工。

UDP socket api的使用

socket api的中文意思是"网络编程套接字",操作系统中有一类文件叫socket文件,它抽象了网卡这样的硬件设备,而进行网络通信最核心的硬件设备就是网卡,通过网卡发送数据就是写socket文件,通过网卡接收数据就是读socket文件。在UDP中,核心的api有两个类,分别是DatagramSocket和DatagramPacket。下面看看这两个类的使用和注意事项。

DatagramSocket

这个类的作用主要是对soclet文件的读写,也就是借助网卡发送接收数据。接收发送接收数据的单位就是DatagramSocket.。

datagramSocket的构造方法
在这里插入图片描述

DatagramSocket的方法
在这里插入图片描述

DatagramPacket

UDP是面向数据报的,每次发送接收数据的基本单位,就是一个udp数据报,此时表示了一个UDP数据报

DatagramPacket的构造方法
在这里插入图片描述

DatagramPacket方法
在这里插入图片描述

InetSocketAddress API

在这里插入图片描述

做一个简单的回显服务器

UDP版本的回显服务器

回显服务器是客户端发送什么请求,服务器就返回什么响应。做这个服务器的目的是学习UDP socket
api的使用和理解网络编程中客户端和服务器的基本工作流程

服务器的基本逻辑

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 requestPacket = new DatagramPacket(new byte[4096], 4096);
                socket.receive(requestPacket);
                // 读到的字节数组, 转成 String 方便后续的逻辑处理.
                String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
                // 2. 根据请求计算响应 (对于 回显服务器来说, 这一步啥都不用做)
                String response = process(request);
                // 3. 把响应返回到客户端.
                //    构造一个 DatagramPacket 作为响应对象
                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();
        }
}


  1. 首先创建DatagramSocket对象,接下来要使用socket对象来操作网卡。在创建对象的时候需要手动指定端口。在网络编程中,服务器一般需要手动指定端口,而客户端一般不需要。一个主机的一个端口只能被一个进程绑定,而一个进程可以绑定多个端口。

  2. 一个服务器一般都是24时运行的,直接使用while(true)可以不用退出。很多时候要重启一个服务器可以直接杀死进程。

  3. 此处 receive就从网卡能读取到一个 UDP 数据报就被放到了 requestPacket 对象中其中 UDP 数据报的载荷部分就被放到 requestPacket 内置的字节数组中了。另外报头部分,也会被 requestPacket 的其他属性保存。除了 UDP 报头之外,还有其他信息,比如收到的数据源 IP 是啥
    通过 requestPacket 还能知道数据从哪里来的(源 ip 源端口) 在这里插入图片描述

  4. 基于字节数组构造出 String字节数组里面保存的内容也不一定就是二进制数据,也可能是文本数据把文本数据交给 String 来保存,恰到好处~~
    这里得到的长度是 requestPacket 中的有效长度,不一定是 40964096 是最大长度。一定是要使用有效长度来构造这里的 String使用最大长度就会生成一个非常长的 String 后半部分都是空白。在这里插入图片描述

  5. 通过process()方法构造响应,这是一个回显服务器,直接返回请求就可以。

  6. 通过requestPacket.getSocketAddress())获得对应客户端的ip和端口。是把请求的源ip和源端口作为响应的目的ip和目的端口。在这里插入图片描述

总结

  • 上述代码中,可以看到,UDP 是无连接的通信 UDP socket 自身不保存对端的IP 和端口.而是在每个数据报中有一个~.另外代码中也没有“建立连接”"接受连接”操作
  • 不可靠传输,代码中体现不到的.
  • 面向数据报,send和receive 都是以 DatagramPacket 为单位
  • 全双工:一个 socket 既可以发送又可以接收

客户端的基本逻辑

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;
    private String serverIp;
    private int serverPort;

 // 此处 ip 使用的字符串, 点分十进制风格. "192.168.2.100"
    public UdpEchoClient(String serverIp, int serverPort) throws SocketException {
        //请求的目的IP和目的端口
        this.serverIp = serverIp;
        this.serverPort = serverPort;
        socket = new DatagramSocket();
    }

    public void start() throws IOException {
        System.out.println("客户端启动");
        Scanner scanner = new Scanner(System.in);
        while (true) {
            // 要做四个事情
            System.out.print("-> "); // 表示提示用户接下来要输入内容.
            // 2. 从控制台读取要发送的请求数据.
            if (!scanner.hasNext()) {
                break;
            }
            String request = scanner.next();
            // 3. 构造请求并发送.
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
                    InetAddress.getByName(serverIp), serverPort);
            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("127.0.0.1", 9090);
        client.start();
    }
}

三种DatagramSocket对象的构造方法
在这里插入图片描述
网络通信的基本流程

  1. 服务器启动.启动之后,立即进入 while 循,执行到 receive,进入阻塞,此时没有任何客户端发来请求呢。
  2. 客户端启动.启动之后,立即进入 while 循环,执行到 hasNext 这里进入阻塞,此时用户没有在控制台输入任何内容
  3. 用户在客户端的控制台中输入字符串,按下回车此时 hasNext 阻塞解除,next 会返回刚才输入的内容基于用户输入的内容,构造出一个 DatagramPacket 对象,并进行 send 。send执行完毕之后,继续执行到
    reeive 操作,等待服务器返回的响应数据此时服务器还没返回响应呢,这里也会阻塞)
  4. 服务器收到请求之后,就会从 receive 的阻塞中返回返回之后,就会根据读到的 DataqramPacket 对象,构造 String request, 通过 process 方法构造一个 String response再根据 response 构造一个
    DatagramPacket表示响应对象, 再通过 send 来进行发送给客户端。执行这个过程中,客户端也始终在阻塞等待
  5. 客户端从 receive 中返回执行.就能够得到服务器返回的响应并且打印倒控制台上于此同时,服务器进入下一次循环,也要进入到第二次的 receive 阳塞等待下个请求了

TCP版本的回显服务器

tcp中的长短连接
TCP发送数据时,需要先建立连接,什么时候关闭连接就决定是短连接还是长连接:

  • 短连接:每次接收到数据并返回响应后,都关闭连接,即是短连接。也就是说,短连接只能一次收发数据。
  • 长连接:不关闭连接,一直保持连接状态,双方不停的收发数据,即是长连接。也就是说,长连接可以多次收发数据。

对比以上长短连接,两者区别如下:

  • 建立连接、关闭连接的耗时:短连接每次请求、响应都需要建立连接,关闭连接;而长连接只需要第一次建立连接,之后的请求、响应都可以直接传输。相对来说建立连接,关闭连接也是要耗时的,长连接效率更高。
  • 主动发送请求不同:短连接一般是客户端主动向服务端发送请求;而长连接可以是客户端主动发送请求,也可以是服务端主动发。
  • 两者的使用场景有不同:短连接适用于客户端请求频率不高的场景,如浏览网页等。长连接适用于 客户端与服务端通信频繁的场景,如聊天室,实时游戏等服务器的处理逻辑。
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;

   public TcpEchoServer(int port) throws IOException {
       serverSocket = new ServerSocket(port);
   }

   public void start() throws IOException {
       System.out.println("服务器启动!");
       ExecutorService pool = Executors.newCachedThreadPool();
       while (true) {
           // 通过 accept 方法来 "接听电话", 然后才能进行通信
           Socket clientSocket = serverSocket.accept();
//            Thread t = new Thread(() -> {
//                processConnection(clientSocket);
//            });
//            t.start();

           pool.submit(new Runnable() {
               @Override
               public void run() {
                   processConnection(clientSocket);
               }
           });
       }
   }

   // 通过这个方法来处理一次连接. 连接建立的过程中就会涉及到多次的请求响应交互.
   private void processConnection(Socket clientSocket) {
       System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
       // 循环的读取客户端的请求并返回响应.
       try (InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()) {
           while (true) {
               Scanner scanner = new Scanner(inputStream);
               if (!scanner.hasNext()) {
                   // 读取完毕. 客户端断开连接, 就会产生读取完毕.
                   System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress(), clientSocket.getPort());
                   break;
               }
               // 1. 读取请求并解析. 这里注意隐藏的约定. next 读的时候要读到空白符才会结束.
               //    因此就要求客户端发来的请求必须带有空白符结尾. 比如 \n 或者空格.
               String request = scanner.next();
               // 2. 根据请求计算响应
               String response = process(request);
               // 3. 把响应返回给客户端
               //    通过这种方式可以写回, 但是这种方式不方便给返回的响应中添加 \n
               // outputStream.write(response.getBytes(), 0, response.getBytes().length);
               //    也可以给 outputStream 套上一层, 完成更方便的写入.
               PrintWriter printWriter = new PrintWriter(outputStream);
               printWriter.println(response);
               printWriter.flush();

               System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.getInetAddress(), clientSocket.getPort(),
                       request, response);
           }
       } catch (IOException e) {
           throw new RuntimeException(e);
       } finally {
           try {
               clientSocket.close();
           } catch (IOException e) {
               throw new RuntimeException(e);
           }
       }
   }

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

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

在这里插入图片描述
在这里插入图片描述

  • 在服务器代码中,ServerSocket是创建服务端Socket的api。Socket是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket.
    不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。
  • 当客户端发送请求的时候,内核就会发起建立连接的请求,服务器的内核会配合客户端的工作来建立连接,而内核的连接不是决定性的,还需要应用程序把这个连接接受,通过accept方法来接受连接。accept方法方法是会阻塞等待的,当没有客户端发起请求的时候此时就会阻塞。
  • 上面的操作也表现出Tcp是有连接的。

在这里插入图片描述

  • Tcp是面向字节流的。这里的字节流和文件中的字节流完全一致。使用和文件操作一样的类和方法来针对Tcp Socket的读和写。
  • InputStream是往网卡上读数据,OutputStream是往网卡上写数据。

客户端的处理逻辑

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 和 port 直接传给 socket 对象.
        // 由于 tcp 是有连接的. 因此 socket 里面就会保存好这俩信息.
        // 因此此处 TcpEchoClient 类就不必保存.
        socket = new Socket(serverIp, serverPort);
    }

    public void start() {
        System.out.println("客户端启动!");
        try (InputStream inputStream = socket.getInputStream()) {
            try (OutputStream outputStream = socket.getOutputStream()) {
                Scanner scannerConsole = new Scanner(System.in);
                Scanner scannerNetwork = new Scanner(inputStream);
                PrintWriter writer = new PrintWriter(outputStream);
                while (true) {
                    // 这里的流程和 UDP 的客户端类似.
                    // 1. 从控制台读取输入的字符串
                    System.out.print("-> ");
                    if (!scannerConsole.hasNext()) {
                        break;
                    }
                    String request = scannerConsole.next();
                    // 2. 把请求发给服务器. 这里需要使用 println 来发送. 为了让发送的请求末尾带有 \n
                    //    这里是和服务器的 scanner.next 呼应的.
                    writer.println(request);
                    // 通过这个 flush 主动刷新缓冲区, 确保数据真的发出去了.
                    writer.flush();
                    // 3. 从服务器读取响应. 这里也是和服务器返回响应的逻辑对应.
                    String response = scannerNetwork.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();
    }
}

在TCP回显服务器中,需要注意下面几种情况

  1. 在上面代码中,PrintWriter中内置了缓冲区,IO操作都是很低效的,为了让低效的操作少一些,会引入一个缓冲区,先把写入网卡的数据王道缓冲区中,等达到一定的数量在一次发送出去,但如果发送的数据太少,缓冲区没有满,可能导致数据发送不出去,使用flush方法可以冲刷缓冲区,确保每条消息都能发送出去。

在这里插入图片描述

  1. ServerSocket在整个程序中,只有唯一一个对象,并且这个对象的生命周期伴随着整个程序,这个对象无法提前关闭,只有程序结束,随着进程的销毁一起结束。而clientSocket是每个客户端一个,随着客户端越来越多,如果不释放可能会占满文件描述符表。需要使用close方法关闭。

在这里插入图片描述

  1. 解决多个客户端向一个服务器发送请求的问题

在这里插入图片描述

上面的问题核心思路就是使用多线程,单个线程无法及给客户端提供服务,又能快速调用第二次accept,使用多线程,主线程就负责执行accept,其他线程就负责给客户端提供服务。如果客户端比较多就会频繁的创建销毁线程,就可以使用线程池解决频繁创建销毁线程的问题。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1463606.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Nacos】构建云原生应用的动态服务发现、配置管理和服务管理平台【企业级生产环境集群搭建应用】

基础描述 一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。Nacos 帮助您更敏捷和容易地构建、交付和…

API接口测试工具的使用指南

API接口测试是确保软件系统正常运作的关键步骤。API接口测试工具可以帮助开发人员和测试人员验证API的功能、性能和安全性。本文将介绍API接口测试工具的基本使用方法,以便有效地进行接口测试。 1. 选择合适的API测试工具 在开始API接口测试之前,首先需要…

MySQL-----多表操作

介绍 实际开发中,一个项目通常需要很多张表才能完成。例如:一个商城项目就需要分类表(category)、商品表(products)、订单表(orders)等多张表。且这些表的数据之间存在一定的关系,接下来我们将在单表的基础上,一起学习多表方面的知识。 一 多…

PostgreSQL限制密码的有效期(每次增加180天)

一、创建用户时,指定密码在180天后过期 创建用户时,可以使用如下语句,自动在当前时间的基础上增加180天,作为这个用户密码的有效时间。等到密码到期后或者临近的时候,可以使用SQL修改用户密码,并且重新定义…

echats 时间直方图示例

需求背景 某订单有N个定时任务,每个任务的执行时间已经确定,希望直观的查看该订单的任务执行趋势 查询SQL: select UNIX_TIMESTAMP(DATE_FORMAT(exec_time,%Y-%m-%d %H:%i)) execTime, count(*) from order_detail_task where order_no 2…

【命令行工具kubectl】

如何在k8s的任意节点使用用kubectl # 正常在node节点上是无法执行kubectl命令 [rootk8s-node-01 ~]# kubectl get pods The connection to the server localhost:8080 was refused - did you specify the right host or port?1、将master节点中/etc/kubernetes/,admin.conf拷…

Led灯驱动添加原子操作后驱动程序测试

一. 简介 上一篇文章实现了(Linux驱动代码中) 对 led灯的互斥处理,即使用Linux内核提供的处理并发与竞争的处理方法:原子操作。文章地址如下: Linux内核中并发与竞争的处理方法:原子操作举例-CSDN博客 …

React 模态框的设计(一)拖动组件的设计

春节终结束了,忙得我头疼。终于有时间弄自己的东西了。今天来写一个关于拖动的实例讲解。先看效果: 这是一个简单的组件设计,如果用原生的js设计就很简单,但在React中有些事件必须要多考虑一些。这是一个系列的文章,…

本机windows搭建达摩院与高德联合出品的地理地址自然语言处理模型GMeo实战

文章目录 本机windows搭建达摩院与高德联合出品的地理地址自然语言处理模型GMeo实战简介多模态地理文本预训练模型MGeoGeoGLUE 环境安装ModelScope相关安装conda虚拟环境深度学习基础库安装modelscope模型 测试代码代码输入xlsx文件运行这段测试代码输出xlsx耗时 如果有自定义样…

PLC_博图系列☞基本指令“异或“运算

PLC_博图系列☞基本指令“异或“运算 文章目录 PLC_博图系列☞基本指令“异或“运算背景介绍X:“异或”运算说明参数示例真值表 关键字: PLC、 西门子、 博图、 Siemens 、 异或 背景介绍 这是一篇关于PLC编程的文章,特别是关于西门子的…

Python如何运用turtle绘制阴阳太极图

本文详细分析如何使用Python turtle绘制阴阳太极图,先来分解这个图形,图片中有四种颜色,每条曲线上的箭头表示乌龟移动的方向,首先从中心画一个半圆(红线),以红线所示圆的直径作半径画一个校园&…

Leo赠书活动-18期 《高效使用Redis》

✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉 🍎个人主页:Leo的博客 💞当前专栏: 赠书活动专栏 ✨特色专栏:…

机器学习面试:逻辑回归与朴素贝叶斯区别

逻辑回归与朴素贝叶斯区别有以下几个方面: (1)逻辑回归是判别模型,朴素贝叶斯是生成模型,所以生成和判别的所有区别它们都有。 (2)朴素贝叶斯属于贝叶斯,逻辑回归是最大似然,两种概率哲学间的区别。 (3)朴素贝叶斯需要条件独立假设…

#1.4w字长文#仿抖音项目架构设计与实现

一、项目介绍 本文介绍了一个Web端短视频应用,致力于为用户提供交互友好、功能完备的短视频浏览体验和直播体验。 集成了Gorse推荐算法,旨在为用户提供更个性化的推荐视频流和更权威的热门视频流。接入大模型,通过对视频内容进行语言分析&a…

大数据Kafka--概述

文章目录 概述定义消息队列传统消息队列的应用场景消息队列的两种模式 Kafka基础架构 Kafka快速入门安装部署集群规划集群部署集群启停脚本 Kafka命令行操作主题命令行操作生产者命令行操作消费者命令行操作 概述 定义 消息队列 目前企业中比较常见的消息队列产品主要有 Kafk…

C/C++的内存管理(1)

内存管理 C与C的内存分布C语言中动态内存管理方式回顾C内存管理的方式 C与C的内存分布 我们学习C语言时就知道,储存不同的变量计算机会相应分配不同区块的内存。那为什么要把内存化为不同的区域呢?实质上是为了方便管理 下面我们来看看下面一道例题&…

了解JSON的作用及其方法

什么是json JSON(JavaScript Object Notation)是一种轻量级的数据交换格式采用完全独立编程语言的文本格式存储和表示数据(就是字符串)。它基于JavaScript语法,但可以被多种编程语言使用和解析。JSON以键值对的形式存…

05 类和对象 3

目录 再谈构造函数static成员友元内部类匿名对象拷贝对象时一些编译器优化再次理解封装 1. 再谈构造函数 1.1 构造函数赋值 在创建对象时,编译器调用构造函数,给对象中各个成员变量一个合适的初始值 class Date { public: Date(int year, int month,…

前端基础自学整理|DOM树

DOM,文档对象模型(Document Object Model),简单的说,DOM是一种理念,一种思想,一个与系统平台和编程语言无关的接口,一种方法, 使 Web开发人员可以访问HTML元素!不是具体方…

MySQL语句大全

MySQL语句大全 数据库操作数据表操作往表中插入数据修改表中数据删除表中数据查询表中数据用户管理函数约束 (限制表中的字段值)多表查询事务 数据库操作 -- 显示数据库 show databases;-- 创建一个数据库 demo create database demo;-- 若不存在数据库 demo 则创建一个数据库…