C/Python/Go示例 | Socket Programing与RPC
Socket Programming介绍
Computer networking这个领域围绕着两台电脑或者同一台电脑内的不同进程之间的数据传输和信息交流,会涉及到许多有意思的话题,诸如怎么确保对方能收到信息,怎么应对数据丢失、被污染或者顺序混乱,怎么提高传输效率,怎么在多人协作的场景下应对交通堵塞,等等。这篇文章要聊的socket programming是这个领域内的内容,socket是计算机的接口,一台计算机的应用程序可以把数据放入到socket接口,传输到另一台计算机的socket接口。从应用程序的角度而言,它只在乎发送本地用户的数据和接受其他用户的数据,而并不在乎数据是怎么完成传输的。
考虑到网络是个庞大复杂的话题——互联网是世界上运行规模最大的计算机网络实现,但这个领域外的大多数公众对于互联网是如何运转的所知甚少,例如,当我们在浏览器内输入网址并敲击回车之后,究竟发生了什么才能让浏览器加载出缤纷绚丽的内容呈现到我们眼前,这实在是很有意思的话题——前辈们设计了网络的五层模型,来帮助人类有效地分工协作解决网络这个难题。模型的最顶层是应用层,是我们大多数人会打交道的层级。
🧑 Application Layer (HTTP, FTP, NFS, SMTP)
|
| 🔌 Socket API (e.g. your Go/C++/C/Python code calls)
│
🌐 Transport Layer (TCP / UDP)
│
📦 Internet Layer (IP)
│
🧰 Link Layer (Ethernet / Wi-Fi)
│
🧱 Physical Layer (Electrical signals, radio, fiber)
Socket是连接应用层和传输层的桥梁,应用程序可以调用一系列简单的Socket API来完成数据传输,实际的传输任务会由操作系统的Kernel负责。我常常感慨计算机领域中「封装」和「分层」的哲学思维很了不起,我们由此得以在前辈们搭建的基础上继续添砖加瓦,做出自己的微小贡献。难以想象如果为了刷小红书我们需要自己写一套操作系统、设计芯片、焊接电路、铺设网线电缆…
Socket API实际上是在调用操作系统内核的system call, 主要函数如下。注意到,网络传输总是会涉及到两方——发送请求讨要数据的client, 以及接受请求提供数据的server——因此我们需要区分下列这些system calls对于这两个socket的相应操作,我对每个system call都附上了对应的Linux manual链接。
-
client/server通用操作:
-
socket creates an endpoint for communication and returns a file descriptor
sockfd
that refers to that endpoint.-
int socket(int domain, int type, int protocol);
-
-
send send a message to a socket
-
ssize_t send(int sockfd, const void buf[.size], size_t size, int flags);
-
-
recv receive a message from a socket
-
ssize_t recv(int sockfd, void buf[.size], size_t size, int flags);
-
-
-
client建立连接:
- connect connects the socket referred to by the file descriptor
sockfd
to the address specified byaddr
.-
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
-
- connect connects the socket referred to by the file descriptor
-
server建立连接:
-
bind assigns the address specified by
addr
to the socket referred to by the file descriptorsockfd
. Traditionally, this operation is called “assigning a name to a socket”.-
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
-
-
listen marks the socket referred to by
sockfd
as a passive socket, that is, as a socket that will be used to accept incoming connection requests usingaccept
.-
int listen(int sockfd, int backlog);
-
-
accept accept a connection on a socket
-
int accept(int sockfd, struct sockaddr *_Nullable restrict addr, socklen_t *_Nullable restrict addrlen);
-
-
Socket Programming示例
下面我们来看看怎么使用编程语言,来调用上面这些socket programming中涉及到的system calls.
我要实现的应用是非常简单的echo server, 不管client发送什么信息,server都会原封不动地将其返还。我会分别使用C, Python和Go这三种语言来实现。注意到,虽然C是最底层、最接近计算机硬件的语言,但在socket programming的例子中,C和Python/Go同样都属于是相对高层的语言,因为它们都在调用相同的system calls.
C - server
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> // for close()
#include <arpa/inet.h> // for inet_ntoa()
#include <sys/socket.h> // for socket(), bind(), listen(), accept()
#include <netinet/in.h> // for sockaddr_in#define PORT 9000
#define BUFFER_SIZE 1024int main() {int server_fd, client_fd;struct sockaddr_in server_addr, client_addr;char buffer[BUFFER_SIZE];// 1. Create a TCP socketserver_fd = socket(AF_INET, SOCK_STREAM, 0);if (server_fd < 0) {perror("socket");exit(1);}// 2. Bind the socket to an IP and portmemset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET; // IPv4server_addr.sin_addr.s_addr = INADDR_ANY; // Any interface (0.0.0.0)server_addr.sin_port = htons(PORT); // Convert port to network byte orderif (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {perror("bind");exit(1);}// 3. Listen for incoming connectionsif (listen(server_fd, 1) < 0) {perror("listen");exit(1);}printf("Echo server listening on port %d...\n", PORT);// 4. Accept a connectionsocklen_t client_len = sizeof(client_addr);client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);if (client_fd < 0) {perror("accept");exit(1);}printf("Client connected: %s:%d\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));// 5. Read data and echo it backwhile (1) {ssize_t bytes_read = read(client_fd, buffer, sizeof(buffer));if (bytes_read <= 0) break; // client closed or errorprintf("Received : %s", buffer);write(client_fd, buffer, bytes_read); // echo back}// 6. Close socketsclose(client_fd);close(server_fd);printf("Connection closed.\n");return 0;
}
C - client
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> // for close()
#include <arpa/inet.h> // for inet_pton()
#include <sys/socket.h> // for socket(), connect()
#include <netinet/in.h> // for sockaddr_in#define PORT 9000
#define BUFFER_SIZE 1024int main() {int sock_fd;struct sockaddr_in server_addr;char buffer[BUFFER_SIZE];// 1. Create a TCP socketsock_fd = socket(AF_INET, SOCK_STREAM, 0);if (sock_fd < 0) {perror("socket");exit(1);}// 2. Set server addressmemset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(PORT);if (inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr) <= 0) {perror("inet_pton");exit(1);}// 3. Connect to serverif (connect(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {perror("connect");close(sock_fd);exit(1);}printf("Connected to server on port %d.\n", PORT);// 4. Read input and send to serverwhile (fgets(buffer, sizeof(buffer), stdin) != NULL) {write(sock_fd, buffer, strlen(buffer));ssize_t bytes_received = read(sock_fd, buffer, sizeof(buffer) - 1);if (bytes_received <= 0) break;buffer[bytes_received] = '\0'; // null-terminateprintf("Echo from server: %s", buffer);}// 5. Close socketclose(sock_fd);printf("Disconnected.\n");return 0;
}
Python - server
import socketdef main():s = socket.socket()s.bind(("127.0.0.1", 9000))s.listen(1)conn, remote_addr = s.accept()print(f"Connected to remote: {remote_addr}")while True:data = conn.recv(1024)if not data:breakconn.sendall(data)if __name__ == '__main__':main()
Python - client
import socketdef main():s = socket.socket()s.connect(("127.0.0.1", 9000))while True:msg = input("Enter message: ")if not msg:breaks.sendall(msg.encode())data = s.recv(1024)print(f"Received: {data.decode()}")if __name__ == '__main__':main()
Go - server
package mainimport ("fmt""net""os"
)func handleConnection(conn net.Conn) {remoteAddr := conn.RemoteAddr()defer func(c net.Conn) {fmt.Println("Closing connection with remote: ", remoteAddr)c.Close()}(conn)buf := make([]byte, 1024)fmt.Println("Connected with remote: ", remoteAddr)for {n, err := conn.Read(buf)if err != nil {return}received := string(buf[:n])fmt.Println("Received: ", received)conn.Write([]byte(received))}
}func main() {fmt.Println("Starting echo server")listener, _ := net.Listen("tcp", ":9000")for {conn, _ := listener.Accept()go handleConnection(conn)}
}
Go - client
package mainimport ("bufio""fmt""net""os"
)func main() {conn, err := net.Dial("tcp", ":9000")if err != nil {fmt.Println("Error connecting to server:", err)return}defer conn.Close()fmt.Println("Connected to server. Type messages and press Enter:")scanner := bufio.NewScanner(os.Stdin)for scanner.Scan() {text := scanner.Text()_, err := conn.Write([]byte(text))if err != nil {fmt.Println("Write error:", err)break}// Read server responsebuf := make([]byte, 1024)n, err := conn.Read(buf)if err != nil {fmt.Println("Read error:", err)break}fmt.Println("Server replied:", string(buf[:n]))}
}
分析
代码风格
我们可以很明显地看到三份代码中的相同模式:server都是在 socket()
创建socket后通过 bind
/ listen
/ accept
建立连接,client则是创建socket后使用 connect
(不同语言下的封装不同,Go对应的是 Dial
)来访问server。
C调用system calls的风格最为淳朴
#include <sys/socket.h>server_fd = socket(AF_INET, SOCK_STREAM, 0);
而Python需要借助 os
module和CPython中的C bindings, Go则是要用 syscall
module(感兴趣可以逛逛源码中的封装)。
另一个代码风格的差别在于,C和Python例子中的server都是只能和单个client进行连接,而Go可以做到多个,得益于goroutine的设计,实现concurrency轻松自然。
关于client
接着来聊聊client, 注意到在server运行之后,我们可以用任意client代码来访问,例如运行Go client代码来访问Python server(这就是client-server思想的魅力之一,让双方可以采用自己喜欢的方式)。 再进一步,其实我们甚至并不需要写client代码,因为我们这个极度简化的server并不在乎client究竟是什么——是一段C/Python/Go程序,又或者是什么其他语言——只要对方使用相同的地址,以及相同的传输层协议(通常是TCP),就足够了。
我们可以使用Unix命令行工具netcat来作为一个极度轻盈便捷的client
% netcat 127.0.0.1 9000
Hi there
Hi there
Don't worry, be happy :)
Don't worry, be happy :)
达成的效果和C/Py/Go的client一致,区别无非在于在这些编程语言中我们可以增添一些个性化操作,例如可以将字符串搭配上 "Server replied: "
之后再print出来。
关于address
绝大多数情况下,socket都是和某个IP地址和port(在上面例子中我统一用port 9000来运行server)绑定起来,这一类是Internet (TCP/IP) sockets, 另一类常见的是Unix domain sockets, 它们是和特殊的文件路径绑定起来的。
例如,Docker其实是通过 /var/run/docker.sock
这个socket文件进行数据传输的,我们在terminal中执行Docker CLI操作时
docker container ls --all
docker info
实际上等同于执行下列命令(完整的Docker engine API可以查阅官网):
curl --unix-socket /var/run/docker.sock 'http://localhost/containers/json?all=true'
curl --unix-socket /var/run/docker.sock http://localhost/info
我们可以稍微改动一下我们的echo server代码来使用Unix sockets, 实现的效果和先前采用internet sockets时是一致的。不过要注意,socket API的system calls会自动为我们创建相应的socket文件,但却不会在事后清理掉,需要我们手动清理。下列是三种编程语言对应的不同类型sockets实现方法:
C:
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(9000);
addr.sin_addr.s_addr = INADDR_ANY;
bind(server_fd, (struct sockaddr *)&addr, sizeof(addr));
int server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "/tmp/server.sock");
unlink("/tmp/server.sock"); // make sure the file doesn't already exist
bind(server_fd, (struct sockaddr *)&addr, sizeof(addr));
Python:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("127.0.0.1", 9000))
import os
SOCK_PATH = "/tmp/server.sock"
if os.path.exists(SOCK_PATH):os.remove(SOCK_PATH)s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.bind(SOCK_PATH)
Go:
listener, _ := net.Listen("tcp", "127.0.0.1:9000")
sockPath := "/tmp/server.sock"
_ = os.Remove(sockPath)
listener, _ := net.Listen("unix", sockPath)
一点小知识:像/tmp/server.sock
这样的socket文件和常规文件不同——虽然本质上而言所有文件都是0和1构成的bytes, 但是我们如何与各种文件进行交互取决于它们的具体类型——它本身不存储任何数据,它只是一个传输数据的媒介,因此我们无法“打开”阅读这个文件,常规的vim /tmp/server.sock
操作在此时也没有意义。
除了上述两类socket (AF_INET
, AF_UNIX
)外,其实还有一些十分罕见的类型,但我们大概很少有机会能够遇见:
AF_NETLINK
(Linux kernel IPC)AF_PACKET
(used in packet sniffers like tcpdump)AF_CAN
(Controller Area Network, used in automotive systems)AF_BLUETOOTH
,AF_VSOCK
(for VM-host communication)
所有这些 AF_*
变量统称为address family, 也就是socket system call中的第一个参数 domain
int socket(int domain, int type, int protocol);
完整的address family列表可以查看Linux manual.
RPC
Socket programming遵循着「client索取数据,server满足提供数据」的设计思路,是现代网络的重要构成,但是如果只依靠它来支撑起整个网络世界未免有些简陋。我们的echo server大概是世界上最简陋的服务器之一,它只能提供「返还用户的输入值」这一个信息服务。而在现实世界中,绝大多数的server都包含着丰富的服务和功能,我们作为client试图去获取这些服务时,需要告诉server我们当前正在访问什么service, 如果采用原始的socket programming进行运作的话,每个service都需要和某个特定的socket进行绑定。
以小红书为例,假设要把访问推荐主页功能绑定在port 9000, 打开帖子功能在 port 9002, 添加评论在9003, 删除评论在9004, 点赞在9005, 收藏在9006…
It just doesn’t work.
现实中的做法是把小红书的server放在单一port (用HTTPS协议的话是port 443)上运行(严格来说应该是面向大众的port, 公司内部还有许多运行在其他port上的server来支撑整套庞大的服务系统运行)。
因此,我们需要设计一套能够让client告诉server “我想要访问这个功能”的方案,而RPC (Remote Procedure Call) 便是众多方案中的一种。现在,我们来拓展一下我们简陋的echo server, 给它增添一个新功能 ToUpper
, 可以把client发送来的字符串转成大写字符之后再返还回去。
借助RPC我们可以告诉server我们想要使用 Echo
和 ToUpper
这两个service中的哪一个,让我们用下列的Go实现来方便理解。为了尽可能地让代码简洁,我删掉了error handling的部分。
Go server:
package mainimport ("fmt""net""net/rpc""strings"
)// Request and Response types
type Request struct {Param string
}type Response struct {Result string
}// StringService with exported methods for RPC
type StringService struct{}func (s *StringService) ToUpper(req Request, res *Response) error {res.Result = strings.ToUpper(req.Param)return nil
}func (s *StringService) Echo(req Request, res *Response) error {res.Result = req.Paramreturn nil
}func main() {rpc.Register(&StringService{})listener, err := net.Listen("tcp", ":9000")fmt.Println("RPC server listening on port 9000")for {conn, _ := listener.Accept()go rpc.ServeConn(conn)}
}
Go client:
package mainimport ("fmt""net/rpc"
)func main() {client, _ := rpc.Dial("tcp", "localhost:9000")defer client.Close()testMethods := []string{"StringService.ToUpper", "StringService.Echo"}testString := "Don't worry, be happy"for _, method := range testMethods {req := Request{Param: testString}var res Responseclient.Call(method, req, &res)fmt.Printf("Method: %s → Result: %s\n", method, res.Result)}
}
在server这台计算机上运行这段Go程序,其中变量 StringService
处在内存中,当client发送信息 "StringService.ToUpper"
给server后,server会在它的这段Go程序里调用 StringService
这个对象的 ToUppe
函数,得到的结果暂存在内存中,server随后将结果发送给client, 由此实现了一段完整的通信服务。
Client通知server去执行某个对象的函数,就如同那个对象就在client的本地内存中一般可以“支配”,但实际上那个对象位于遥远的server之中,client也只能遵循双方约定好的接口来调用那个对象,而不能为所欲为——既不知道它内部有哪些fields/ states/ methods, 也不知道它们具体是怎么实现的。
以下便是来自维基百科的对于RPC的完整定义:
In distributed computing, a remote procedure call (RPC) is when a computer program causes a procedure (subroutine) to execute in a different address space (commonly on another computer on a shared computer network), which is written as if it were a normal (local) procedure call, without the programmer explicitly writing the details for the remote interaction.
回顾我们上面的server代码,信息量最大的一行代码是
rpc.Register(&StringService{})
所谓的register究竟是什么神奇的操作?
简单来说便是server在内存中维护着一个mapping/ look-up table, 能够按照对象函数的名字来定位目标。于是在收到client的消息时,server可以执行相应的操作(只要它能够找到相应的函数)。
{"StringService.ToUpper" -> StringService.ToUpper,"StringService.Echo" -> StringService.Echo
}
手写RPC
我们可以通过自己动手写一个迷你版本的RPC来加深理解。
server:
package mainimport ("bufio""encoding/json""fmt""net""reflect""strings"
)type Request struct {Method string `json:"method"`Param string `json:"param"`
}type Response struct {Result string `json:"result"`Error string `json:"error,omitempty"`
}// ========== Service Implementations ==========type StringService struct{}func (s *StringService) ToUpper(inputString string) string {return strings.ToUpper(inputString)
}func (s *StringService) Echo(inputString string) string {return inputString
}// ========== Dynamic Dispatcher ==========
var serviceRegistry = map[string]interface{}{"StringService": &StringService{},
}func handleConnection(conn net.Conn) {defer conn.Close()scanner := bufio.NewScanner(conn)encoder := json.NewEncoder(conn)for scanner.Scan() {line := scanner.Text()var req Requestif err := json.Unmarshal([]byte(line), &req); err != nil {json.NewEncoder(conn).Encode(Response{Error: "invalid request"})continue}// Split method name into "Service.Method"parts := strings.Split(req.Method, ".")if len(parts) != 2 {encoder.Encode(Response{Error: "invalid method format"})continue}serviceName, methodName := parts[0], parts[1]service, ok := serviceRegistry[serviceName]if !ok {encoder.Encode(Response{Error: "unknown service"})continue}method := reflect.ValueOf(service).MethodByName(methodName)if !method.IsValid() {encoder.Encode(Response{Error: "unknown method"})continue}args := []reflect.Value{reflect.ValueOf(req.Param)}results := method.Call(args)res := Response{Result: results[0].Interface().(string)}encoder.Encode(res)}
}func main() {listener, _ := net.Listen("tcp", ":9000")fmt.Println("RPC server listening on port 9000")for {conn, _ := listener.Accept()remoteAddr := conn.RemoteAddr()fmt.Println("Connected with remote: ", remoteAddr)go handleConnection(conn)}
}
client:
package mainimport ("bufio""encoding/json""fmt""net"
)func main() {conn, _ := net.Dial("tcp", "localhost:9000")defer conn.Close()testMethods := []string{"StringService.ToUpper", "StringService.Echo"}testString := "Don't worry, be happy"scanner := bufio.NewScanner(conn)for _, method := range testMethods {req := Request{Method: method, Param: testString}data, _ := json.Marshal(req)conn.Write(data)conn.Write([]byte("\n")) // newline is needed for Scanner on serverif scanner.Scan() {var res Responsejson.Unmarshal(scanner.Bytes(), &res)if res.Error != "" {fmt.Println("Error:", res.Error)} else {fmt.Println("Result:", res.Result)}}}
}
Server中的变量 serviceRegistry
便是我们刚才说所说的那个mapping, 它是RPC实现的核心之一,Go标准库 net/rpc
对此的实现要漂亮得多:
// Server represents an RPC Server.
type Server struct {serviceMap sync.Map // map[string]*servicereqLock sync.Mutex // protects freeReqfreeReq *RequestrespLock sync.Mutex // protects freeRespfreeResp *Response
}
当然啦,这对于业界的实际应用还是远远不够的。等到我对于Google开源的gRPC有足够深的理解后再来写文章吧~
Feature | Used in |
---|---|
Protobuf-based serialization | gRPC |
TLS + authentication | gRPC, Thrift |
Streaming APIs | gRPC |
Bidirectional channels | gRPC, WebSocket-based RPC |
Retry policies | gRPC |
Load balancing | gRPC, Dubbo |
Tracing/metrics | gRPC, Zipkin, OpenTelemetry |
相关文章:
C/Python/Go示例 | Socket Programing与RPC
Socket Programming介绍 Computer networking这个领域围绕着两台电脑或者同一台电脑内的不同进程之间的数据传输和信息交流,会涉及到许多有意思的话题,诸如怎么确保对方能收到信息,怎么应对数据丢失、被污染或者顺序混乱,怎么提高…...

Spring是如何实现无代理对象的循环依赖
无代理对象的循环依赖 什么是循环依赖解决方案实现方式测试验证 引入代理对象的影响创建代理对象问题分析 源码见:mini-spring 什么是循环依赖 循环依赖是指在对象创建过程中,两个或多个对象相互依赖,导致创建过程陷入死循环。以下通过一个简…...

C++ Saucer 编写Windows桌面应用
文章目录 一、背景二、Saucer 简介核心特性典型应用场景 三、生成自己的项目四、以Win32项目方式构建Win32项目禁用最大化按钮 五、总结 一、背景 使用Saucer框架,开发Windows桌面应用,把一个html页面作为GUI设计放到Saucer里,隐藏掉运行时弹…...
中国政务数据安全建设细化及市场需求分析
(基于新《政务数据共享条例》及相关法规) 一、引言 近年来,中国政府高度重视数字政府建设和数据要素市场化配置改革。《政务数据共享条例》(以下简称“《共享条例》”)的发布,与《中华人民共和国数据安全法》(以下简称“《数据安全法》”)、《中华人民共和国个人信息…...

【AI News | 20250609】每日AI进展
AI Repos 1、OpenHands-Versa OpenHands-Versa 是一个通用型 AI 智能体,通过结合代码编辑与执行、网络搜索、多模态网络浏览和文件访问等通用工具,在软件工程、网络导航和工作流自动化等多个领域展现出卓越性能。它在 SWE-Bench Multimodal、GAIA 和 Th…...

轻量安全的密码管理工具Vaultwarden
一、Vaultwarden概述 Vaultwarden主要作用是提供一个自托管的密码管理器服务。它是Bitwarden密码管理器的第三方轻量版,由国外开发者在Bitwarden的基础上,采用Rust语言重写而成。 (一)Vaultwarden镜像的作用及特点 轻量级与高性…...

SQLSERVER-DB操作记录
在SQL Server中,将查询结果放入一张新表可以通过几种方法实现。 方法1:使用SELECT INTO语句 SELECT INTO 语句可以直接将查询结果作为一个新表创建出来。这个新表的结构(包括列名和数据类型)将与查询结果匹配。 SELECT * INTO 新…...

开疆智能Ethernet/IP转Modbus网关连接鸣志步进电机驱动器配置案例
在工业自动化控制系统中,常常会遇到不同品牌和通信协议的设备需要协同工作的情况。本案例中,客户现场采用了 罗克韦尔PLC,但需要控制的变频器仅支持 ModbusRTU 协议。为了实现PLC 对变频器的有效控制与监控,引入了开疆智能Etherne…...

NineData数据库DevOps功能全面支持百度智能云向量数据库 VectorDB,助力企业 AI 应用高效落地
NineData 的数据库 DevOps 解决方案已完成对百度智能云向量数据库 VectorDB 的全链路适配,成为国内首批提供 VectorDB 原生操作能力的服务商。此次合作聚焦 AI 开发核心场景,通过标准化 SQL 工作台与细粒度权限管控两大能力,助力企业安全高效…...

代理服务器-LVS的3种模式与调度算法
作者介绍:简历上没有一个精通的运维工程师。请点击上方的蓝色《运维小路》关注我,下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 我们上一章介绍了Web服务器,其中以Nginx为主,本章我们来讲解几个代理软件:…...
ubuntu清理垃圾
windows和ubuntu 双系统,ubuntu 150GB,开发用,基本不装太多软件。但是磁盘基本用完。 1、查看home目录 sudo du -h -d 1 $HOME | grep -v K 上面的命令查看$HOME一级目录大小,发现 .cache 有26GB,.local 有几个GB&am…...
学习 Hooks【Plan - June - Week 2】
一、React API React 提供了丰富的核心 API,用于创建组件、管理状态、处理副作用、优化性能等。本文档总结 React 常用的 API 方法和组件。 1. React 核心 API React.createElement(type, props, …children) 用于创建 React 元素,JSX 会被编译成该函数…...
宠物车载安全座椅市场报告:解读行业趋势与投资前景
一、什么是宠物车载安全座椅? 宠物车载安全座椅是一种专为宠物设计的车内固定装置,旨在保障宠物在乘车过程中的安全性与舒适性。它通常由高强度材料制成,具备良好的缓冲性能,并可通过安全带或ISOFIX接口固定于车内。 近年来&…...

解决MybatisPlus使用Druid1.2.11连接池查询PG数据库报Merge sql error的一种办法
目录 前言 一、问题重现 1、环境说明 2、重现步骤 3、错误信息 二、关于LATERAL 1、Lateral作用场景 2、在四至场景中使用 三、问题解决之道 1、源码追踪 2、关闭sql合并 3、改写处理SQL 四、总结 前言 在博客:【写在创作纪念日】基于SpringBoot和PostG…...
Neo4j 完全指南:从入门到精通
第1章:Neo4j简介与图数据库基础 1.1 图数据库概述 传统关系型数据库与图数据库的对比图数据库的核心优势图数据库的应用场景 1.2 Neo4j的发展历史 Neo4j的起源与演进Neo4j的版本迭代Neo4j在图数据库领域的地位 1.3 图数据库的基本概念 节点(Node)与关系(Relat…...
day51 python CBAM注意力
目录 一、CBAM 模块简介 二、CBAM 模块的实现 (一)通道注意力模块 (二)空间注意力模块 (三)CBAM 模块的组合 三、CBAM 模块的特性 四、CBAM 模块在 CNN 中的应用 一、CBAM 模块简介 在之前的探索中…...

使用VMware克隆功能快速搭建集群
自己搭建的虚拟机,后续不管是学习java还是大数据,都需要集群,java需要分布式的微服务,大数据Hadoop的计算集群,如果从头开始搭建虚拟机会比较费时费力,这里分享一下如何使用克隆功能快速搭建一个集群 先把…...

篇章一 论坛系统——前置知识
目录 1.软件开发 1.1 软件的生命周期 1.2 面向对象 1.3 CS、BS架构 1.CS架构编辑 2.BS架构 1.4 软件需求 1.需求分类 2.需求获取 1.5 需求分析 1. 工作内容 1.6 面向对象分析 1.OOA的任务 2.统一建模语言UML 3. 用例模型 3.1 用例图的元素 3.2 建立用例模型 …...
十二、【ESP32全栈开发指南: IDF开发环境下cJSON使用】
一、JSON简介 JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,具有以下核心特性: 完全独立于编程语言的文本格式易于人阅读和编写易于机器解析和生成基于ECMAScript标准子集 1.1 JSON语法规则 {"name"…...

Qt/C++学习系列之列表使用记录
Qt/C学习系列之列表使用记录 前言列表的初始化界面初始化设置名称获取简单设置 单元格存储总结 前言 列表的使用主要基于QTableWidget控件,同步使用QTableWidgetItem进行单元格的设置,最后可以使用QAxObject进行单元格的数据读出将数据进行存储。接下来…...
【Pandas】pandas DataFrame dropna
Pandas2.2 DataFrame Missing data handling 方法描述DataFrame.fillna([value, method, axis, …])用于填充 DataFrame 中的缺失值(NaN)DataFrame.backfill(*[, axis, inplace, …])用于**使用后向填充(即“下一个有效观测值”)…...

基于django+vue的健身房管理系统-vue
开发语言:Python框架:djangoPython版本:python3.8数据库:mysql 5.7数据库工具:Navicat12开发软件:PyCharm 系统展示 会员信息管理 员工信息管理 会员卡类型管理 健身项目管理 会员卡管理 摘要 健身房管理…...

Yolo11改进策略:Block改进|FCM,特征互补映射模块|AAAI 2025|即插即用
1 论文信息 FBRT-YOLO(Faster and Better for Real-Time Aerial Image Detection)是由北京理工大学团队提出的专用于航拍图像实时目标检测的创新框架,发表于AAAI 2025。论文针对航拍场景中小目标检测的核心难题展开研究,重点解决…...
【系统架构设计师-2025上半年真题】综合知识-参考答案及部分详解(回忆版)
更多内容请见: 备考系统架构设计师-专栏介绍和目录 文章目录 【第1题】【第2题】【第3题】【第4题】【第5题】【第6题】【第7题】【第8题】【第9题】【第10题】【第11题】【第12题】【第13题】【第14题】【第15题】【第16题】【第17题】【第18题】【第19题】【第20~21题】【第…...

简单聊下阿里云DNS劫持事件
阿里云域名被DNS劫持事件 事件总结 根据ICANN规则,域名注册商(Verisign)认定aliyuncs.com域名下的部分网站被用于非法活动(如传播恶意软件);顶级域名DNS服务器将aliyuncs.com域名的DNS记录统一解析到shado…...
LTR-381RGB-01RGB+环境光检测应用场景及客户类型主要有哪些?
RGB环境光检测 功能,在应用场景及客户类型: 1. 可应用的儿童玩具类型 (1) 智能互动玩具 功能:通过检测环境光或物体颜色触发互动(如颜色识别积木、光感音乐盒)。 客户参考: LEGO(乐高&#x…...

循环语句之while
While语句包括一个循环条件和一段代码块,只要条件为真,就不断 循环执行代码块。 1 2 3 while (条件) { 语句 ; } var i 0; while (i < 100) {console.log(i 当前为: i); i i 1; } 下面的例子是一个无限循环,因…...

机器学习复习3--模型评估
误差与过拟合 我们将学习器对样本的实际预测结果与样本的真实值之间的差异称为:误差(error)。 误差定义: ①在训练集上的误差称为训练误差(training error)或经验误差(empirical error&#x…...

联邦学习带宽资源分配
带宽资源分配是指在网络中如何合理分配有限的带宽资源,以满足各个通信任务和用户的需求,尤其是在多用户共享带宽的情况下,如何确保各个设备或用户的通信需求得到高效且公平的满足。带宽是网络中的一个重要资源,通常指的是单位时间…...

今日行情明日机会——20250609
上证指数放量上涨,接近3400点,个股涨多跌少。 深证放量上涨,但有个小上影线,相对上证走势更弱。 2025年6月9日涨停股主要行业方向分析(基于最新图片数据) 1. 医药(11家涨停) 代表标…...