GO:Socket编程

目录

一、TCP/IP协议族和四层模型概述

1.1 互联网协议族(TCP/IP)

1.2 TCP/IP四层模型

1. 网络访问层(Network Access Layer)

2. 网络层(Internet Layer)

3. 传输层(Transport Layer)

4. 应用层(Application Layer)

1.3 特点和作用

二、Socket基础        

2.1 Socket简介

2.2 TCP编程

2.2.1 TCP简介

2.2.2 TCP的关键特性

2.2.3 TCP客户端

2.2.4 TCP服务端

2.3 UDP编程

2.3.1 UDP简介

2.3.2 UDP客户端

2.3.3 UDP服务端


        在学习 Socket之前,我们需要了解、什么是TCP/IP以及如何使用TCP/IP中的Socket 连接实现网络通信。Socket是我们在使用Go语言的过程中会使用到的最底层的网络协议,大部分的网络通信协议都是基于TCP/IP的Socket协议

一、TCP/IP协议族和四层模型概述

1.1 互联网协议族(TCP/IP)

  • 是互联网的基础通信架构
  • 包含整个网络传输协议家族
  • 核心协议:TCP(传输控制协议)和IP(网际互连协议)
  • 提供点对点的链接机制,标准化数据封装、定址、传输、路由和接收

1.2 TCP/IP四层模型

1. 网络访问层(Network Access Layer)

  • 未详细描述,指出主机必须使用某种协议与网络相连

2. 网络层(Internet Layer)

  • 关键部分,使用IP协议
  • 功能:使主机可以发送分组到任何网络
  • 特点:分组可能经由不同网络,到达顺序可能不同

3. 传输层(Transport Layer)

  • 定义两个端到端协议:TCP和UDP
  • TCP:面向连接,提供可靠传输、流量控制、多路复用等
  • UDP:无连接,不可靠传输,用于简单应用

4. 应用层(Application Layer)

  • 包含所有高层协议
  • 主要协议:
    • TELNET(远程终端)
    • FTP(文件传输)
    • SMTP(电子邮件)
    • DNS(域名服务)
    • HTTP(超文本传输)
    • ...

1.3 特点和作用

  • 将软件通信过程抽象为四个层
  • 采用协议堆栈方式实现不同通信协议
  • 简化了OSI七层模型
  • 提供了灵活、可扩展的网络通信框架

二、Socket基础        

        网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个 Socket。建立网络通信连接至少要一对端口号(Socket)。Socket的本质是编程接口(API),对 TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口。如果 说HTTP是轿车,提供了封装或者显示数据的具体形式,那么Socket就是发动机,提供了网络通信 的能力。

2.1 Socket简介

        Socket的英文原义是“孔”或“插座”,作为BSD UNIX的进程通信机制,取后一种意思。 Socket通常也称作“套接字”,用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不 同虚拟机或不同计算机之间的通信。在网络上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。

Socket正 如其英文原义那样,像一个多孔插座。一台主机犹如布满各种插座的房间,每个插座都有一个编 号,有的插座提供220伏交流电,有的提供110伏交流电,有的则提供有线电视节目。客户软件将 插头插到不同编号的插座中,就可以得到不同的服务。

Socket起源于Unix,而Unix的基本哲学之一就是“一切皆文件”,都可以使用如下模式来 操作。

打开 -> 读写write/read -> 关闭close

Socket就是该模式的一个实现,网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件 描述符。Socket的类型有两种:流式Socket和数据报式Socket。流式是一种面向连接的Socket,针 对于面向连接的TCP服务应用;数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用


2.2 TCP编程

2.2.1 TCP简介

        TCP是Transmission Control Protocol的缩写,中文名是传输控制协议。它是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。在简化的计算机网络OSI模型中, 它完成第四层传输层所指定的功能,用户数据报协议(UDP)是同一层内另一个重要的传输协议。

        在互联网协议族中,TCP层是位于IP层之上、应用层之下的中间层。不同主机的应用层之间经常需 要可靠的、像通道一样的连接,但是IP层不提供这样的流机制,而是提供不可靠的包交换。

        TCP是因特网中的传输层协议,使用三次握手协议建立连接。当主动方发出SYN连接请求后, 等待对方回答SYN+ACK,并最终对对方的SYN执行ACK确认。这种建立连接的方法可以防止产生错误的连接。TCP使用的流量控制协议是可变大小的滑动窗口协议。TCP三次握手的过程如下:

这张图描述的是TCP(传输控制协议)的三次握手过程,这是建立TCP连接的标准方法。

1. 第一步 - SYN:

  • 客户端发送一个SYN(同步)包到服务器。
  • SYN=1 表示这是一个同步请求。
  • seq=J 是客户端选择的初始序列号。

2. 第二步 - SYN-ACK:

  • 服务器收到SYN后,回复一个SYN-ACK包。
  • SYN=1 表示这也是一个同步包。
  • ACK=1 表示这是一个确认包。
  • ack=J+1 是对客户端序列号的确认(下一个期望收到的序列号)。
  • seq=K 是服务器选择的初始序列号。

3. 第三步 - ACK:

  • 客户端收到SYN-ACK后,发送一个ACK包作为响应。
  • ACK=1 表示这是一个确认包。
  • ack=K+1 是对服务器序列号的确认。

4. 连接建立:

  • 完成这三步后,TCP连接就建立了。
  • 双方都标记连接为ESTABLISHED状态。

这个过程的目的是:

  • 同步双方的初始序列号。
  • 确保双方都能发送和接收数据。
  • 避免旧的或重复的连接干扰新连接。

这种机制保证了连接的可靠性和数据传输的有序性,是TCP协议可靠传输特性的基础。

2.2.2 TCP的关键特性

  1. 分段传输: 数据被分割成适合的数据块。
  2. 确认机制:
    • 发送方启动定时器等待确认
    • 接收方发送确认
  3. 校验和: 检测传输中的数据变化。
  4. 排序重组: 处理失序到达的数据包。
  5. 去重: 丢弃重复的数据包。
  6. 流量控制: 防止缓冲区溢出。
  7. 可靠传输: 通过以上机制确保数据可靠传输。

2.2.3 TCP客户端

        Go语言提供了net包来实现Socket编程,大部分使用者只需要Dial、Listen和Accept函数提供的基本接口,以及相关的Conn和Listener接口。

        对于网络编程而言,推荐使用log包代替fmt包进行打印信息,log包打印时,会附加打印出 时间,方便调试程序。log.Fatal表示当遇到严重错误时打印错误信息,并停止程序的运行。

对于TCP和UDP网络,地址格式是“host:port”或“[host]:port”,例如:

package mainimport ("log""net"
)func main() {// 尝试连接百度服务器conn, err := net.Dial("tcp", "www.baidu.com:80")// 连接本地端口// conn, err := net.Dial("tcp", ":1234")if err != nil {log.Fatal("连接失败!", err)}defer conn.Close()log.Println("连接成功!")
}

开启了一个对百度服务器的80端口的TCP连接,如果没报错,就表示连接成功,程序运行后输出如下:

2024/07/19 00:10:13 连接成功!

Dial函数在连接时,如果端口未开放,尝试连接就会立刻返回服务器拒绝连接的错误。

尝试连接本地(127.0.0.1)的1234端口,由于该端口未开放任何TCP服务,程序就会抛出连接失败的信息,如下所示:

2024/07/19 00:12:49 连接失败!dial tcp :1234: connectex: No connection could be made because the target machine actively refused it.

有时我们会遇到这种情况:需要连接的TCP服务开放着,但由于网络或者防火墙的原因,导致始终无法连接成功。这时我们需要设置超时时间来避免程序一直阻塞运行,设置超时可以使用 DialTimeout函数。

        HTTP协议页是基于TCP的Socket协议实现的,因此可以使用TCP客户端来请求百度的HTTP服务。

package mainimport ("log""net"
)func main() {// 尝试连接百度服务器conn, err := net.Dial("tcp", "www.baidu.com:80")if err != nil {log.Fatal("连接失败!", err)}defer conn.Close()log.Println("连接成功!")// 发送HTTP形式的内容conn.Write([]byte("GET / HTTP/1.1\r\nHost: www.baidu.com\r\nUser-Agent: curl/7.55.1\r\nAccept: */*\r\n\r\n"))log.Println("发送HTTP请求成功!")var buf = make([]byte, 1024)conn.Read(buf)log.Println(string(buf))
}

        连接百度服务器的80端口,并向80端口发送了HTTP请求包,模拟了一次HTTP请求,百度服务器接收到并成功解析该请求后,就会做出响应,程序运行结果如下:

2024/07/19 00:59:26 连接成功!
2024/07/19 00:59:26 发送HTTP请求成功!
2024/07/19 00:59:27 HTTP/1.1 200 OK
Accept-Ranges: bytes
...
Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link
...

2.2.4 TCP服务端

  • 将服务器代码保存为 server.go,客户端代码保存为 client.go
  • 在一个终端中运行服务器,在另一个终端中运行客户端
  • 在客户端终端中输入消息并按回车发送。你会看到服务器的响应。
  • 你可以运行多个客户端实例来测试多客户端场景。

服务端

// 服务器代码 (server.go)
package mainimport ("bufio""fmt""net""time"
)func main() {// 在8080端口上创建TCP监听器listener, err := net.Listen("tcp", ":8080")if err != nil {fmt.Println("监听错误:", err)return}defer listener.Close() // 确保在函数结束时关闭监听器fmt.Println("服务器正在监听8080端口")for {// 接受新的客户端连接conn, err := listener.Accept()if err != nil {fmt.Println("接受连接错误:", err)continue}fmt.Println("新客户端连接:", conn.RemoteAddr())// 为每个客户端启动一个新的goroutine来处理连接go handleConnection(conn)}
}func handleConnection(conn net.Conn) {defer conn.Close() // 确保在函数结束时关闭连接reader := bufio.NewReader(conn)for {// 设置30秒的读取超时conn.SetReadDeadline(time.Now().Add(30 * time.Second))// 读取客户端发送的消息,直到遇到换行符message, err := reader.ReadString('\n')if err != nil {if netErr, ok := err.(net.Error); ok && netErr.Timeout() {fmt.Println("读取超时,关闭连接")} else {fmt.Println("读取错误:", err)}return}fmt.Printf("收到来自 %s 的消息: %s", conn.RemoteAddr(), message)// 设置10秒的写入超时conn.SetWriteDeadline(time.Now().Add(10 * time.Second))// 向客户端发送确认消息_, err = conn.Write([]byte("已接收: " + message))if err != nil {fmt.Println("写入错误:", err)return}}
}

客户端

// 客户端代码 (client.go)
package mainimport ("bufio""fmt""net""os""time"
)func main() {// 连接到服务器conn, err := net.Dial("tcp", "localhost:8080")if err != nil {fmt.Println("连接错误:", err)return}defer conn.Close() // 确保在函数结束时关闭连接fmt.Println("连接成功,请输入消息,按回车键发送") // 连接成功后的提示// 启动一个goroutine来接收服务器消息go receiveMessages(conn)// 在主goroutine中发送消息sendMessages(conn)
}func receiveMessages(conn net.Conn) {reader := bufio.NewReader(conn)for {// 设置60秒的读取超时conn.SetReadDeadline(time.Now().Add(60 * time.Second))// 读取服务器发送的消息,直到遇到换行符message, err := reader.ReadString('\n')if err != nil {if netErr, ok := err.(net.Error); ok && netErr.Timeout() {fmt.Println("读取超时,未收到服务器消息")continue}fmt.Println("读取错误:", err)return}fmt.Print("服务器: ", message)}
}func sendMessages(conn net.Conn) {scanner := bufio.NewScanner(os.Stdin)for scanner.Scan() {message := scanner.Text()// 设置10秒的写入超时conn.SetWriteDeadline(time.Now().Add(10 * time.Second))// 发送消息到服务器_, err := conn.Write([]byte(message + "\n"))if err != nil {fmt.Println("发送消息错误:", err)return}}
}

2.3 UDP编程

2.3.1 UDP简介

        UDP是User Datagram Protocol的缩写,中文名是用户数据报协议。它是OSI参考模型中一种无连 接的传输层协议,提供面向事务的简单不可靠信息传送服务。

        UDP协议在网络中与TCP协议一样用于处理数据包,是一种无连接的协议。在OSI模型中位于 第四层——传输层,处于IP协议的上一层。UDP有不提供数据包分组、组装和不能对数据包进行排 序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。UDP用来支持那些需 要在计算机之间传输数据的网络应用,包括网络视频会议系统在内的众多的客户/服务器模式的网 络应用。UDP协议从问世至今已经被使用了很多年,虽然其最初的光彩已经被一些类似协议所掩 盖,但是即使是在今天,UDP仍然不失为一项非常实用和可行的网络传输层协议。

2.3.2 UDP客户端

        与TCP客户端类似,创建一个UDP客户端同样使用Dial函数,只需在参数中声明发起的请求协 议为UDP即可

package mainimport ("log""net"
)func main() {// 尝试连接本地1234端口conn, err := net.Dial("udp", ":1234")if err != nil {log.Fatal("连接失败!", err)}defer conn.Close()log.Println("连接成功!")
}

        尝试连接本地UDP的1234端口,该端口是关闭的,打印输出的结果如下:

024/07/19 02:22:08 连接成功!

        由于UDP是无连接的协议,只关心信息是否成功发送,不关心对方是否成功接收,只要消息 报文发送成功,就不会报错,因此会输出连接成功的信息

2.3.3 UDP服务端

        与TCP服务端不同,创建一个UDP服务端无法使用有连接的Listen函数,而要使用无连接的 ListenUDP函数。

        基于UDP的协议有很多,如DNS域名解析服务、NTP网络时间协议等。我们来模拟一个最简单的NTP服务器,每当接收到任意字节的信息,就将当前的时间发送给客户端。

服务器 (server.go):

  1. 在指定端口(8123)上创建UDP监听器。
  2. 使用无限循环持续监听客户端请求。
  3. 当收到任何消息时,获取当前时间并发送回客户端。
// 服务器代码 (server.go)
package mainimport ("fmt""net""time"
)func main() {// 在8123端口上监听UDP连接addr, err := net.ResolveUDPAddr("udp", ":8123")if err != nil {fmt.Println("地址解析错误:", err)return}conn, err := net.ListenUDP("udp", addr)if err != nil {fmt.Println("监听错误:", err)return}defer conn.Close()fmt.Println("NTP服务器正在监听 :8123")for {handleClient(conn)}
}func handleClient(conn *net.UDPConn) {buffer := make([]byte, 1024)// 读取客户端发送的数据_, remoteAddr, err := conn.ReadFromUDP(buffer)if err != nil {fmt.Println("读取数据错误:", err)return}fmt.Printf("收到来自 %s 的请求\n", remoteAddr)// 获取当前时间并格式化currentTime := time.Now().Format(time.RFC3339)// 发送时间信息给客户端_, err = conn.WriteToUDP([]byte(currentTime), remoteAddr)if err != nil {fmt.Println("发送响应错误:", err)}
}

客户端 (client.go):

  1. 连接到指定的服务器地址和端口。
  2. 发送一个简单的消息("获取时间")到服务器。
  3. 等待并接收服务器的响应(当前时间)。
  4. 打印接收到的时间信息。
// 客户端代码 (client.go)
package mainimport ("fmt""net"
)func main() {// 服务器地址serverAddr, err := net.ResolveUDPAddr("udp", "localhost:8123")if err != nil {fmt.Println("地址解析错误:", err)return}// 创建UDP连接conn, err := net.DialUDP("udp", nil, serverAddr)if err != nil {fmt.Println("连接错误:", err)return}defer conn.Close()// 发送任意消息给服务器_, err = conn.Write([]byte("获取时间"))if err != nil {fmt.Println("发送请求错误:", err)return}// 接收服务器响应buffer := make([]byte, 1024)n, _, err := conn.ReadFromUDP(buffer)if err != nil {fmt.Println("接收响应错误:", err)return}// 打印接收到的时间fmt.Printf("服务器时间: %s\n", string(buffer[:n]))
}

使用:

  1. 先运行服务器:go run server.go
  2. 然后在另一个终端运行客户端:go run client.go
服务器时间: 2024-07-19T02:37:05-07:00

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

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

相关文章

WPF+Mvvm 项目入门完整教程(一)

WPF+Mvvm 入门完整教程一 创建项目MvvmLight框架安装完善整个项目的目录结构创建自定义的字体资源下载更新和使用字体资源创建项目 打开VS2022,点击创建新项目,选择**WPF应用(.NET Framework)** 创建一个名称为 CommonProject_DeskTop 的项目,如下图所示:MvvmLight框架安装…

【JavaScript 算法】双指针法:高效处理数组问题

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、算法原理二、算法实现示例问题1&#xff1a;两数之和 II - 输入有序数组示例问题2&#xff1a;反转字符串中的元音字母注释说明&#xff1a; 三、应用场景四、总结 双指针法&#xff08;Two Pointer Technique&#xff…

深入理解Java并发线程阻塞唤醒类LockSupport

LockSupprot 用来阻塞和唤醒线程&#xff0c;底层实现依赖于Unsafe类 该类包含一组用于阻塞和唤醒线程的静态方法&#xff0c;这些方法主要是围绕 park 和 unpark 展开 public class LockSupportDemo1 {public static void main(String[] args) {Thread mainThread Thread.cu…

微信小程序(百战商城)的实战项目的首页的制作及讲解

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

【人工智能】基于香橙派AIpro和昇腾AI计算芯片的面部口罩检测(详细教程)

目录 前言 1.介绍开发板 2.应用场景 3.安装操作系统 3.1 下载工具 3.2 烧录系统 3.3 系统装载 4.配置操作系统 4.1 登录系统账户 4.2 配置网络连接 4.3 查看设备网络 4.4 配置远程连接 5.部署目标检测应用 5.1 准备运行环境 5.2 模型二次训练 ​5.3 热成像温度…

数据结构 day4

目录 思维导图&#xff1a; 学习内容&#xff1a; 1. 链表的引入 1.1 顺序表的优缺点 1.1.1 优点 1.1.2 不足 1.1.3 缺点 1.2 链表的概念 1.2.1 链式存储的线性表叫做链表 1.2.2 链表的基础概念 1.3 链表的分类 2. 单向链表 2.1 节点结构体类型 2.2 创建链表 2.…

pikachu之暴力破解

1基于表单的暴力破解 随便输入然后抓包 选中添加账号密码 添加分别添加payload1&#xff0c;2&#xff0c;的字典 开始攻击 2验证码绕过on server 和基于表单的暴力破解相比&#xff0c;多了一个验证码功能 这个验证码是前端的验证码&#xff08;和前面那个一样选中添加账号密码…

Java小技能:多级组织机构排序并返回树结构(包含每个层级的子节点和业务数据集合)

文章目录 引言I 实体定义1.1 部门1.2 用户组织机构中间表1.3 树状DTOII 抽取组织机构排序方法2.1 树状排序方法2.2 案例III 查询条件构建3.1 根据部门进行权限控制3.2 注入风险引言 需求: 根据组织机构进行数据授权控制,例如控制船舶、船舶设备、摄像头、港区查看权限。 一…

kettle从入门到精通 第七十六课 ETL之kettle kettle连接hive教程

1、群里有小伙伴询问kettle连接hive的demo&#xff0c;今天抽点时间整理下。其实kettle连接hive和连接mysql数据库也是一样的。 1&#xff09;kettle中的lib目录下放hive驱动jar&#xff0c;这里我使用的是kyuubi-hive-jdbc-shaded-1.9.0.jar。 2&#xff09;设置hive连接参数…

pytorch学习(九)激活函数

1.pytorch常用激活函数如下&#xff1a; #ReLU激活函数 #Leaky ReLU激活函数 #Sigmoid激活函数 #Tanh激活函数 #Softmax激活函数 #Softplus2.代码 import torch.nn as nn import torch import numpy from torch.utils.tensorboard import SummaryWriterwriter SummaryWriter…

ModuleNotFoundError: No module named ‘lime‘,lime。 安装 LIME库

LIME LIME 的作用安装 LIME示例代码详细解释 总结 LIME&#xff08;Local Interpretable Model-agnostic Explanations&#xff0c;局部可解释不可知模型&#xff09;是一个Python库&#xff0c;用于解释机器学习模型的预测结果。它通过构建一个简单的、本地的可解释模型来近似…

【BUG】已解决:ModuleNotFoundError: No module named ‘torch‘

已解决&#xff1a;ModuleNotFoundError: No module named ‘torch‘ 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身&#xff0c;就职于医疗科技公司&#xff0c;热衷分享知识&#xff0c;武汉城市…

Unity UGUI 之EventSystem

本文仅作学习笔记与交流&#xff0c;不作任何商业用途 本文包括但不限于unity官方手册&#xff0c;唐老狮&#xff0c;麦扣教程知识&#xff0c;引用会标记&#xff0c;如有不足还请斧正 1.EventSystem是什么&#xff1f; 有需要请查看手册&#xff1a;Unity - 手册&#xff1…

2024.7.19最新详细的VMware17.0.0安装

VM官网VMware - Delivering a Digital Foundation For Businesses。现在官网无法下载&#xff0c;点击会跳转到https://access.broadcom.com/default/ui/v1/signin/ 要注册一个账号&#xff1a; 注册登录以后&#xff0c;点击Please select your identity provider. - Support …

昇思25天学习打卡营第2天 | 快速入门

在快速发展的人工智能领域&#xff0c;深度学习已经成为数据分析和模式识别的核心技术。作为一名深度学习初学者&#xff0c;我有幸通过MindSpore平台进行了实战演练&#xff0c;从数据预处理到模型训练与测试&#xff0c;再到模型保存与加载&#xff0c;经历了一次完整的深度学…

基于SpringBoot+Vue的校园台球厅设备管理系统(带1w+文档)

基于SpringBootVue的校园台球厅设备管理系统(带1w文档) 基于SpringBootVue的校园台球厅设备管理系统(带1w文档) 本次设计任务是要设计一个校园台球厅人员与设备管理系统&#xff0c;这个系统能够满足校园台球厅人员与设备的管理及用户的校园台球厅人员与设备管理功能。系统的主…

彻底卸载360安全卫士的方法

法一&#xff1a; 按下WindowsR键&#xff0c;并输入msconfig, 在“引导”选项卡中选择“安全引导”&#xff0c;并重新启动进入安全模式。此时&#xff0c;重复第一种方法“应用和功能”-“360安全卫士”-“卸载”&#xff0c;在弹出的对话框中残忍的拒绝它的各种令人发指的无…

go-微服务的设计概括

一、微服务到底是什么&#xff1f; 初学者很容易把微服务和分布式混为一谈&#xff0c;但其实二者之间存在非常大的差异&#xff0c;我个人认为主要有以下几点&#xff1a; 分布式主要是一种技术手段&#xff0c;用来保证多个相同的进程能够共同工作而不出错。采用各种复杂的…

基于Ubuntu2310搭建openstack高可用集群B版

openstack-ha 环境初始化安装haproxy安装keepalived数据库集群高可用rabbitmq集群高可用memcache集群配置 keystone高可用glance高可用placement高可用nova高可用neutron高可用horizon高可用 本实验使用两台节点master和node配置haproxy高可用&#xff0c;keepliaved配置主备抢…

IntelliJ IDEA 直接在软件中更新为最新版

当我们的 IDEA 工具许久没有更新&#xff0c;已经拖了好几个版本&#xff0c;想跨大版本更新&#xff0c;比如从2020.2.1 -> 2023.x.x 此时&#xff0c;我们菜单栏点击 Help -> Check for Updates… &#xff0c;右下角会有提示更新&#xff0c;如下图&#xff1a; 点…