透过 Go 语言探索 Linux 网络通信的本质
大家好,我是码农先森。
前言
各种编程语言百花齐放、百家争鸣,但是 “万变不离其中”。对于网络通信而言,每一种编程语言的实现方式都不一样;但其实,调用的底层逻辑都是一样的。linux 系统底层向上提供了统一的 Socket 通信系统函数,动态链接库 /lib64/libc.so 中就是实现网络通信的关键类库。下面我们会以 Go 语言为例,来分析网络通信数据传输的路径;最终揭开各大编程语言网络通信的神秘面纱。
演示程序
1、使用 Go 编写一个简单的 Socket 程序
package mainimport ("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 mainlinux-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 语言为例,举一反三。如果有什么问题,可以评论留言。
欢迎关注、分享、点赞、收藏、在看,我是微信公众号「码农先森」作者。

相关文章:
透过 Go 语言探索 Linux 网络通信的本质
大家好,我是码农先森。 前言 各种编程语言百花齐放、百家争鸣,但是 “万变不离其中”。对于网络通信而言,每一种编程语言的实现方式都不一样;但其实,调用的底层逻辑都是一样的。linux 系统底层向上提供了统一的 Sock…...
【C语言】—— 文件操作(下)
【C语言】—— 文件操作(下) 前言:五、文件的顺序读写5.1、 顺序读写函数介绍5.2、 f p u t c fputc fputc 函数5.3、 f g e t c fgetc fgetc 函数5.4、 f p u t s fputs fputs 函数5.5、 f g e t s fgets fgets 函数5.6、 f p r i n t f…...
np.argsort
函数解释 np.argsort是NumPy库中的一个函数,用于对数组进行排序并返回排序后的索引。它不会直接对数组进行排序,而是返回一个数组,这个数组中的元素是原数组中元素按升序排序后的索引。 numpy.argsort(a, axis-1, kindNone, orderNone) 参…...
ORC与Parquet列式存储的区别
ORC与Parquet列式存储 1、ORC与Parquet列式存储2、ORC与Parquet的区别 列式存储(Columnar Storage)是一种优化的数据存储方式,与传统的行式存储(Row Storage)相比,列式存储在数据压缩、查询性能、I/O效率等…...
析构函数和拷贝构造函数
文章目录 析构函数1.析构函数的定义:2.析构函数的语法:3.析构函数的特性: 拷贝构造函数1.拷贝构造函数的定义:2.拷贝构造函数的语法3.拷贝构造函数的特性(1)拷贝构造函数是构造函数的一个重载形式**(这个其实也很好理解࿰…...
sql server启动、连接 与 navicat连接sql server
一、sql server 启动 1.搜索cmd->以管理员身份运行 2.输入以下命令 net start mssqlserver 3.服务器启动成功 二、sql server连接 1.打开ssms,输入,连接 2.右键,属性 3.连接,勾选允许远程连接到此服务器 三、navicat连接sq…...
数据库测试数据准备厂商 Snaplet 宣布停止运营
上周刚获知「数据库调优厂商 OtterTune 宣布停止运营」。而今天下班前,同事又突然刷到另一家海外数据库工具商 Snaplet 也停止运营了。Snaplet 主要帮助开发团队在数据库中生成仿真度高且合规的测试数据。我们在年初还撰文介绍过它「告别手搓!Postgres 一…...
【Java09】方法(下)
1. 形参个数可变的方法 Java允许方法指定数量不确定的形参。如果在定义方法是,在最后一个形参的类型后加...,则表明该形参可以接受多个参数值。多个参数值作为数组传入: public class Varargs {public static void test(int a, String... b…...
d88888888
分析:v9999999999 vn输出n个n 先算出n的位数p 所以答案是nn*10的p次方n*10的2p次方.....n*10的(n-1)p次方 化简n*(10的0次方10的p次方10的2p次方.....10的(n-1)p次方) 后面为等比数列求和 …...
【MySQL备份】mysqldump基础篇
目录 1.简介 2.基本用途 3.命令格式 3.1常用选项 3.2常用命令 4.备份脚本 5.定时执行备份脚本 1.简介 mysqldump 是 MySQL 数据库管理系统的命令行实用程序,用于创建数据库的逻辑备份。它能够导出数据库的结构(如表结构、视图、触发器等…...
C# Halcon目标检测算法
在Halcon中进行目标检测可以使用传统的计算机视觉方法,也可以使用深度学习的方法。Halcon提供了丰富的函数库来处理这些任务,而在C#中使用Halcon,你需要通过Halcon .NET接口。 以下是使用Halcon进行目标检测的一般步骤,这里我将给…...
7.4总结
今天写了几道题目 最近,一年级学生马克西姆学习了科拉兹猜想,但他在讲课时没有太注意,所以他认为猜想中提到了以下过程: 有一个变量 $$$x$$$ 和一个常数 $$$y$$$ 。下面的操作要执行 $$$k$$$ 次: - 将 $$$x$$$ 增加…...
知识图谱查询语言的表示
文章目录 SPARQL知识图谱查询基本构成常见的SPARQL查询算子语义Markup表示语言SPARQL知识图谱查询基本构成 RDF 支持类似数据库的查询语言,叫作SPARQL,它提供了查询RDF 数据的标准语法、处理SPARQL查询的规则以及结果返回形式。 变量,RDF中的资源,以“?”或者“$”指示;…...
重生之我要学后端100--计算机网络部分概念(持续更新)
TCP/IP、DNS、负载均衡器等等 前言一、TCP/IP(传输控制协议/互联网协议)二、DNS(域名系统)三、负载均衡器其他网络概念 前言 了解网络基础知识对于后端开发者至关重要,因为这些知识有助于理解应用程序是如何在更广阔的…...
时空预测+特征分解!高性能!EMD-Transformer和Transformer多变量交通流量时空预测对比
时空预测特征分解!高性能!EMD-Transformer和Transformer多变量交通流量时空预测对比 目录 时空预测特征分解!高性能!EMD-Transformer和Transformer多变量交通流量时空预测对比效果一览基本介绍程序设计参考资料 效果一览 基本介绍…...
Vue 循环内部获取图片高度
在vue循环里面获取图片宽度或者高度,有时候会用到,则可以 <div classconmon v-for"(item, index) in items"><router-link :to"{path: /art/details,query:{artid:item.app_id,item_id:item.image_id}}"><img :src"item.src" al…...
vue动态组件与插件到底是什么?
background: yellow; } 子组件1 <ul><li v-for"item of items" :key"item"><input type"checkbox" />{{ item }}</li></ul>子组件2 PostMail 子组件3 RecycleBin  符号的含义
您已经很好地解释了 SQL 查询中 () 符号的含义,它确实用于表示左外连接(LEFT OUTER JOIN),这是 SQL 中的一种连接操作。以下是对您提供的信息的补充和完善: ### 左外连接(LEFT OUTER JOIN)&…...
智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...
ip子接口配置及删除
配置永久生效的子接口,2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
[免费]微信小程序问卷调查系统(SpringBoot后端+Vue管理端)【论文+源码+SQL脚本】
大家好,我是java1234_小锋老师,看到一个不错的微信小程序问卷调查系统(SpringBoot后端Vue管理端)【论文源码SQL脚本】,分享下哈。 项目视频演示 【免费】微信小程序问卷调查系统(SpringBoot后端Vue管理端) Java毕业设计_哔哩哔哩_bilibili 项…...
uniapp 开发ios, xcode 提交app store connect 和 testflight内测
uniapp 中配置 配置manifest 文档:manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号:4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...
4. TypeScript 类型推断与类型组合
一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式,自动确定它们的类型。 这一特性减少了显式类型注解的需要,在保持类型安全的同时简化了代码。通过分析上下文和初始值,TypeSc…...
Docker拉取MySQL后数据库连接失败的解决方案
在使用Docker部署MySQL时,拉取并启动容器后,有时可能会遇到数据库连接失败的问题。这种问题可能由多种原因导致,包括配置错误、网络设置问题、权限问题等。本文将分析可能的原因,并提供解决方案。 一、确认MySQL容器的运行状态 …...
Java并发编程实战 Day 11:并发设计模式
【Java并发编程实战 Day 11】并发设计模式 开篇 这是"Java并发编程实战"系列的第11天,今天我们聚焦于并发设计模式。并发设计模式是解决多线程环境下常见问题的经典解决方案,它们不仅提供了优雅的设计思路,还能显著提升系统的性能…...
如何做好一份技术文档?从规划到实践的完整指南
如何做好一份技术文档?从规划到实践的完整指南 🌟 嗨,我是IRpickstars! 🌌 总有一行代码,能点亮万千星辰。 🔍 在技术的宇宙中,我愿做永不停歇的探索者。 ✨ 用代码丈量世界&…...
