GO:Socket编程

news2024/11/13 9:27:06

目录

一、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 main

import (
	"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 main

import (
	"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 main

import (
	"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 main

import (
	"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 main

import (
	"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 main

import (
	"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 main

import (
	"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.coloradmin.cn/o/1936089.html

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

相关文章

kotlin compose 实现应用内多语言切换(不重新打开App)

1. 示例图 2.具体实现 如何实现上述示例,且不需要重新打开App ①自定义 MainApplication 实现 Application ,定义两个变量: class MainApplication : Application() { object GlobalDpData { var language: String = "" var defaultLanguage: Strin…

你不是拖延,是没找对感觉!

在这个快节奏的时代&#xff0c;学习效率成为了我们每个人都渴望提升的关键能力。如何通过训练潜意识、深化知识印象、调整学习模式、找到适合自己的学习方法&#xff0c;以及利用倒计时硬逼法来提高执行力&#xff1f; 1. 训练潜意识&#xff1a;形成习惯 习惯的力量是巨大的…

使用python的pillow库生成图像验证码

一、pillow库 Pillow库&#xff1a;是一个功能强大的Python图像处理库&#xff0c;它提供了丰富的图像处理功能&#xff0c;使得用户能够方便地使用Python对图像进行各种操作。 二、图像验证码的分析 首先需要一个图像&#xff0c;图像上需要绘制验证码&#xff0c;还需要任意多…

博客最细 STM32CubeProgrammer 软件使用教程 二(学不会举报我)

前言&#xff1a;编写不易&#xff0c;仅供学习&#xff0c;参考&#xff0c;请勿转载 前言&#xff1a;本篇教程是 STM32CubeProgrammer 软件使用教程二&#xff0c;通过本篇你可以学习到&#xff0c;使用STM32CubeProgrammer读取 flash RAM&#xff0c;开启读写保护&#x…

科普文:TaobaoVM信息收集

网上关于TaobaoVM的信息很少&#xff0c;只有一个简介&#xff0c;就没有其他信息。毕竟这是别人企业自己的jvm&#xff0c;不可能公开。 Taobao VM 由AliJVM团队发布。阿里&#xff0c;国内使用Java最强大的公司&#xff0c;覆盖云计算、金融、物流、电商等众多领域&#xf…

Python和C++行人轨迹预推算和空间机器人多传感融合双图算法模型

&#x1f3af;要点 &#x1f3af;双图神经网络模型&#xff1a;最大后验推理和线性纠错码解码器 | &#x1f3af;重复结构和过约束问题超图推理模型 | &#x1f3af;无向图模型变量概率计算、和积消息传播图结构计算、隐马尔可夫模型图结构计算、矩阵图结构计算、图结构学习 |…

数据库MySQL学习第一天|了解数据库、数据类型、存储引擎、sql语言

文章目录 了解数据库什么是数据库数据库分类MySQL概念 数据类型整数类型小数类型日期类型文本,二进制类型 存储引擎种类引擎选择 sql主键和外键主键设计原则选取策略 外键索引 表与表的关联在语法上关联关系sql约束sql注入聚合函数常见查询关键字 了解数据库 什么是数据库 概…

网络安全协议系列

目录 一、安全协议的引入 1.TCP/IP协议族中普通协议的安全缺陷 1.信息泄露 2.信息篡改 3.身份伪装 4.行为否认 2.网络安全需求 二、网络安全协议的定义 三、构建网络安全协议所需的组件 1.加密与解密 2.消息摘要 3.消息验证码 4.数字签名 5.密钥管理 1.建立共享…

关于C#多个文本框输入的问题

&#x1f3c6;本文收录于《CSDN问答解答》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&…

堆排序基本思想以及代码实现

1、基本思想 堆排序是利用堆这种数据结构而设计的一种排序算法&#xff0c;堆排序是一种选择排序&#xff0c;它的最坏&#xff0c;最好&#xff0c;平均时间复杂度均为O(n*logn)&#xff0c;它也是不稳定排序。 堆是具有以下性质的完全二叉树&#xff1a;每个结点的值都大于或…

Vue3路由如何携带 # 路由模式选择

vue3中创建路由的时候&#xff0c;有两种可选模型 hash模式、HTML5模式、服务端渲染模式 createWebHashHistory&#xff08;hash模式&#xff09; const router createRouter({// hash模式&#xff0c;带 # 号history: createWebHashHistory(), //函数可携带参数&#xff0c;…

C++11 容器emplace方法刨析

如果是直接插入对象 push_back()和emplace_back()没有区别但如果直接传入构造函数所需参数&#xff0c;emplace_back()会直接在容器底层构造对象&#xff0c;省去了调用拷贝构造或者移动构造的过程 class Test { public:Test(int a){cout<<"Test(int)"<<…

使用Web控制端和轻量级客户端构建的开放Web应用防火墙(OpenWAF)

目录 1. 简介2. 项目结构3. Web控制端3.1. 功能概述3.2. 审计&#xff08;攻击&#xff09;日志查看3.3. 多个WAF的集中监控和操作3.4. 使用socket进行封装3.5. 日志的高效存储和检索&#xff08;Redis&#xff09; 4. 轻量级客户端4.1. 功能概述4.2. 对Web程序的防护4.3. 网络…

36.UART(通用异步收发传输器)-RS232(3)

&#xff08;1&#xff09;串口发送模块visio视图&#xff1a; &#xff08;2&#xff09;串口发送模块Verilog代码: /* 常见波特率&#xff1a; 4800、9600、14400、115200 在系统时钟为50MHz时&#xff0c;对应计数为&#xff1a; (1/4800) * 10^9 /20 -1 10416 …

鸿蒙语言基础类库:【@system.vibrator (振动)】

振动 说明&#xff1a; 本模块首批接口从API version 4开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。从API Version 8开始&#xff0c;该接口不再维护&#xff0c;推荐使用新接口[ohos.vibrator]。该功能使用需要对应硬件支持&#xff0c;仅支持…

学生信息管理系统-优化版

springbootthymeleafmybatis 记录一下闲来无事&#xff0c;将之前做的1.0页面优化。 一、【管理员】首页 1、增加了【批量删除】、【导出学生信息】、【分页】、【统计及格率、平均分、优秀率】等功能。 2、将页面样式优化了一下&#xff0c;做的好看些 原来&#xff1a; 现…

.NET C# 配置 Options

.NET C# 配置 Options 使用 options 模式可以带来许多好处&#xff0c;包括清晰的配置管理、类型安全、易于测试和灵活性。但在使用过程中&#xff0c;也需要注意配置复杂性、性能开销和依赖框架等问题。通过合理设计和使用&#xff0c;可以充分发挥 options 模式的优势&#…

【链表】算法题(二) ----- 力扣/牛客

一、链表的回文结构 思路&#xff1a; 找到链表的中间节点&#xff0c;然后逆置链表的后半部分&#xff0c;再一一遍历链表的前半部分和后半部分&#xff0c;判断是是否为回文结构。 快慢指针找到链表的中间节点 slow指针指向的就是中间节点 逆置链表后半部分 逆置链表后半部分…

【JavaScript 算法】图的遍历:理解图的结构

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、深度优先搜索&#xff08;DFS&#xff09;深度优先搜索的步骤深度优先搜索的JavaScript实现 二、广度优先搜索&#xff08;BFS&#xff09;广度优先搜索的步骤 三、应用场景四、总结 图的遍历是图论中的基本操作之一&am…

安卓 mvp 的架构的详细介绍

MVP 架构介绍 MVP&#xff08;Model-View-Presenter&#xff09;是一种软件架构模式&#xff0c;常用于构建用户界面&#xff08;UI&#xff09;。它将应用程序的逻辑划分为三个部分&#xff1a;Model、View 和 Presenter。MVP 的主要目标是分离视图和业务逻辑&#xff0c;使代…