透过 Go 语言探索 Linux 网络通信的本质

news2024/12/23 23:50:03

大家好,我是码农先森。

前言

各种编程语言百花齐放、百家争鸣,但是 “万变不离其中”。对于网络通信而言,每一种编程语言的实现方式都不一样;但其实,调用的底层逻辑都是一样的。linux 系统底层向上提供了统一的 Socket 通信系统函数,动态链接库 /lib64/libc.so 中就是实现网络通信的关键类库。下面我们会以 Go 语言为例,来分析网络通信数据传输的路径;最终揭开各大编程语言网络通信的神秘面纱。

演示程序

1、使用 Go 编写一个简单的 Socket 程序

package main

import (
    "fmt"
    "net"
)

func main() {
    // 监听本地端口
    listener, err := net.Listen("tcp", ":8080")
    if err != nil {
        fmt.Println("Error listening:", err.Error())
        return
    }
    defer listener.Close()

    fmt.Println("Listening on localhost:8080")

    for {
        // 接收客户端连接
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("Error accepting:", err.Error())
            return
        }

        // 处理客户端请求
        go handleRequest(conn)
    }
}

func handleRequest(conn net.Conn) {
    // 读取请求数据
    buffer := make([]byte, 1024)
    n, err := conn.Read(buffer)
    if err != nil {
        fmt.Println("Error reading:", err.Error())
        return
    }

    // 处理请求数据
    message := string(buffer[:n])
    fmt.Println("Received message:", message)

    // 发送响应数据
    reply := "Hello, client!"
    conn.Write([]byte(reply))

    // 关闭连接
    conn.Close()
}

2、编译成二进制文件。

go build main.go

[yxh@dev01 demo]$ ls -l
total 2536
-rwxr-xr-x 1 yangxionghai devops 2590837 Jun  2 15:42 main
-rw-r--r-- 1 yangxionghai devops    1023 Jun  2 15:39 main.go

3、执行 main 二进制文件

[yxh@dev01 demo]$ ./main 
Listening on localhost:8080

跟踪进程数据

1、跟踪 main 进程

# 找到进程ID
[yxh@dev01 demo]$ ps -aux | grep main
yxh+ 32270  0.0  0.0 816460  1732 pts/3    Sl+  16:47   0:00 ./main
yxh+ 32404  0.0  0.0 112816   968 pts/13   S+   16:47   0:00 grep --color=auto main

2、使用 strace 跟踪进程

strace -f -s 2048 -i -T -o trace.log -p 32270

命令各参数的解释:
-f: 跟踪在运行时从父进程派生出来的子进程,包括进程创建和退出等操作。
-s: 指定在输出中显示的字符串的最大长度为 2048 字节,这样可以避免过长的输出导致日志文件过大。
-i: 在输出中同时显示系统调用的入口和返回地址。
-T: 在输出中显示每个系统调用花费的时间。
-o: 将输出的结果写入到名为 trace.log 的文件中,而不是输出到控制台。
-p: 指定要跟踪的进程 ID 是 32270。

3、使用 telnet 发生数据

# 客户端发送数据
[yxh@dev01 ~]$ telnet 127.0.0.1 8080
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
^]

telnet> 
# 客户端发生的数据
hello world
# 服务端返回的数据
Hello, client!
Connection closed by foreign host.

4、服务端接收到数据

# 服务端接收到数据
[yxh@dev01 demo]$ ./main 
Listening on localhost:8080
Received message: hello world

深度分析

1、分析跟踪信息

我们先分析刚刚使用 strace 工具跟踪到的信息,可以看到里面有很多的系统调用。具体每个系统函数的说明及用法,可以去搜索引擎上查找资料学习。

# 跟踪到的信息
[yxh@dev01 demo]$ cat trace.log
32275 [0000000000462943] futex(0x5e0058, FUTEX_WAIT_PRIVATE, 0, NULL <unfinished ...>
32274 [0000000000462943] futex(0xc000080150, FUTEX_WAIT_PRIVATE, 0, NULL <unfinished ...>
32273 [0000000000462943] futex(0xc000044d50, FUTEX_WAIT_PRIVATE, 0, NULL <unfinished ...>
32272 [0000000000462943] futex(0xc000044950, FUTEX_WAIT_PRIVATE, 0, NULL <unfinished ...>
32271 [0000000000462943] restart_syscall(<... resuming interrupted restart_syscall ...> <unfinished ...>
32270 [0000000000462b60] epoll_pwait(5,  <unfinished ...>
32271 [0000000000462943] <... restart_syscall resumed>) = -1 ETIMEDOUT (Connection timed out) <25.495631>
32271 [00000000004623bd] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0 <0.000107>
32271 [0000000000462943] futex(0x5b1bd8, FUTEX_WAIT_PRIVATE, 0, {tv_sec=60, tv_nsec=0}) = -1 ETIMEDOUT (Connection timed out) <60.000108>
32271 [00000000004623bd] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0 <0.000100>
32271 [0000000000462943] futex(0x5b1bd8, FUTEX_WAIT_PRIVATE, 0, {tv_sec=60, tv_nsec=0}) = -1 ETIMEDOUT (Connection timed out) <60.000112>
32271 [00000000004623bd] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0 <0.000123>
32271 [0000000000462943] futex(0x5b1bd8, FUTEX_WAIT_PRIVATE, 0, {tv_sec=60, tv_nsec=0} <unfinished ...>
32270 [0000000000462b60] <... epoll_pwait resumed>[{EPOLLIN, {u32=2087155416, u64=140464697603800}}], 128, -1, NULL, 0) = 1 <188.828350>
32270 [0000000000462943] futex(0x5b1bd8, FUTEX_WAKE_PRIVATE, 1) = 1 <0.000028>
32271 [0000000000462943] <... futex resumed>) = 0 <43.331766>
32270 [000000000047f08a] accept4(3,  <unfinished ...>
32271 [00000000004623bd] nanosleep({tv_sec=0, tv_nsec=20000},  <unfinished ...>
32270 [000000000047f08a] <... accept4 resumed>{sa_family=AF_INET6, sin6_port=htons(9622), inet_pton(AF_INET6, "::ffff:127.0.0.1", &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, [112->28], SOCK_CLOEXEC|SOCK_NONBLOCK) = 4 <0.000019>
32271 [00000000004623bd] <... nanosleep resumed>NULL) = 0 <0.000076>
32270 [0000000000462b38] epoll_ctl(5, EPOLL_CTL_ADD, 4, {EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, {u32=2087155184, u64=140464697603568}} <unfinished ...>
32271 [00000000004623bd] nanosleep({tv_sec=0, tv_nsec=20000},  <unfinished ...>
32270 [0000000000462b38] <... epoll_ctl resumed>) = 0 <0.000017>
32270 [000000000047f0f6] getsockname(4, {sa_family=AF_INET6, sin6_port=htons(8080), inet_pton(AF_INET6, "::ffff:127.0.0.1", &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id=0}, [112->28]) = 0 <0.000017>
32270 [000000000047f08a] setsockopt(4, SOL_TCP, TCP_NODELAY, [1], 4 <unfinished ...>
32271 [00000000004623bd] <... nanosleep resumed>NULL) = 0 <0.000103>
32270 [000000000047f08a] <... setsockopt resumed>) = 0 <0.000016>
32271 [00000000004623bd] nanosleep({tv_sec=0, tv_nsec=20000},  <unfinished ...>
32270 [000000000047f08a] setsockopt(4, SOL_SOCKET, SO_KEEPALIVE, [1], 4) = 0 <0.000017>
32270 [000000000047f08a] setsockopt(4, SOL_TCP, TCP_KEEPINTVL, [15], 4) = 0 <0.000017>
32271 [00000000004623bd] <... nanosleep resumed>NULL) = 0 <0.000090>
32270 [000000000047f08a] setsockopt(4, SOL_TCP, TCP_KEEPIDLE, [15], 4 <unfinished ...>
32271 [00000000004623bd] nanosleep({tv_sec=0, tv_nsec=20000},  <unfinished ...>
32270 [000000000047f08a] <... setsockopt resumed>) = 0 <0.000016>
32270 [0000000000462943] futex(0xc000080150, FUTEX_WAKE_PRIVATE, 1) = 1 <0.000020>
32274 [0000000000462943] <... futex resumed>) = 0 <188.828982>
32270 [000000000047f08a] accept4(3,  <unfinished ...>
32274 [0000000000462b60] epoll_pwait(5,  <unfinished ...>
32271 [00000000004623bd] <... nanosleep resumed>NULL) = 0 <0.000101>
32274 [0000000000462b60] <... epoll_pwait resumed>[{EPOLLOUT, {u32=2087155184, u64=140464697603568}}], 128, 0, NULL, 0) = 1 <0.000014>
32270 [000000000047f08a] <... accept4 resumed>0xc000053c10, [112], SOCK_CLOEXEC|SOCK_NONBLOCK) = -1 EAGAIN (Resource temporarily unavailable) <0.000037>
32274 [0000000000462943] futex(0xc000044950, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
32271 [00000000004623bd] nanosleep({tv_sec=0, tv_nsec=20000},  <unfinished ...>
32274 [0000000000462943] <... futex resumed>) = 1 <0.000018>
32270 [0000000000462b60] epoll_pwait(5,  <unfinished ...>
32274 [000000000047f01b] read(4,  <unfinished ...>
32272 [0000000000462943] <... futex resumed>) = 0 <188.829100>
32274 [000000000047f01b] <... read resumed>0xc00008e400, 1024) = -1 EAGAIN (Resource temporarily unavailable) <0.000017>
32270 [0000000000462b60] <... epoll_pwait resumed>[], 128, 0, NULL, 2) = 0 <0.000039>
32274 [0000000000462b60] epoll_pwait(5,  <unfinished ...>
32272 [0000000000462b60] epoll_pwait(5,  <unfinished ...>
32274 [0000000000462b60] <... epoll_pwait resumed>[], 128, 0, NULL, 2) = 0 <0.000019>
32271 [00000000004623bd] <... nanosleep resumed>NULL) = 0 <0.000120>
32270 [0000000000462b60] epoll_pwait(5,  <unfinished ...>
32274 [0000000000462943] futex(0xc000080150, FUTEX_WAIT_PRIVATE, 0, NULL <unfinished ...>
32272 [0000000000462b60] <... epoll_pwait resumed>[], 128, 0, NULL, 0) = 0 <0.000054>
32271 [00000000004623bd] nanosleep({tv_sec=0, tv_nsec=20000},  <unfinished ...>
32272 [0000000000462943] futex(0xc000044950, FUTEX_WAIT_PRIVATE, 0, NULL <unfinished ...>
32271 [00000000004623bd] <... nanosleep resumed>NULL) = 0 <0.000112>
32271 [0000000000462943] futex(0x5b1bd8, FUTEX_WAIT_PRIVATE, 0, {tv_sec=60, tv_nsec=0} <unfinished ...>
32270 [0000000000462b60] <... epoll_pwait resumed>[{EPOLLIN|EPOLLOUT, {u32=2087155184, u64=140464697603568}}], 128, -1, NULL, 0) = 1 <8.305983>
32270 [0000000000462943] futex(0x5b1bd8, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
32271 [0000000000462943] <... futex resumed>) = 0 <8.305878>
32270 [0000000000462943] <... futex resumed>) = 1 <0.000035>
32271 [0000000000462aa7] sched_yield( <unfinished ...>
32270 [000000000047f01b] read(4,  <unfinished ...>
32271 [0000000000462aa7] <... sched_yield resumed>) = 0 <0.000015>
# 接收到来自客户端的数据
32270 [000000000047f01b] <... read resumed>"hello world\r\n", 1024) = 13 <0.000015>
32271 [0000000000462943] futex(0x5b1ad8, FUTEX_WAKE_PRIVATE, 1) = 0 <0.000021>
32271 [00000000004623bd] nanosleep({tv_sec=0, tv_nsec=20000},  <unfinished ...>
32270 [000000000047f01b] write(1, "Received message: hello world\r\n\n", 32) = 32 <0.000025>
# 服务端返回给客户端的数据
32270 [000000000047f01b] write(4, "Hello, client!", 14 <unfinished ...>
32271 [00000000004623bd] <... nanosleep resumed>NULL) = 0 <0.000092>
32271 [00000000004623bd] nanosleep({tv_sec=0, tv_nsec=20000},  <unfinished ...>
32270 [000000000047f01b] <... write resumed>) = 14 <0.000040>
32270 [0000000000462b38] epoll_ctl(5, EPOLL_CTL_DEL, 4, 0xc00004ee3c <unfinished ...>
32271 [00000000004623bd] <... nanosleep resumed>NULL) = 0 <0.000092>
32270 [0000000000462b38] <... epoll_ctl resumed>) = 0 <0.000067>
32271 [00000000004623bd] nanosleep({tv_sec=0, tv_nsec=20000},  <unfinished ...>
32270 [000000000047f01b] close(4)       = 0 <0.000033>
32270 [0000000000462b60] epoll_pwait(5, [], 128, 0, NULL, 824634044416) = 0 <0.000017>
32271 [00000000004623bd] <... nanosleep resumed>NULL) = 0 <0.000101>
32270 [0000000000462b60] epoll_pwait(5,  <unfinished ...>
32271 [0000000000462943] futex(0x5b1bd8, FUTEX_WAIT_PRIVATE, 0, {tv_sec=60, tv_nsec=0}) = -1 ETIMEDOUT (Connection timed out) <60.000106>
32271 [00000000004623bd] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0 <0.000116>
32271 [0000000000462943] futex(0x5b1bd8, FUTEX_WAIT_PRIVATE, 0, {tv_sec=60, tv_nsec=0}) = -1 ETIMEDOUT (Connection timed out) <60.000107>
32271 [00000000004623bd] nanosleep({tv_sec=0, tv_nsec=20000}, NULL) = 0 <0.000119>

strace 命令是 Linux 系统下的一个系统调用跟踪工具,其主要作用是打印出程序执行时调用的所有系统调用以及相应的返回值。

2、分析 main 编译的可执行二进制文件

使用 ldd 查看二进制文件的动态链接调用库。

[yxh@dev01 demo]$ ldd main
	linux-vdso.so.1 =>  (0x00007ffd761d2000)
	libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f6cb5857000)
	# libc.so 库的主要作用是为开发者提供一些常用的、通用的基础函数,例如字符串处理、文件操作、进程管理、网络通信等,同时也提供了一些系统调用的封装接口。
	libc.so.6 => /lib64/libc.so.6 (0x00007f6cb5489000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f6cb5a73000)

ldd 命令是 Linux 系统下的一个动态链接库依赖检查工具,用于显示可执行程序或共享库文件所依赖的动态链接库列表。

分析 libc.so 动态链接库

[yxh@dev01 demo]$ nm /lib64/libc.so.6 | grep read
000000000010c550 W pthread_setcancelstate
000000000010c580 T pthread_setcanceltype
000000000010c430 T pthread_setschedparam
000000000013e6c0 t __pthread_unwind
# 读函数
00000000000ef990 W read
00000000000ef990 W __read
00000000000fe9a0 W readahead
00000000000fe9a0 t __readahead
0000000000033180 t read_alias_file

[yxh@dev01 demo]$ nm /lib64/libc.so.6 | grep write
00000000000f5510 T pwritev
00000000000f5510 T pwritev64
# 写函数
00000000000ef9f0 W write
00000000000ef9f0 W __write
0000000000100250 t write_gmon
00000000000ef9f9 t __write_nocancel
00000000001009a0 t __write_profiling
000000000012f440 t writetcp

nm 是一个 Linux 系统下的二进制文件分析工具,用于查看目标文件或者可执行文件的符号表信息以及相关的重定位信息等。

除了 read、write 还有 accept、sendto、recvfrom、setsockopt、getsockopt、epoll 等函数。

总结

唯一不变的是变化,新技术层出不穷。对于我们技术人来说,不断学习新的技术是永无止境的,时间长了会陷入疲惫不堪。我们只有在不断变化中 “寻找不变化的东西”,通过掌握本质的东西,来以不变来应万变。这篇文章以 Go 语言为例,来逐步的从应用层到系统层的跟踪剖析,挖掘网络通信的本质,深入了解 Socket 通信的底层逻辑。希望大家可以以本文中的 Go 语言为例,举一反三。如果有什么问题,可以评论留言。


欢迎关注、分享、点赞、收藏、在看,我是微信公众号「码农先森」作者。

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

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

相关文章

君子签区块链+AI,驱动组织实现高效合同管理、精准风险控制

在传统合同签署的过程中&#xff0c;企业、组织、机构都面临着合同签署与管理的诸多问题和挑战&#xff1a;合同种类繁多、数量庞大导致起草效率低下&#xff1b;管理流程繁琐、权限分散使得审批周期冗长且效率低下&#xff1b;合同签订版本难以精准复核&#xff0c;风险防控更…

大型网站软件系统架构演进过程

在我们的生活中,通常会使用大型网站系统,比如购物网站淘宝,京东,阿里1688;大型搜索引擎网站百度,社交类的如腾讯旗下的微信,QQ及新浪旗下的微博等,他们通常都有一下特点: 高并发、大流量&#xff1a;这些系统必须能够处理成千上万甚至数百万的并发用户请求&#xff0c;以及持续…

深入理解pytest fixture:提升测试的灵活性和可维护性!

在现代软件开发中&#xff0c;测试是保证代码质量的重要环节。pytest作为一个强大的测试框架&#xff0c;以其灵活的fixture系统脱颖而出。本文将详细介绍pytest中的fixture概念&#xff0c;通过具体案例展示其应用&#xff0c;并说明如何利用fixture提高测试的灵活性和可维护性…

CVPR 2024最佳论文:“神兵”的组合器 Generative Image Dynamics

CVPR 2024的最佳论文来自谷歌、美国加州大学圣迭戈分校。两篇都来至于视频生成领域&#xff0c;可见今年外界对视频生成领域关注度很高。今天的这篇是“Generative Image Dynamics”&#xff0c;Google Research发布的。它的研究成果令人震惊&#xff0c;从单张RGB图像生成连续…

VIM介绍

VIM&#xff08;Vi IMproved&#xff09;是一种高度可配置的文本编辑器&#xff0c;用于有效地创建和更改任何类型的文本。它是从 vi 编辑器发展而来的&#xff0c;后者最初是 UNIX 系统上的一个文本编辑器。VIM 以其键盘驱动的界面和强大的文本处理能力而闻名&#xff0c;是许…

【pytorch14】感知机

单层感知机模型 对于单层的感知机&#xff0c;它的激活函数是一个sigmoid 对于符号的定义做一个规范化&#xff0c;输入层每一层进行一个编号 输入是第0层&#xff0c;上标0表示属于输入层&#xff0c;下标0到n表示一共有n个节点(这里严格来说应该是0~n-1&#xff0c;为了书写…

阿里Qwen2-72B大模型已是开源榜的王者,为什么还要推出其他参数模型,被其他模型打榜?

6 月 27 日&#xff0c;全球知名的开源平台 Hugging Face 的联合创始人兼首席执行官 Clem 在社交平台激动宣布&#xff0c;阿里 Qwen2-72B 成为了开源模型排行榜的王者。 这是一件大好事&#xff0c;说明了我们在大模型领域从先前的追赶&#xff0c;逐渐走向了领导&#xff0c;…

【 VIPKID-注册安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞 …

Redis 7.x 系列【17】四种持久化策略

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Redis 版本 7.2.5 源码地址&#xff1a;https://gitee.com/pearl-organization/study-redis-demo 文章目录 1. 概述2. 案例演示2.1 无持久化2.2 RDB2.3 AOF2.4 混合模式2.4.1 方式一&#xff1a;…

初学Spring之自动装配 Bean

Bean 的作用域&#xff1a; 1.单例模式&#xff08;Spring 默认机制&#xff09; scope“singleton” 2.原型模式&#xff1a;每次从容器中 get 时&#xff0c;都会产生一个新对象 scope"prototype" 3. request、session、application&#xff0c;只能在 web 开…

不可变集合

目录 1.1 什么是不可变集合 1.2 不可变集合分类 1.3 不可变的list集合&#xff1a;list of 1.4 不可变的Set集合&#xff1a;set.of 1.5 不可变的Map集合 1.5.1&#xff1a;键值对个数小于等于10 1.5.2&#xff1a;键值对个数大于10 Map.ofEntries方法&#xff1a; copy…

JBoss JMXInvokerServlet 反序列化漏洞

漏洞原理&#xff1a; 这是经典的JBoss反序列化漏洞&#xff0c;JBoss在/invoker/JMXInvokerServlet请求中读取了用户传入的对象&#xff0c;然后我们利用Apache Commons Collections中的Gadget执行任意代码。 影响版本&#xff1a; JBoss Enterprise Application Platform 6…

如视“VR+AI”实力闪耀2024世界人工智能大会

7月4日&#xff0c;2024世界人工智能大会暨人工智能全球治理高级别会议&#xff08;以下简称为“WAIC 2024”&#xff09;在上海盛大开幕&#xff0c;本届大会由外交部、国家发展和改革委员会、教育部等部门共同主办&#xff0c;围绕“以共商促共享 以善治促善智”主题&#xf…

【虚拟机】虚拟机网络无法访问问题【已解决】

【虚拟机】虚拟机无法上网问题【已解决】 问题探究解决方法法1&#xff1a;查看相关“网络服务”是否处于正常启动状态法2&#xff1a;重启网络法3&#xff1a;重新安装VMWare法4&#xff1a;使用NAT模式&#xff0c;每次打开win7都没连上网的解决办法 问题探究 安装了很多个虚…

昇思MindSpore学习总结八——静态图加速

AI编译框架分为两种运行模式&#xff0c;分别是动态图模式以及静态图模式。MindSpore默认情况下是以动态图模式运行&#xff0c;但也支持手工切换为静态图模式。两种运行模式的详细介绍如下&#xff1a; 1、动态图模式 动态图的特点是计算图的构建和计算同时发生&#xff08;D…

vue模板语法v-html

模板语法v-html vue使用一种基于HTML的模板语法&#xff0c;使我们能够声明式的将其组件实例的数据绑定到呈现的DOM上&#xff0c;所有的vue模板都是语法层面的HTML&#xff0c;可以被符合规范的浏览器和HTML解释器解析。 一.文本插值 最基本的数据绑定形式是文本插值&#…

高二的他已通过NOI保送北大了,让我们一起了解他的信息学奥赛学习经历吧!!!

相信关注本号的各位&#xff0c;对于信息学奥赛已经不陌生了&#xff0c;部分同学也已经开始踏入信息学的旅程&#xff0c;但前路茫茫&#xff0c;让我们一起看看已经取得成就的同学的经历吧。 今天要介绍的这位同学&#xff0c;是来自深圳中学的高二某班的欧阳达晟同学&#x…

一、强化学习基本概念

一、强化学习基本概念 1.1 何为强化学习&#xff1f;1.2 强化学习的环境1.3 强化学习的目标1.4 强化学习的数据 1.1 何为强化学习&#xff1f; 强化学习(Reinforcement Learning, RL)是机器通过与环境交互来实现目标的一种计算方法。机器和环境的一轮交互是指&#xff1a;机器在…

Spring AI之后,阿里推出Spring Cloud Alibaba AI,接入体验篇——Java也能方便用 AI

阿里推出Spring Cloud Alibaba AI&#xff0c;接入体验篇——Java也能方便用 AI 1.Spring AI2.Spring Cloud Alibaba AI3. 接入体验 1.Spring AI Spring AI 是 Spring 官方社区项目&#xff0c;旨在简化 Java AI 应用程序开发&#xff0c;让 Java 开发者像使用 Spring 开发普通…

安装Anaconda找不到旧版本怎么办?

标题Anaconda官网&#xff1a;https://www.anaconda.com/ 对于个人学习使用&#xff0c;不用下载最新版本&#xff0c;使用之前的版本相对还是比较稳定的。所以需要寻找旧版网址。 标题旧版网址&#xff1a;https://repo.anaconda.com/archive/ 里面多种版本选择 然后选择自…