【Gin框架入门到精通系列15】Gin框架中的WebSocket实时通信

[go]引入websocket并基于redis实现消息发送 消息通信功能一共三步1、建立路由接口2、初始化信息3、循环监听通道读取消息和循环读取通道发送消息。 阅读详情
AI摘要

本文聚焦Gin框架中的WebSocket实时通信。先介绍WebSocket协议原理、特点、与HTTP区别及工作机制,接着说明Go语言对其支持,重点展示在Gin框架集成该功能,构建实时聊天应用,还添加私聊、消息持久化等高级功能,最后给出连接管理、安全及性能优化技巧。

📚 原创系列: “Gin框架入门到精通系列”

🔄 转载说明: 本文最初发布于"Gopher部落"微信公众号,经原作者授权转载。

🔗 关注原创: 欢迎扫描文末二维码,关注"Gopher部落"微信公众号获取第一手Gin框架技术文章。

📑 Gin框架学习系列导航

本文是【Gin框架入门到精通系列15】的第15篇 - Gin框架中的WebSocket实时通信

高级特性篇
  1. Gin框架中的国际化与本地化
  2. Gin框架中的WebSocket实时通信👈 当前位置
  3. Gin框架的优雅关闭与热重启
  4. Gin框架的请求限流与熔断

🔍 查看完整系列文章

📖 文章导读

在现代Web应用中,实时通信已成为提升用户体验的关键因素。传统的HTTP协议基于请求-响应模式,不适合实时交互场景。WebSocket作为一种新的网络协议,为Web应用提供了全双工通信能力,使服务器能够主动向客户端推送数据,实现真正的实时通信。

一、引言

1.1 知识点概述

在现代Web应用中,实时通信已成为提升用户体验的关键因素。传统的HTTP协议基于请求-响应模式,不适合实时交互场景。WebSocket作为一种新的网络协议,为Web应用提供了全双工通信能力,使服务器能够主动向客户端推送数据,实现真正的实时通信。

本文将详细介绍WebSocket协议的原理,并展示如何在Gin框架中集成WebSocket功能,构建一个功能完善的实时聊天应用。通过学习本文内容,你将掌握:

  1. WebSocket协议的基本原理和工作机制
  2. WebSocket与HTTP的区别和联系
  3. 在Gin框架中集成gorilla/websocket库
  4. 实现WebSocket连接的建立、消息发送和接收
  5. 构建一个完整的实时聊天室应用
  6. WebSocket连接管理和性能优化策略

1.2 学习目标

完成本篇学习后,你将能够:

  • 理解WebSocket协议的技术原理和应用场景
  • 在Gin框架中正确集成WebSocket功能
  • 实现基本的实时通信功能,如广播、私聊等
  • 处理WebSocket连接的生命周期管理
  • 开发实时应用的常见模式和最佳实践

1.3 预备知识

在学习本文内容前,你需要具备以下知识:

  • 熟悉Go语言基础语法
  • 了解Gin框架的基本使用
  • 掌握HTTP协议的基本知识
  • 基本的并发编程概念
  • 了解通道(Channel)和Goroutine的使用

二、理论讲解

2.1 WebSocket协议概述

2.1.1 WebSocket协议的起源与发展

WebSocket协议最初由Ian Hickson在2008年提出,并于2011年成为RFC 6455标准。该协议的设计目的是解决Web应用中的实时通信问题,为浏览器和服务器之间提供一种更有效的通信机制。

在WebSocket出现之前,开发者通常使用以下技术模拟实时通信:

  • 轮询(Polling): 客户端定期向服务器发送请求,检查是否有新数据
  • 长轮询(Long Polling): 服务器保持请求开放直到有新数据或超时
  • 服务器发送事件(Server-Sent Events): 允许服务器推送数据到客户端,但仍基于HTTP

这些方法都有各自的局限性,如效率低下、资源消耗大、延迟高等。WebSocket协议通过建立持久连接,解决了这些问题,使真正的双向实时通信成为可能。

2.1.2 WebSocket的技术特点

WebSocket具有以下核心特性:

  1. 全双工通信: 支持客户端和服务器之间的双向通信,双方可以同时发送和接收数据
  2. 单一TCP连接: 通信双方建立一次连接后保持长期开放,避免频繁的连接建立和断开
  3. 低延迟: 数据传输的延迟显著降低,适合实时应用
  4. 高效传输: 相比HTTP协议,WebSocket的数据包头部更小,减少了带宽消耗
  5. 基于事件的模型: 通过事件监听机制处理消息,更符合实时应用的设计模式
  6. 原生支持: 现代浏览器都内置支持WebSocket协议,无需额外插件
2.1.3 WebSocket与HTTP的区别与联系

WebSocket与HTTP的关系密切但也有显著区别:

联系:

  • WebSocket使用HTTP进行初始握手
  • 使用相同的端口(80和443),便于穿透防火墙
  • URI模式相似(ws://和wss://对应http://和https://)

区别:

特性 HTTP WebSocket
通信方式 单向(请求-响应) 双向(全双工)
连接特性 无状态,短连接 有状态,长连接
数据格式 基于文本的请求和响应 支持文本和二进制数据
开销 每次请求都有HTTP头部 建立连接后的消息开销小
实时性 依赖轮询或其他技术 原生支持实时推送
状态管理 无内置状态,依赖Cookie等 连接本身维护状态

2.2 WebSocket的工作原理

2.2.1 WebSocket握手过程

WebSocket连接建立需要通过一个特殊的HTTP握手过程,主要步骤如下:

  1. 客户端发起请求:

    GET /chat HTTP/1.1
    Host: server.example.com
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
    Origin: http://example.com
    Sec-WebSocket-Version: 13
    
  2. 服务器响应:

    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
    
  3. 连接建立:
    一旦握手成功,HTTP协议升级为WebSocket协议,之后的通信不再使用HTTP,而是使用WebSocket的数据帧格式。

关键字段解释:

  • Upgrade: websocket: 表示请求升级到WebSocket协议
  • Connection: Upgrade: 告知服务器处理协议升级
  • Sec-WebSocket-Key: 客户端生成的随机密钥
  • Sec-WebSocket-Accept: 服务器根据客户端密钥计算的响应值
2.2.2 WebSocket数据帧格式

WebSocket通信使用特定的数据帧格式,与HTTP完全不同:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------- - - - - - - - - - - - - - - - +
:                     Payload Data continued ...                :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|                     Payload Data continued ...                |
+---------------------------------------------------------------+

重要组成部分:

  • FIN(1位): 标识这是否为消息的最后一个片段
  • Opcode(4位): 定义数据的类型(0x1为文本,0x2为二进制)
  • MASK(1位): 表示有效载荷是否经过掩码处理
  • Payload length(7位、7+16位或7+64位): 有效载荷的长度
  • Masking-key(0或4字节): 用于解码有效载荷的密钥
  • Payload data: 实际传输的数据内容
2.2.3 WebSocket生命周期

WebSocket连接的完整生命周期包括以下阶段:

  1. 连接建立: 通过HTTP握手升级到WebSocket协议
  2. 数据传输: 双方可以随时发送或接收消息
  3. 心跳检测: 定期发送Ping/Pong帧确保连接活跃
  4. 错误处理: 处理网络异常、超时等错误情况
  5. 连接关闭: 任一方可以发送关闭帧,完成有序关闭

关闭码(Close Code)在关闭连接时用于表明关闭原因:

  • 1000: 正常关闭
  • 1001: 端点离开(如服务器关闭或浏览器导航离开)
  • 1002: 协议错误
  • 1003: 接收到不可接受的数据类型
  • 1006: 异常关闭(连接非正常终止)
  • 1007: 数据类型不一致
  • 1008: 策略违规
  • 1009: 消息过大
  • 1010: 需要扩展(客户端)
  • 1011: 意外情况(服务器)

2.3 WebSocket应用场景

WebSocket技术特别适合以下应用场景:

2.3.1 实时通信应用
  • 聊天应用: 即时消息传递、群聊、私聊
  • 协作工具: 多人编辑、实时协作文档
  • 社交媒体: 实时通知、动态更新
  • 客户支持系统: 实时客服聊天
2.3.2 实时数据更新
  • 金融应用: 股票价格、货币汇率实时更新
  • 体育应用: 比分实时更新、赛事直播
  • 监控系统: 服务器状态、网络流量实时监控
  • 物联网应用: 传感器数据实时显示
2.3.3 多人在线游戏
  • 多人对战游戏: 实时同步游戏状态
  • 在线棋牌游戏: 玩家间的实时交互
  • 游戏大厅: 在线状态、匹配系统
2.3.4 流媒体应用
  • 实时视频会议: WebRTC结合WebSocket实现信令
  • 直播平台: 弹幕系统、观众互动
  • 实时通知系统: 推送重要事件和通知

2.4 Go语言中的WebSocket支持

Go语言社区提供了多个WebSocket库,其中最常用的是gorilla/websocket。

2.4.1 gorilla/websocket库简介

gorilla/websocket是Go语言中最流行的WebSocket实现,提供了完整的WebSocket协议支持,特点包括:

  • 完全实现RFC 6455标准
  • 简洁易用的API
  • 高性能设计
  • 支持各种WebSocket特性(子协议协商、压缩等)
  • 广泛的测试覆盖
  • 活跃的社区维护
2.4.2 其他WebSocket实现

除了gorilla/websocket,Go还有其他WebSocket库:

  • golang.org/x/net/websocket: Go标准库的子包,但功能较简单,不推荐用于生产环境
  • nhooyr.io/websocket: 较新的库,注重性能和安全性
  • gobwas/ws: 专注于性能的低级WebSocket库
2.4.3 Gin框架中集成WebSocket

Gin本身不直接提供WebSocket支持,但可以轻松集成gorilla/websocket库。集成的基本步骤:

  1. 安装gorilla/websocket:

    go get github.com/gorilla/websocket
    
  2. 在Gin路由中设置WebSocket处理器:

    var upgrader = websocket.Upgrader{
         
         
        ReadBufferSize:  1024,
        WriteBufferSize: 1024,
        CheckOrigin: func(r *http.Request) bool {
         
         
            return true  // 允许所有跨域请求,生产环境应当限制
        },
    }
    
    func handleWebSocket(c *gin.Context) {
         
         
        conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
        if err != nil {
         
         
            return
        }
        defer conn.Close()
    
        // 处理WebSocket连接...
    }
    
    func main() {
         
         
        r := gin.Default()
        r.GET("/ws", handleWebSocket)
        r.Run(":8080")
    }
    

在下一节中,我们将通过实践代码,详细展示如何在Gin框架中实现WebSocket服务,并构建一个功能完整的实时聊天应用。

三、代码实践

在这一部分,我们将通过一系列实际代码示例,展示如何在Gin框架中集成WebSocket功能,并逐步构建一个实时聊天应用。

3.1 基础WebSocket服务器

首先,我们来创建一个最简单的WebSocket服务器,它能够接受连接并与客户端进行消息交换。

3.1.1 项目初始化

首先创建项目目录并初始化Go模块:

mkdir gin-websocket-demo
cd gin-websocket-demo
go mod init gin-websocket-demo

安装必要的依赖:

go get github.com/gin-gonic/gin
go get github.com/gorilla/websocket
3.1.2 基础WebSocket服务实现

下面是一个基本的WebSocket服务器实现,支持连接建立和简单的消息收发:

// main.go
package main

import (
	"log"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
)

// 配置WebSocket upgrader
var upgrader = websocket.Upgrader{
   
   
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	// 允许所有CORS请求,生产环境应当配置更严格的规则
	CheckOrigin: func(r *http.Request) bool {
   
   
		return true
	},
}

// WebSocket处理函数
func handleWebSocket(c *gin.Context) {
   
   
	// 将HTTP连接升级为WebSocket连接
	conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
	if err != nil {
   
   
		log.Printf("升级连接失败: %v", err)
		return
	}
	defer conn.Close()

	log.Printf("客户端已连接: %s", c.Request.RemoteAddr)

	// 向客户端发送欢迎消息
	if err := conn.WriteMessage(websocket.TextMessage, []byte("欢迎连接到WebSocket服务器!")); err != nil {
   
   
		log.Println("写入消息失败:", err)
		return
	}

	// 持续读取客户端消息
	for {
   
   
		// 读取消息
		messageType, message, err := conn.ReadMessage()
		if err != nil {
   
   
			log.Println("读取消息错误:", err)
			break
		}

		// 记录收到的消息
		log.Printf("收到消息: %s", message)

		// 将消息发送回客户端(回显)
		if err := conn.WriteMessage(messageType, message); err != nil {
   
   
			log.Println("写入消息失败:", err)
			break
		}
	}

	log.Printf("客户端断开连接: %s", c.Request.RemoteAddr)
}

func main() {
   
   
	// 创建Gin路由
	r := gin.Default()

	// 提供WebSocket端点
	r.GET("/ws", handleWebSocket)

	// 提供一个简单的HTML页面用于测试
	r.GET("/", func(c *gin.Context) {
   
   
		c.File("index.html")
	})

	// 启动服务器
	log.Println("服务器启动在 http://localhost:8080")
	if err := r.Run(":8080"); err != nil {
   
   
		log.Fatal("启动服务器失败:", err)
	}
}
3.1.3 客户端测试页面

创建一个简单的HTML页面(index.html)用于测试WebSocket连接:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebSocket测试</title>
    <style>
        body {
     
     
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }
        #messages {
     
     
            height: 300px;
            border: 1px solid #ddd;
            padding: 10px;
            overflow-y: scroll;
            margin-bottom: 20px;
            background-color: #f9f9f9;
        }
        #messageInput {
     
     
            padding: 10px;
            width: 70%;
        }
        button {
     
     
            padding: 10px 15px;
            background-color: #4CAF50;
            color: white;
            border: none;
            cursor: pointer;
        }
        .status {
     
     
            margin-bottom: 10px;
            padding: 5px;
        }
        .connected {
     
     
            background-color: #dff0d8;
            color: #3c763d;
        }
        .disconnected {
     
     
            background-color: #f2dede;
            color: #a94442;
        }
        .message {
     
     
            margin: 5px 0;
            padding: 5px;
            border-bottom: 1px solid #eee;
        }
        .sent {
     
     
            text-align: right;
            color: #0066cc;
        }
        .received {
     
     
            text-align: left;
            color: #006600;
        }
    </style>
</head>
<body>
    <h1>WebSocket测试页面</h1>
    
    <div id="status" class="status disconnected">未连接</div>
    
    <div id="messages"></div>
    
    <div>
        <input type="text" id="messageInput" placeholder="输入消息..." disabled>
        <button id="sendButton" disabled>发送</button>
        <button id="connectButton">连接</button>
        <button id="disconnectButton" disabled>断开</button>
    </div>
    
    <script>
        // DOM元素
        const statusDiv = document.getElementById('status');
        const messagesDiv = document.getElementById('messages');
        const messageInput = document.getElementById('messageInput');
        const sendButton = document.getElementById('sendButton');
        const connectButton = document.getElementById('connectButton');
        const disconnectButton = document.getElementById('disconnectButton');
        
        // WebSocket连接
        let socket = null;
        
        // 添加消息到消息区
        function addMessage(message, type) {
     
     
            const messageElement = document.createElement('div');
            messageElement.classList.add('message', type);
            messageElement.textContent = message;
            messagesDiv.appendChild(messageElement);
            messagesDiv.scrollTop = messagesDiv.scrollHeight;
        }
        
        // 连接WebSocket
        function connect() {
     
     
            // 创建WebSocket连接
            const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
            const wsUrl = `${ 
       wsProtocol}//${ 
       window.location.host}/ws`;
            
            socket = new WebSocket(wsUrl);
            
            // 连接打开事件
            socket.onopen = function(e) {
     
     
                statusDiv.textContent = '已连接';
                statusDiv.className = 'status connected';
                
                // 启用输入和发送按钮
                messageInput.disabled = false;
                sendButton.disabled = false;
                connectButton.disabled = true;
                disconnectButton.disabled = false;
                
                addMessage('连接已建立', 'system');
            };
            
            // 接收消息事件
            socket.onmessage = function(event) {
     
     
                addMessage(`服务器: ${ 
       event.data}`, 'received');
            };
            
            // 连接关闭事件
            socket.onclose = function(event) {
     
     
                statusDiv.textContent = '已断开连接';
                statusDiv.className = 'status disconnected';
                
                // 禁用输入和发送按钮
                messageInput.disabled = true;
                sendButton.disabled = true;
                connectButton.disabled = false;
                disconnectButton.disabled = true;
                
                const reason = event.reason ? `原因: ${ 
       event.reason}` : '';
                addMessage(`连接已关闭。代码: ${ 
       event.code} ${ 
       reason}`, 'system');
            };
            
            // 连接错误事件
            socket.onerror = function(error) {
     
     
                statusDiv.textContent = '连接错误';
                statusDiv.className = 'status disconnected';
                addMessage('连接发生错误', 'system');
                console.error('WebSocket错误:', error);
            };
        }
        
        // 断开连接
        function disconnect() {
     
     
            if (socket) {
     
     
                socket.close(1000, "用户主动断开");
                socket = null;
            }
        }
        
        // 发送消息
        function sendMessage() {
     
     
            const message = messageInput.value;
            if (message && socket) {
     
     
                socket.send(message);
                addMessage(`我: ${ 
       message}`, 'sent');
                messageInput.value = '';
            }
        }
        
        // 事件监听
        connectButton.addEventListener('click', connect);
        disconnectButton.addEventListener('click', disconnect);
        sendButton.addEventListener('click', sendMessage);
        
        // 按回车键发送消息
        messageInput.addEventListener('keypress', function(e) {
     
     
            if (e.key === 'Enter') {
     
     
                sendMessage();
            }
        });
    </script>
</body>
</html>
3.1.4 运行和测试

运行应用并在浏览器中测试:

go run main.go
  1. 打开浏览器访问 http://localhost:8080
  2. 点击"连接"按钮建立WebSocket连接
  3. 在输入框中输入消息并发送
  4. 观察服务器如何回显消息

这个简单的例子展示了WebSocket的基本工作原理:

  • 建立双向通信通道
  • 服务器可以主动向客户端发送消息
  • 客户端可以随时向服务器发送消息
  • 连接保持开放直到任一方断开

3.2 构建实时聊天室应用

接下来,我们将扩展基础示例,构建一个功能更完善的聊天室应用,支持多用户、消息广播、用户列表等功能。

3.2.1 聊天室结构设计

聊天室应用需要管理多个WebSocket连接,处理消息广播和用户状态。我们将创建以下核心结构:

  1. Client: 表示单个WebSocket连接的客户端
  2. ChatRoom: 管理所有客户端,处理消息广播
  3. Message: 定义聊天消息的格式

下面是完整的聊天室实现:

// main.go
package main

import (
	"encoding/json"
	"log"
	"net/http"
	"sync"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/gorilla/websocket"
)

// 配置WebSocket upgrader
var upgrader = websocket.Upgrader{
   
   
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	Check
gin框架接入websocket服务 路径的GET请求处理函数。在该处理函数中,我们将HTTP连接升级为WebSocket连接。然后,我们循环读取来自客户端的消息,并将其原样发送回客户端。当使用Gin框架实现WebSocket服务器的Go部分时,你需要在项目中导入。创建了一个默认的Gin路由引擎,你可以根据需要进行进一步的自定义和路由定义。在这个示例中,我们使用Gin框架创建了一个路由组,并定义了。运行以上代码后,WebSocket服务器将在。路径上的WebSocket连接请求。请注意,示例代码中使用了。 阅读详情

相关推荐

gin框架中结合gorilla实现webSocket

gin框架写rest接口特别6,性能也很好。 本文介绍一下gin和gorilla结合创建websocket 1. gin github地址:https://github.com/gin-gonic/gin 安装gin的代码包 go get github.com/gin-gonic/gin package main import ( "github.com/...

lihao19910921的专栏 9591

gin+websocket+mongodb实现 IM 即时聊天系统,基于WS连接的即时聊天

gin+websocket+mongodb实现 IM 即时聊天系统,基于WS连接的即时聊天

python特征选择pso_【来点干货】机器学习中常用的特征选择方法及非常详细的Python实例...

花费了很长时间整理编辑,转载请联系作者授权,违者必究。特征选择(Feature selection)是在构建预测模型的过程中减少输入变量的一个过程。它是机器学习中非常重要的一步并在很大程度上可以提高模型预测精度。这里我总结了一些机器学习中常见的比较有用的特征选择方法并附上相关python实现code。希望可以给大家一些启发。首先,我们为什么要进行特征选择呢?它有以下几个优点:减少过拟合:冗余数据常...

weixin_39927158的博客 1161

RedPitaya火龙果板基础操作

火龙果板基础操作一级目录二级目录三级目录 一级目录 二级目录 三级目录

qq_41523340的博客 9721

微纳加工技术中的自对准硅化物(Silicide)工艺:关键步骤与应用场景解析

本文深入解析了微纳加工技术中的自对准硅化物(Silicide)工艺,详细介绍了其关键步骤与应用场景。从表面清洁、金属沉积到两步退火法,全面拆解工艺全流程,并探讨了不同技术节点下的工艺演进。文章还提供了实际应用中的问题排查方法和新兴技术对Silicide工艺的挑战,为芯片制造领域的工程师提供了实用参考。

weixin_30613727的博客 429

linux应用程序段错误调试

一、堆、栈 在分析段错误之前,先了解一下什么是堆?什么是栈? 堆:一般由开发者分配释放,如果没有释放,程序结束时,在有的OS中可能会被自动释放,分配方式类似于链表。堆的操作方式为,队列优先,先进先出的原则。 栈:由操作系统自动分配,存放函数的参数值,局部变量。栈的操作方式为,先进后出的原则。 堆栈中定义了一些操作。 两个最重要的是PUSH和POP。  PUSH:操作在堆栈的顶部加入一个元素...

农村种地人 1111

Google 人机验证(reCaptcha)无法显示解决方案

Google 人机验证无法显示解决方案第一步 安装插件Chrome/Edge 电脑版Firefox 电脑版第二步 配置插件原理参考文章 前言:为了防止机器人攻击,国外很多网站都使用了 Google reCaptcha 验证码。reCaptcha 对于国外用户非常的友好,但是… 对于国内用户就不怎么友好了。究其原因,则是国内网络全线屏蔽 Google 服务,导致 reCaptcha 完全加载不出来。这样,国内玩家就无法在对应的网站进行下一步操作了。本方案可以解决 reCaptcha 无法加载的问题。 适用

qq_40713201的博客 8万+

Aspice的理解

ASPICE 是由汽车行业的主要公司和组织合作开发的,包括德国汽车工业联合会(VDA)和国际汽车工作组(Automotive SIG)。其目标是通过标准化的软件开发流程,提高汽车电子系统的软件质量和可靠性。

hkj8808的博客 2015

Virtual Hard Disk version 2 (VHDX) 文件格式详解

VHDX 镜像格式详解 0. References windows github 1. Overview Microsoft 虚拟化产品中常用的映像格式是虚拟硬盘版本 2 (VHDX) 映像格式。该规范基于可用文档,并通过分析测试数据进行了增强。 VHDX 镜像格式种类 固定硬盘镜像 动态硬盘镜像 差异硬盘镜像 VHDX 镜像文件由以下元素构成: file (type) identifier 2x image headers 2x region tables log or metadata journ

圣三一 2696

Mastercam软件二次开发:使用C#进行Mastercam二次开发

C#(发音为“C Sharp”)是一种现代、面向对象的编程语言,由微软开发,首次发布于2000年。它结合了C和C++的强大功能,同时引入了Java的垃圾回收机制,使得C#在开发效率和运行性能之间找到了一个良好的平衡点。C#的设计目标是提高开发者的生产力,它支持多种编程范式,包括面向对象、泛型、委托和事件等,这使得它成为开发Windows应用程序、Web应用程序、游戏(尤其是使用Unity引擎)和企业级应用的理想选择。

kkchenjj的博客 2214

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

上一篇: 【Gin框架入门到精通系列14】Gin框架中的国际化与本地化
下一篇: 【Gin框架入门到精通系列16】Gin框架的优雅关闭与热重启
Gopher部落
博客等级 码龄2年 784粉丝 92原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Gopher部落

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值