Java【网络编程2】详解基于 TCP 协议的 Socket API, 逐行代码解析如何服务器客户端通信(附代码)

news2024/11/26 15:28:47

文章目录

  • 前言
  • 一、认识 Socket(套接字), TCP 协议和 UDP 协议
    • 1, 什么是 Socket(套接字)
    • 2, 浅谈 TCP 协议和 UDP 协议的区别和特点
  • 二、基于 TCP 协议的 Socket API
    • 1, ServerSocket 类
    • 2, Socket 类
  • 三、逐行代码解析网络编程
    • 1, 逐行解析客户端
      • 1.1, 核心成员方法 start()
    • 2, 逐行解析服务器
      • 2.1, 核心成员方法 start()
    • 3, bug 修改
      • 3.1, bug1
      • 3.2, bug2
      • 3.3, 最终运行效果
  • 四、完整代码
    • 1, 客户端
    • 2, 服务器
  • 总结


前言

📕各位读者好, 我是小陈, 这是我的个人主页
📗小陈还在持续努力学习编程, 努力通过博客输出所学知识
📘如果本篇对你有帮助, 烦请点赞关注支持一波, 感激不尽
📙 希望我的专栏能够帮助到你:
JavaSE基础: 从数据类型类和对象, 封装继承多态, 接口, 综合小练习图书管理系统
Java数据结构: 顺序表, 链表, 二叉树, , 哈希表等 (正在持续更新)
JavaEE初阶: 多线程, 网络编程, html, css, js, severlet, http协议, linux等(正在持续更新)

上篇文章介绍了基于 UDP 协议的 Socket API, 以及简单写了一个回显服务器, 实现了服务器和客户端之间的网络通信

本篇将介绍网络编程中 : 基于 TCP 协议的 Socket 套接字的相关知识


提示:是正在努力进步的小菜鸟一只,如有大佬发现文章欠佳之处欢迎批评指点~ 废话不多说,直接上干货!

一、认识 Socket(套接字), TCP 协议和 UDP 协议

以下内容上篇介绍过了, 看过上篇文章的读者可以跳过

上篇提到, 我们程序员进行网络编程主要是在 TCP/UDP 五层网络模型中的应用层, 而数据在网络上传输, 需要进行封装和分用, 其中应用层需要调用传输层提供的 API , 这一组 API 就被称作 Socket API

1, 什么是 Socket(套接字)

概念 : Socket 套接字是由系统提供于网络通信的技术, 是基于 TCP/IP 协议的网络通信的基本操作党员, 基于 Socket 套接字的网络程序开发就是网络编程

要进行网络通信, 需要有一个 socket 对象, 一个 socket 对象对应着一个 socket 文件, 这个文件在 网卡上而不是硬盘上, 所以有了 sokcet 对象才能通过操作内存来操作网卡
在 socket 文件中写数据相当于通过网卡发送数据, 在 socket 文件中读数据相当于通过网卡接收数据

Socket API 分为两类 : 基于 TCP 协议的 API , 和基于 UDP 协议的 API, 下面先认识一下 TCP 协议和 UDP 协议的区别和特点


2, 浅谈 TCP 协议和 UDP 协议的区别和特点

TCP 协议说明UDP 协议说明
有连接通信双方需要刻意保存对方的相关信息无链接通信双方不需要刻意保存对方的信息
可靠传输如果数据发送不成功, 发送方会知道不可靠传输发送方不关心数据是否发送成功
面向字节流发送的数据以字节为单位面向数据报发送的数据以 UDP 数据报为单位
全双工双向通信全双工双向通信

这里只做简单介绍, 这两个协议后续会单独详细介绍


二、基于 TCP 协议的 Socket API

首先要明确 TCP 协议和 UDP 协议的很重要的区别 : TCP 协议是有链接, 面向字节流传输, 主要体现在 : 发送方和接收方在网络通信之间要先建立连接, 并且传输的数据的基本单位是字节

基于 UDP 协议的 Socket API 中, 要分清楚以下两个类 :

类名解释
ServerSocket只能服务器使用, 客户端不能使用, 这个类是在等待客户端发起连接之前不做任何事的"监听器"
Socket服务器或客户端都可以使用, 客户端使用这个类向服务器发起连接之后, 双端都使用这个类进行网络通信

这两个类的联系就是, 服务器启动之后, 先使用 ServerSocket 类等待客户端发来连接请求, 连接成功后服务器和客户端都使用 Socket 类进行通信


1, ServerSocket 类

ServerSocket 类的构造方法 :

方法签名作用
ServerSocket (int port)创建一个 ServerSocket 对象, 一般用于服务器, 需要指定本机端口号

ServerSocket 类的成员方法 :

方法签名作用
Socket accept()开始"监听", 有客户端发来连接请求之后, 返回一个用于服务器使用的 Socket 对象, 如果客户端没有发起连接, 则阻塞等待
void close()关闭 ServerSocket

2, Socket 类

再次说明, Socket 这个类用于客户端, 也可以在服务器与客户端连接之后使用, 无论客户端或服务器使用, 都会保存对端的相关信息

Socket 类的构造方法 :

方法签名作用
Socket(String host, int port)一般用于客户端, 需要指定服务器的 IP 地址和端口号
void close()用于关闭 ServerSocket

Socket 类的成员方法 :
由于 TCP 协议是面向字节流, 所以有两个关于字节流输入输出的成员方法

方法签名作用
InputStream getInputStream()获取 Socket 的字节输入流
OutputStream getOutputStream()获取 Socket 的字节输出流
InetAddress getInetAddress()获取对端的 IP 地址
InetAddress getPort()获取对端的端口号

调用 getInputStream() 和 getOutputStream() 这个两个方法, 就可以通过字节流对象, 从网卡中读写数据
getInputStream()返回的对象用来输入(读), 从网卡读数据到内存(从网卡接收数据)
getOutputStream返回的对象用来输出(写), 从网卡写数据到内存(从网卡发送数据)

先对上述 API 有个印象即可, 接下来逐行解析如何从 0 到 1 地进行客户端和服务器之间地网络编程, 代码敲完之后再消化吸收


三、逐行代码解析网络编程

下面我们还是写一个最简单的客户端服务器网络通信模型 : 客户端给服务器发送什么请求, 服务器就给客户发送什么响应(这是最简单但是毫无意义的回显服务器, 只是方便熟悉 TCP Socket 的 API 使用)

客户端和服务器各自为一个进程在运行, 双方互不干涉(当然我们现在要写的客户端服务器程序是在同一台主机上的)

一定是服务器先启动, 一直等待客户端发来请求, 所以按照时间顺序, 代码逻辑应该如下所示 :

客户端服务器
/1, 启动服务器, 构造 ServerSocket 对象, 调用 accept() 时刻准备和客户端连接
2, 构造 Socket 对象即为发起连接/
/3, 连接成功, 通过 accept() 的返回值得到 Socket 对象
4, 把请求写入网卡
/5, 从网卡读取请求
/6, 处理请求
/7, 把响应写入网卡
8, 从网卡读取响应/

有了这个思路, 下面正式开始使用上述 API 进行网络编程


1, 逐行解析客户端

创建一个类 : TCPEchoClient 作为客户端

成员属性 :
需要定义一个 Scoket 对象来进行和服务器的通信

public class TCPEchoClient {
	// 成员属性
    private Socket socket = null;
}

构造方法 :
用于实例化客户端的 socket 对象, 别忘了需要绑定服务器的 IP 地址和端口号

public class TCPEchoClient {
	// 成员属性
    private Socket socket = null;
    
    // 构造方法
	public TCPEchoClient(String serverIP, int serverPort) throws IOException {
        socket = new Socket(serverIP, serverPort);
    }
}

main 方法 :
1, 构造 tcpEchoClient 对象, 由于服务器在本机, IP 地址为"127.0.0.1", 端口号随意指定 [1024, 65535] 之间的任意一个
2, 调用 TCPEchoClient 类的核心成员方法 start(), 这个方法实现了客户端的核心逻辑

public class TCPEchoClient {
	// 成员属性
    private Socket socket = null;
    
    // 构造方法
	public TCPEchoClient(String serverIP, int serverPort) throws IOException {
        socket = new Socket(serverIP, serverPort);
    }
    
	// main 方法
    public static void main(String[] args) throws IOException {
        TCPEchoClient tcpEchoClient = new TCPEchoClient("127.0.0.1", 9999);
        tcpEchoClient.start();
    }
}

1.1, 核心成员方法 start()

1️⃣构造一个 Scanner 对象, 从控制台输入字符串, 这个字符串当作请求的内容
2️⃣核心逻辑在一个 while(true) 循环中, 实现多次发送请求

public void start() {
        Scanner in = new Scanner(System.in);
        // 发送多个请求
        while (true) {
                
        }
    }
}

由于TCP协议是面向字节流传输, 所以为了方便读写数据, 我们把字节流转化成字符流处理

所以在进入 while 循环之前, 先构造字符流的输入输出对象

public void start() {
        Scanner in = new Scanner(System.in);
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            // 把字节流转换成字符流
            PrintWriter printWriter = new PrintWriter(outputStream);
            Scanner inFromSocket = new Scanner(inputStream);

            // 发送多个请求
            while (true) {
            
            }
       } catch (IOException e) {
            e.printStackTrace();
       }
}

getInputStream()返回的对象用来输入(读), 从网卡读数据到内存(从网卡接收数据)
getOutputStream返回的对象用来输出(写), 从网卡写数据到内存(从网卡发送数据)

然后每次进入循环, 主要有两个操作 : 1, 把请求写入网卡 2, 把响应从网卡中读出来, 写的使用调用 println(), 读的时候调用 next(), 这样能以空白符为结束标志进行读写数据

图解如下 :
在这里插入图片描述


2, 逐行解析服务器

创建一个类 TCPEchoServer 作为服务器

成员属性 :
需要定义一个 ServerSocket 对象, 用来等待客户端发来连接的"监听器"

public class TCPEchoServer {
	// 构造方法
    private ServerSocket serverSocket = null;
}

构造方法 :
用于实例化客户端的 ServerSocket 对象, 别忘了需要绑定本机端口号

public class TCPEchoServer {
	// 构造方法
    private ServerSocket serverSocket = null;
    
	// 构造方法
    public TCPEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
}

main 方法 :
1, 构造 tcpEchoServer 对象, 需要绑定端口号, 必须和客户端那边绑定的一致
2, 调用 tcpEchoServer 类的核心成员方法 start(), 这个方法实现了服务器的核心逻辑

public class TCPEchoServer {
	// 构造方法
    private ServerSocket serverSocket = null;
    
	// 构造方法
    public TCPEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
    
	// main 方法
    public static void main(String[] args) throws IOException {
        TCPEchoServer tcpEchoServer = new TCPEchoServer(9999);
        tcpEchoServer.start();
    }
}

2.1, 核心成员方法 start()

由于 TCP 是有连接的传输协议, 所以服务器在和客户端连接之前, 要先和客户端建立连接, 也就是调用 accept(), 连接成功之后, 服务器就可以处理这个连接了

如果有多个服务器来和客户端连接, 服务器就需要处理多个连接, 所以把上述过程写在 while(true) 中

    public void start() throws IOException {
        while (true) {
            // 建立连接 返回一个 Socket 对象
            Socket socket = serverSocket.accept();

            // 处理连接到的这个客户端
            processConnection(socket);
        }
    }

处理连接的过程其实就是从网卡中读取数据, 处理响应, 再把响应写回网卡, 我们把这个过程封装成 processConnection(Socket socket);

注意 : 调用 accept() 的是 ServerSocket 的对象, 而这个方法的返回值是Socket, 上面已经强调过了

接下来解析 processConnection() 的过程

和客户端一样, 要先把字节流转化成字符流, 方便读写数据

	private void processConnection(Socket socket) {
        System.out.println(socket.getInetAddress() + " + " + socket.getPort() + " + " + "此客户端上线");
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream() ) {
            PrintWriter printWriter = new PrintWriter(outputStream);
            Scanner inFromSocket = new Scanner(inputStream);

            // 处理多个请求
            while(true) {
            
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

getInputStream()返回的对象用来输入(读), 从网卡读数据到内存(从网卡接收数据)
getOutputStream返回的对象用来输出(写), 从网卡写数据到内存(从网卡发送数据)

然后就是while循环, 进入循环后主要就三个操作: 1, 从网卡中读取数据 2, 处理响应 3, 再把响应写回网卡

图解如下 :
在这里插入图片描述

看到这里, 应该感受到了 TCP 和 UDP 的不同之处体现在哪了
首先是 TCP 的服务器需要先使用 ServerSocket 建立连接, 建立连接之后服务器和客户端都是用 Socket 进行通信
通信时, TCP 进行传输使用的是字节流, 直接从网卡读写, 但我们可以转化成字符流, 而 UDP 进行传输是把数据封装成 DatagramPacket(数据报), 再进行发送和接收


3, bug 修改

3.1, bug1

上述代码中, 有个隐性的严重的 bug, 由于服务器可能是处理多个客户端连接, 那么处理完客户端 A 后, 服务器这个进程不一定会结束, 很有可能还要处理客户端 B

所以服务器和某个客户端进行通信时打开的 Socket 文件就必须在 finally 语句块中调用 close(), 以避免内存资源泄露, 修改后的代码如下 :

    private void processConnection(Socket socket) throws IOException {
        System.out.println(socket.getInetAddress() + " + " + socket.getPort() + " + " + "此客户端上线");
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream() ) {
            // 处理多个请求
            while(true) {
                
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            socket.close();
        }
    }

客户端那边不需要调用 close() 是因为在当前场景下, 客户端的 Socket 生命争取伴随着整个客户端进程, 不会出现频繁创建 Socket 但没有 close 导致内存资源泄露


3.2, bug2

还有一个显性的 bug, 我们首先打开两个客户端, 步骤如下 :
在这里插入图片描述
在这里插入图片描述

然后先运行服务器, 再运行两个客户端, 观察运行效果 :
1 号客户端 :
在这里插入图片描述
2 号客户端 :
在这里插入图片描述
服务器 :
在这里插入图片描述

会发现, 第二个开启的客户端并没有和服务器成功通信, 这是因为, 我们的服务器处理多个连接时, 是在一个while循环中, 如果第一个连接的客户端没有下线, 就不会接收第二个客户端的连接

    public void start() throws IOException {
        while (true) {
            // 建立连接 返回一个 Socket 对象
            Socket socket = serverSocket.accept();

            // 处理连接到的这个客户端
            processConnection(socket);
        }
    }

正确的代码应该是, 每连接成功一个客户端, 就开启一个线程来处理这个连接, 修改后的代码如下 :

    public void start() throws IOException {
        while (true) {
            // 建立连接 返回一个 Socket 对象
            Socket socket = serverSocket.accept();

            // 处理连接到的这个客户端
            Thread thread = new Thread( () -> {
                try {
                    processConnection(socket);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
            // 别忘了调用 start() 启动线程
            thread.start();
        }
    }

3.3, 最终运行效果

1 号客户端 :
在这里插入图片描述

2 号客户端 :
在这里插入图片描述

服务器 :
在这里插入图片描述


四、完整代码

1, 客户端

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() {
        Scanner in = new Scanner(System.in);
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            // 把字节流转换成字符流
            PrintWriter printWriter = new PrintWriter(outputStream);
            Scanner inFromSocket = new Scanner(inputStream);

            // 发送多个请求
            while (true) {
                // 1,从控制台输入字符串
                String requestString = in.next();

                // 2,写入请求
                printWriter.println(requestString);
                printWriter.flush();

                // 3,读取请求
                String responseString = inFromSocket.next();

                // 控制台 打印请求字符串 + 响应字符串
                System.out.println(requestString + " + " + responseString);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        TCPEchoClient tcpEchoClient = new TCPEchoClient("127.0.0.1", 9999);
        tcpEchoClient.start();
    }
}

2, 服务器

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class TCPEchoServer {
    private ServerSocket serverSocket = null;

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

    public void start() throws IOException {
        while (true) {
            // 建立连接 返回一个 Socket 对象
            Socket socket = serverSocket.accept();

            // 处理连接到的这个客户端
            Thread thread = new Thread( () -> {
                try {
                    processConnection(socket);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
            // 别忘了调用 start() 启动线程
            thread.start();
        }
    }

    private void processConnection(Socket socket) throws IOException {
        System.out.println(socket.getInetAddress() + " + " + socket.getPort() + " + " + "此客户端上线");
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream() ) {
            PrintWriter printWriter = new PrintWriter(outputStream);
            Scanner inFromSocket = new Scanner(inputStream);

            // 处理多个请求
            while(true) {
                if (!inFromSocket.hasNext()) {
                    System.out.println(socket.getInetAddress() + " + " + socket.getPort() + " + " + "此客户端下线");
                    break;
                }

                // 1,读取请求
                String requestString = inFromSocket.next();

                // 2,处理请求
                String responseString = process(requestString);

                // 3,写入响应
                printWriter.println(responseString);
                printWriter.flush();

                // 控制台打印 客户端IP地址 + 客户端端口号 + 请求字符串 + 响应字符串
                System.out.println(socket.getInetAddress() + " + " + socket.getPort() + " + " + requestString + " + " + responseString);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            socket.close();
        }
    }

    private String process(String requestString) {
        return requestString;
    }

    public static void main(String[] args) throws IOException {
        TCPEchoServer tcpEchoServer = new TCPEchoServer(9999);
        tcpEchoServer.start();
    }
}

总结

以上就是本篇的全部内容, 主要介绍了 : 基于 TCP协议的 Socket API , 以及利用这些 API 写了一个最简单但无意义的客户端服务器网络通信程序

再回顾一下, Socket 类的成员方法 :
由于TCP协议是面向字节流, 所以有两个成员方法是关于字节流输入输出的

方法签名作用
InputStream getInputStream()获取 Socket 的字节输入流
OutputStream getOutputStream()获取 Socket 的字节输出流
InetAddress getInetAddress()获取对端的 IP 地址
InetAddress getPort()获取对端的端口号

如果本篇对你有帮助,请点赞收藏支持一下,小手一抖就是对作者莫大的鼓励啦😋😋😋~


上山总比下山辛苦
下篇文章见

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

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

相关文章

三维空间与位姿变换

在使用相机或者机械臂的过程中,经常会用到三维空间变换,大部分时候我们可以使用TF去获取空间中的位姿关系,但是也存在一些时候无法使用现有TF表示的情况,例如:使用相机识别工件时,已知通过图像确定了相机到…

C++ - RBTree

前面的文章中我们讲述了以二叉搜索树为基础的AVL树,本文中我们将继续讲一种二叉搜索树为基础的红黑树。 红黑树的概念 红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或 Black。 通过对任何一条…

冲量在线出席2023鲲鹏昇腾开发者峰会,联合鲲鹏打造可信AIGC一体机,共筑产业数字根基

近日,以“创未来 享非凡”为主题的2023鲲鹏昇腾开发者峰会在东莞松山湖举办。鲲鹏昇腾开发者峰会是面向ICT领域开发者的技术盛会,旨在打造生态伙伴、开发者学习、交流的平台,帮助开发者深入了解鲲鹏、昇腾全栈技术,加速行业技术、…

超聚变携手冲量在线打造可信AIGC计算联合解决方案:软硬件高效协同之跃

金融行业作为全球经济的核心引擎,不断变革和创新是其发展的常态,在算力这一日趋成为数字经济时代的新型生产力的趋势下,围绕金融业数字化,业界展开了新一轮探索。 近日,2023中国国际金融展(简称&#xff1…

uniapp app 实现qq登录、微信登录

一、申请 uniapp qq登录流程: 开通 | uni-app官网 申请微信登录可前往微信开发平台:微信开放平台 uniapp 微信登录流程: uni-app官网 申请qq登录可前往qq互联:QQ互联官网首页 这些都可以请运维同学帮我们申请,前…

【Linux】驱动内核调试,是需要几板斧的

目录 前言: 一、基础打印工具 (1)printk---最常用 ①Log Buffer: ②Console: ③RAM Console: (2)动态打印 ①动态打印与printk之间的区别联系 ②动态打印常用的例子 ③动态打印转为pri…

推荐系统---AUC / NDGG

目录: ROC / AUC1:坐标含义(横坐标)FPR:伪阳性率,分类器 “分类错误的负样本个数” 占 “总负样本个数” 的比例。(纵坐标)TPR:真阳性率,分类器 “分类正确的…

centos 7.6 安装mysql 5.7.35

centos 7.6 安装mysql 5.7.35 1、下载mysql安装包2、安装文档3、安装MySQL包4、安装后形成的配置文件和程序位置5、安装后设置5.1、修改MySQL root账户默认密码5.2、关闭系统防火墙 6、使用mysql 5.7.35 数据库6.1、命令行登录MySQL 5.7.35 数据库6.2、navicat连接mysql 5.7.35…

酒精和肠内外健康:有帮助还是有害?

谷禾健康 酒精与健康 饮酒作为一种特殊的文化形式,在我们国家有其独特的地位,在几千年的发展中,酒几乎渗透到日常生活、社会经济、文化活动之中。 据2018年发表的《中国饮酒人群适量饮酒状况》白皮书数据显示,中国饮酒人群高达6亿…

MS5208数模转换器可pin对pin兼容DAC128S085

DAC128S085 是一款功能齐全的通用八通道 12 位电压输出数模转换器 (DAC),可采用 2.7V 至 5.5V 单电源供电,3V 时功耗为 1.95mW,5 V 时功耗为 4.85mW。DAC128S085 采用 16 引脚 WQFN 封装和 16 引脚 TSSOP 封装。WQFN 封…

Convolutional Neural network(卷积神经网络)

目录 Why CNN for Image? The whole CNN structure Convolution(卷积) Max Pooling Flatten CNN in Keras What does CNN learn? what does filter do what does neuron do what about output Deep Dream Application Pla…

数据库缓存服务——NoSQL之Redis配置与优化

一、缓存概念 缓存是为了调节速度不一致的两个或多个不同的物质的速度,在中间对速度较慢的一方起到加速作用,比如CPU的一级、二级缓存是保存了CPU最近经常访问的数据,内存是保存CPU经常访问硬盘的数据,而且硬盘也有大小不一的缓存…

测试用例的设计方法

目录 测试用例的设计方法 等价类: 等价类分为有效等价类与无效等价类 分类树 边界值: 语法测试 正面测试: 负面测试: 判定表测试 因果图: 场景法: 随机测试: 希望能起到帮助&#xf…

关于使用pyinstaller来打包PySide2程序中的问题

打包 pyinstaller 02.py --noconsole --hidden-import PySide2.QtXml 报错0:The ‘pathlib‘ package is an obsolete backport of a standard library package 分析:这个是因为笔者使用的conda的集成环境,这里面自带了打包程序&#xff0c…

Cocos creator小游戏实现套牛小游戏资源及代码

Cocos creator实现套牛小游戏资源及代码 一 安装CocosDashBoard二 新建2D项目RunCow1、管理项目目录2、搭建界面 三 上线微信小游戏1、上线微信小游戏2、Cocos Creator代码打包上传3、上线微信小游戏出现问题 Cocos creator小游戏实现套牛小游戏资源及代码 最近在学习Cocos Cre…

23案例P135-员工部门增删改查实现

一、准备工作 需要完成tlias的部门管理和员工管理 创建tlias数据库,导入 -- 部门管理 create table dept(id int unsigned primary key auto_increment comment 主键ID,name varchar(10) not null unique comment 部门名称,create_time datetime not null commen…

软件I2C读写MPU6050代码

1、硬件电路 SCL引到了STM32的PB10号引脚,SDA引到了PB11号引脚软件I2C协议: 用普通GPIO口,手动反转电平实现协议,不需要STM32内部的外设资源支持,故端口是可以任意指定MPU605在SCL和SDA自带了两个上拉电阻,…

漏刻有时地理信息系统说明文档(LOCKGIS、php后台管理、三端一体PC-H5-微信小程序、百度地图jsAPI二次开发、标注弹窗导航)

漏刻有时地理信息系统LOCKGIS 前言一、运行环境(一)环境检查(二)权限检查(三)函数支持(四)域名相关 二、核心代码(一)坐标展示(二)实时…

Excel快捷键大全(2023最新版总结)

案例:Excel快捷键大全 【作为一名打工人,我总是要用到Excel表格,大家平常在使用Excel时都有什么比较好用的快捷键推荐吗?】 Excel是一款功能强大的电子表格软件,可以用于数据管理、计算、分析和报表生成等多种任务。…

HDFS的数据流

1.HDFS写数据流程 (1)客户端通过Distributed FileSystem模块向NameNode请求上传文件,NameNode检查目标文件是否已存在,父目录是否存在。 (2)NameNode返回是否可以上传。 (3)客户端…