万字详解网络编程之socket

一,socket简介

1.什么是socket

socket通常也称作"套接字",⽤于描述IP地址和端⼝,是⼀个通信链的句柄,应用程序通常通过"套接字"向⽹络发出请求或者应答⽹络请求。⽹络通信就是两个进程间的通信,这两个进程之间是如何识别彼此的呢?那就是套接字(Socket),每个套接字由⼀个 IP 地址和⼀个端⼝号组成。
Socket起源于Unix,⽽Unix/Linux基本哲学之⼀就是“⼀切皆⽂件”,对于⽂件⽤【打开】【读写】【关闭】模式来操作
socket是⼀种特殊的⽂件,⼀些socket函数就是对其进⾏的操作(打开、读/写IO、关闭)。
套接字的类型:
1.流式套接字(SOCK_STREAM)
提供了⼀个面向连接、可靠的数据传输服务,数据⽆差错、⽆   重复的发送且按发送顺序接收。内设置流量控制,避免数据流   淹没慢的接收⽅。数据被看作是字节流,无长度限制。
2. 数据报套接字(SOCK_DGRAM)
提供⽆连接服务。数据包以独⽴数据包的形式被发送,不提供⽆差错    保证,数据可能丢失或重复,顺序发送,可能乱序接收。
3.原始套接字(SOCK_RAW)
可以对较低层次协议,如IP、ICMP直接访问。功能强⼤但使⽤较为不便, 主要⽤于⼀些协议的开发。
socket在所有的⽹络操作系统中是必不可少,它是网络通信中应⽤程序对应的进程
和网络协议之间的接⼝。

2.网络字节序

计算机数据存储有两种字节优先顺序:高位字节优先低位字节优先。Internet上数据以⾼位字节优先顺序在网络上传输,所以对于在内部是以低位字节优先⽅式存储数据的机器,在Internet上传输数据时就需要进⾏转换,否则就会出现数据不⼀致。
内存中存储的多字节数据相对于内存地址有大端小端之分,网络数据流同样有大端小端之分。
主机字节序:就是自己的主机内部,内存中数据的存放⽅式,可以分为两种:  
大端字节序(big-endian):按照内存的增长方向,高位数据存储于低位内存中
小端字节序(little-endian):按照内存的增长方向,高位数据存储于⾼位内存中。
⼤多数Intel兼容机都采⽤⼩端模式。
编写一个简单程序判断当前机器是大端机还是小端机
test.c
#include <stdio.h>int main()
{unsigned int num = 0x12345678;unsigned char *p = (unsigned char *)&num;if (*p == 0x78){printf("当前是小端机\n");}else if (*p == 0x12){printf("当前是大端机\n");}else{printf("无法判断\n");}return 0;
}

⽹络字节顺序: 是TCP/IP中规定好的⼀种数据表示格式,它与具体的CPU类型、操作系统等⽆关,⽹络数据流同样有⼤端⼩端之分,也就是说,当接收端收到第⼀个字节的时候,它将这个字节作为⾼位字节还是低位字节处理.从⽽可以保证数据在不同主机之间传输时能够被正确解释。网络字节顺序采⽤big endian排序⽅式(⾼位字节优先)。
⼀般考虑到计算机中的字符与⽹络中的字符存储顺序是不同的,当计算机中的整数与⽹络中的整数进⾏交换时,需要相关的函数进⾏转换。
传输字节时可分两种情况:
1.传输⼀个⾮单字节数据,这时要转换成⽹络字节顺序,
2.传输⼀个单字节数据,这时就不需要转换了,因为单字节数据⽆所谓G高或低字节优先
涉及相关函数如下:
#include <arpa/inet.h>
/*主机字节顺序 --> ⽹络字节顺序*/
uint32_t htonl(uint32_t hostlong); /* IP*/
uint16_t htons(uint16_t hostshort); /* 端⼝*/
in_addr_t inet_addr(const char *cp); //将⼀个点分字符串IP地址转换为⼀个32位的⽹络序列IP地址。所属头⽂件:Winsock2.h (windows)       arpa/inet.h (Linux)
/*⽹络字节顺序 --> 主机字节顺序*/
uint32_t ntohl(uint32_t netlong); /* IP*/
char *inet_ntoa(struct in_addr in);//将⼀个32位的⽹络字节序转换为⼀个点分⼗进制字符串
struct in_addr //结构体in_addr ⽤来表示⼀个32位的IPv4地址。
{
in_addr_t s_addr; //in_addr_t ⼀般为 32位的unsigned int,其字节顺序为⽹络顺序
};

二,基于TCP/IP协议的socket通信

socket抽象层与体系结构关系示意图

当我们使⽤不同的协议进⾏通信时就得使⽤不同的接⼝,还得处理不同协议的各种细节,这就增加了开发的难度,软件也不易于扩展。于是就出现了socket,socket屏蔽了各个协议的通信细节,使得程序员无需关注协议本身,直接使⽤socket提供的接⼝来进⾏互联的不同主机间的进程的通信。

⼈们把socket设计成⽂件,通过描述符我们可以定位到具体的file结构体,file结构体中有个f_type属性,标识了⽂件的类型(⽐如套接⼦类型,so_pcb结构内包含了当前主机的地址和端⼝信息,这给结构的编号就是套接字的⽂件描述符,和进程的pcb块类似)

1. 基于TCP/ip的相关通信api简介

当进程创建⼀个socket套接字时,操作系统会相应的创建出⼀个由⽂件系统管理的socket对象,这个socket对象中包含:发送缓冲区、接收缓冲区、等待队列、 等成员。每⼀个tcp的套接字(socket)都有两个缓冲区,发送缓冲区和接收缓冲区都是先进先出的队列。
其中,等待队列是⾮常重要的成员。操作系统会将调动recv阻塞的进程挂在对应socket的等待队列
中(只是进程的引⽤,不是进程本身),此时进程进⼊阻塞态,不会占⽤CPU资源。
当socket上收到数据后,操作系统会将该socket从等待队列中重新放回到⼯作队列,继续执⾏。

1)创建套接字

#include <sys/types.h> /* See NOTES */#include <sys/socket.h>//建⽴⼀个新的socket(即为建⽴⼀个通信端⼝)int socket(int domain, int type, int protocol);成功返回⾮负的套接字描述符,失败返回 -1参数说明:domain:即协议域,⼜称为协议族(family)Name Purpose Man pageAF_UNIX, AF_LOCAL Local communication unix(7)AF_INET IPv4 Internet protocols ip(7)AF_INET6 IPv6 Internet protocols ipv6(7)AF_IPX IPX - Novell protocolsAF_NETLINK Kernel user interface device netlink(7)AF_X25 ITU-T X.25 / ISO-8208 protocol x25(7)AF_AX25 Amateur radio AX.25 protocolAF_ATMPVC Access to raw ATM PVCsAF_APPLETALK AppleTalk ddp(7)AF_PACKET Low level packet interface packet(7)AF_ALG Interface to kernel crypto APItype:SOCK_STREAM TCPSOCK_DGRAM UDPSOCK_SEQPACKET 为最⼤⻓度固定的数据报提供有序、可靠、基于双向连接的数据
传输路径SOCK_RAW 原始套接字SOCK_RDM 提供不保证排序的可靠数据报层。protocol:⽤于指定socket所使⽤的传输协议编号,通常默认设置为0即可0选择type类型对应的默认协议;IPPROTO_TCP:TCP传输协议;IPPROTO_UDP:UDP传输协议;

2)绑定套接字和服务器地址

注意:⼀般来说,⼀个端⼝释放后会等待两分钟之后才能再被使⽤,SO_REUSEADDR是让端⼝释放后⽴即就可以被再次使⽤。SO_REUSEADDR⽤于对TCP套接字处于TIME_WAIT状态下的socket,才可以重复绑定使⽤。server程序总是应该在调⽤bind()之前设置SO_REUSEADDR套接字选项。先调⽤close()的⼀⽅会进⼊TIME_WAIT状态。
#include <sys/types.h> /* See NOTES */#include <sys/socket.h>//⽤来给参数sockfd的socket设置⼀个名称,该名称由addr参数指向的sockadr结构int bind(int sockfd, const struct sockaddr *addr, socklen_t addrle
n);返回说明:成功返回 0 失败返回 -1⽤途:主要⽤与在TCP中的连接形参说明:sockfd 套接字⽂件描述符addr 服务器地址信息struct sockaddr {sa_family_t sa_family;char sa_data[14];}但在编程中⼀般使⽤下边这种等价结构sockaddr,对于IPV4我们常⽤这个结构注意:使⽤该结构需要包含:#include <netinet/in.h>头⽂件 ****struct sockaddr_in {sa_family_t sin_family; IPV4对应AF_INET//htons()u_int16_t sin_port; 端⼝号//sin_port存储端⼝
号(使⽤⽹络字节顺序)struct in_addr sin_addr; IP地址 //inet_addr()将
字符串形象ip转⽹络字节序};/* Internet address. */struct in_addr {u_int32_t s_addr; IP地址};addrlen addr的⻓度 sizeof(struct sockaddr)//如果使⽤IPV6地址,需要⽤这个结构来定义变量存放ipv6相关信息 struct sockaddr_in6 {sa_family_t sin6_family; /* AF_INET6 */in_port_t sin6_port; /* port number */uint32_t sin6_flowinfo; /* IPv6 flow information */struct in6_addr sin6_addr; /* IPv6 address */uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */};struct in6_addr {unsigned char s6_addr[16]; /* IPv6 address */};

3)监听模式

#include <sys/socket.h>//⽤于等待参数sockfd的scoket连线int listen(int sockfd, int backlog);返回值说明:成功返回0,失败返回-1sockfd 套接字⽂件描述符backlog 监听队列⻓度(等待连接的客户端的个数)缺省值20,最⼤值为128即为规定了内核应该为相应套接⼝排队的最⼤连接个数

4)等待客户端连接的到来

#include <sys/types.h> #include <sys/socket.h> //接收socket的连线int accept(int sockfd, struct sockaddr *addr, socklen_t *addrle
n); 返回值说明:成功返回连接的客户端的套接字⽂件描述符 失败返回 -1参数说明:sockfd 服务器套接字⽂件描述符addr 客户端信息地址,做返回值⽤的,不获取可以直接输⼊NULLaddrlen addr的⻓度,注意是⼀个指针类型 ,传⼊指定地址的⻓度,不指定
则NULL

5)客户端建立socket连线

#include <sys/types.h> /* See NOTES */#include <sys/socket.h>/*⽤于将参数sockfd的socket连接到参数addr指定的⽹络地址*/int connect(int sockfd, const struct sockaddr *addr, socklen_t addrl
en);addr:服务端的地址addrlen:为scokarr的结构⻓度返回值:返回0时,表明该连接已经成功建⽴,返回-1时,表明该连接发⽣了错误,即没有成
功建⽴连接。注意:connect() 函数不阻塞,所以在使⽤之前要确保服务器已经进⼊等待连接状态。

6)读写函数

读(read/recvfrom/msgrcv):
读的本质来说其实不能是读,在实际中, 具体的接收数据不是由这些调⽤来进⾏,是由于系统底层⾃动完成的。read 也好,recv 也好只负责把数据从底层缓冲copy 到我们指定的位置。
写(send/write):
写的本质也不是进⾏发送操作,⽽是把⽤户态的数据copy 到系统底层去,然后再由系统进⾏发送操作,send,write返回成功,只表示数据已经copy 到底层缓冲,⽽不表示数据已经发出,更不能表示对⽅端⼝已经接收到数据。
#include<unistd.h>
//将数据写⼊已打开的⽂件内,写⼊count个字节到参数fd所指的⽂件内。
ssize_t write(int fd,const void*buf,size_t count);
//从已打开的⽂件中读取数据
ssize_t read(int fd,void*buf,size_t count);
返回值:读取到的实际数据数,如果返回0表示已经到达⽂件末尾或⽆可读取的数据,当read()函数
返回值为0时,
表示对端已经关闭了 socket,这时候也要关闭这个socket,否则会导致socket泄露。
当read()或者write()函数返回值⼤于0时,表示实际从缓冲区读取或者写⼊的字节数⽬
当read()或者write()返回-1时,⼀般要判断errno
⼀般是读写操作超时了,还未返回。这个超时是指socket的SO_RCVTIMEO与SO_SNDTIMEO两个属
性。
所以在使⽤阻塞socket时,不要将超时时间设置的过⼩。不然返回了-1,
你也不知道是socket连接是真的断开了,还是正常的⽹络抖动。⼀般情况下,阻塞的socket返回
了-1,
都需要关闭重新连接。
Close()和shutdown()——结束数据传输
当所有的数据操作结束以后,你可以调⽤close()函数来释放该socket,从⽽
停⽌在该socket上的任何数据操作:close(sockfd);

2.并发服务器

TCP服务器⼀次只能接收⼀个客户端的连接的请求,只有在该客户端的所有请求都满⾜后,服务器才可以继续响应后边的请求,如果⼀个客户端占⽤服务器不释放,其他客户端都不能⼯作了,因此上述的TCP服务器⼜称为循环服务器,鉴于TCP循环服务器的缺陷,很少TCP服务器采⽤。
为了解决循环TCP服务器的缺陷,⼈们⼜想出了并发服务器模型。并发服务器的思想为:每⼀个客户端的请求并不由服务器直接处理,⽽是由服务器创建⼀个子进程或⼦线程来解决。
示例程序:
server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <netinet/in.h>#define PORT 8888
#define BUF_SIZE 1024// 线程函数:处理一个客户端
void *handle_client(void *arg)
{int client_sock = *(int *)arg;free(arg);char buffer[BUF_SIZE];int n;while ((n = read(client_sock, buffer, BUF_SIZE - 1)) > 0){buffer[n] = '\0';printf("[客户端消息] %s\n", buffer); // 在服务器端显示客户端消息}printf("客户端断开连接。\n");close(client_sock);return NULL;
}int main()
{int server_sock, *client_sock;struct sockaddr_in server_addr, client_addr;socklen_t client_len = sizeof(client_addr);// 创建socketserver_sock = socket(AF_INET, SOCK_STREAM, 0);if (server_sock < 0){perror("socket");exit(EXIT_FAILURE);}int opt = 1;setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = INADDR_ANY;server_addr.sin_port = htons(PORT);if (bind(server_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0){perror("bind");close(server_sock);exit(EXIT_FAILURE);}if (listen(server_sock, 5) < 0){perror("listen");close(server_sock);exit(EXIT_FAILURE);}printf("服务器已启动,监听端口 %d...\n", PORT);while (1){client_sock = malloc(sizeof(int));*client_sock = accept(server_sock, (struct sockaddr *)&client_addr, &client_len);if (*client_sock < 0){perror("accept");free(client_sock);continue;}printf("新客户端连接:%s:%d\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));pthread_t tid;pthread_create(&tid, NULL, handle_client, client_sock);pthread_detach(tid);}close(server_sock);return 0;
}

client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>#define PORT 8888
#define BUF_SIZE 1024int main()
{int sock;struct sockaddr_in server_addr;char buffer[BUF_SIZE];// 创建socketsock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0){perror("socket");exit(EXIT_FAILURE);}server_addr.sin_family = AF_INET;server_addr.sin_port = htons(PORT);server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 服务器IPif (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0){perror("connect");close(sock);exit(EXIT_FAILURE);}printf("已连接到服务器,现在可以输入消息...\n");while (1){// 从标准输入读取消息printf("输入: ");if (fgets(buffer, BUF_SIZE, stdin) == NULL)break;// 发送到服务器write(sock, buffer, strlen(buffer));}close(sock);return 0;
}

运行结果:

三,基于UDP/IP协议的socket通信

 基于UDP/IP通信的相关api简介

1)发送UDP报格式数据

#include <sys/types.h>
#include <sys/socket.h>
//把UDP数据报发给指定地址
int sendto (int sockfd, const void *buf, int len, unsigned int flags, const struct sockaddr *to, int tolen);
参数说明:sockfd 套接字⽂件描述符buf 存放发送的数据len 期望发送的数据⻓度flags 0to struct sockaddr_in类型,指明UDP数据发往哪⾥报tolen: 对⽅地址⻓度,⼀般为:sizeof(struct sockaddr_in)。

2)接收UDP报格式数据

#include <sys/types.h>
#include <sys/socket.h>
//接收UDP的数据
int recvfrom(int sockfd, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen);
参数意义和sentdo类似,其中romlen传递是接收到地址的⻓度
例如 int p=sizeof(struct adrr_in),最后⼀个参数就传为&p
注意:如果数据流量突然增⼤,也可以通过如下函数设置发送或接收缓冲区的大小。
调整UDP缓冲区⼤⼩:使用函数setsockopt()函数修改接收缓冲区大小
int setsockopt(int sockfd,int level,int optname,const void *optval,socklen_t optlen);
level:选项定义的层次:⽀持soL_SOCKET,IPPROTO_TCP,IPPROTO_IP和
IPPROTO_IPV6optname。
需设置得选项so_RCVBUF(接收缓冲区),So_SNDBUF(发送缓冲区)

3) 创建套接字 (Socket)

在进行任何网络通信之前,应用程序必须先向操作系统请求一个网络通信的“端点”,这个端点就是套接字。

#include <sys/types.h>
#include <sys/socket.h>// 创建一个套接字
int socket(int domain, int type, int protocol);```**参数说明:***   `domain`: 指定通信协议族。对于IPv4网络通信,这个值通常是 `AF_INET`。
*   `type`: 指定套接字的类型。*   对于UDP通信,这个值必须是 `SOCK_DGRAM` (Datagram),表示这是一个提供数据报服务的套接字,不建立连接,每个数据包都是独立的。*   (作为对比,TCP使用 `SOCK_STREAM`)
*   `protocol`: 协议类型。通常设置为 `0`,让系统根据 `domain` 和 `type` 自动选择最合适的协议(对于 `AF_INET` 和 `SOCK_DGRAM`,系统会选择IPPROTO_UDP)。**返回值:***   **成功**:返回一个新的文件描述符 (一个非负整数),代表创建的套接字。后续的所有操作都将通过这个文件描述符进行。
*   **失败**:返回 `-1`,并设置全局变量 `errno` 来表示错误原因。**示例:**
```c
int sockfd;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {perror("socket creation failed");exit(EXIT_FAILURE);
}

4) 绑定地址和端口 (Bind)

对于需要接收数据的UDP程序(通常是服务器端),必须将其创建的套接字与一个具体的IP地址和端口号关联起来。这样,当网络中有数据包发送到这个IP和端口时,操作系统才知道应该将数据交给哪个程序处理。

#include <sys/types.h>
#include <sys/socket.h>// 将套接字与一个地址绑定
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数说明:

  • sockfd: socket() 函数返回的套接字文件描述符。

  • addr: 一个指向 struct sockaddr 结构体的指针,但实际使用中通常传入一个 struct sockaddr_in 结构体(需要进行类型强制转换)。该结构体包含了要绑定的IP地址和端口号。

  • addrlen: addr 结构体的长度,通常为 sizeof(struct sockaddr_in)。

struct sockaddr_in 结构体详解:

struct sockaddr_in {sa_family_t    sin_family; // 地址族, 必须是 AF_INETin_port_t      sin_port;   // 端口号 (需要使用 htons() 转换)struct in_addr sin_addr;   // IPv4 地址
};struct in_addr {uint32_t       s_addr;     // 32位的IPv4地址 (需要使用 inet_addr() 或 INADDR_ANY)
};

注意: 端口号和IP地址必须从“主机字节序”转换为“网络字节序”。通常使用 htons() (Host to Network Short) 来转换端口,使用 inet_addr("127.0.0.1") 或 INADDR_ANY (表示绑定到本机所有IP地址) 来设置地址。

返回值:

  • 成功:返回 0。

  • 失败:返回 -1,并设置 errno。

示例:

struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = INADDR_ANY; // 监听任意网卡的请求
servaddr.sin_port = htons(8888);       // 绑定到8888端口if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {perror("bind failed");exit(EXIT_FAILURE);
}

对于UDP客户端,通常不需要显式调用 bind()。因为在第一次调用 sendto() 时,操作系统会自动为其分配一个临时的端口号。

5) 关闭套接字 (Close)

当通信结束时,应该释放套接字占用的系统资源。这个操作就像关闭一个文件一样。

#include <unistd.h>// 关闭一个文件描述符
int close(int fd);

参数说明:

  • fd: 需要关闭的文件描述符,这里就是 socket() 返回的套接字文件描述符。

返回值:

  • 成功:返回 0。

  • 失败:返回 -1,并设置 errno。

示例:

printf("通信结束,关闭套接字。\n");
close(sockfd);

总结:一个典型UDP程序的API调用流程

  • 服务器端:

    1. socket(): 创建套接字。

    2. bind(): 为套接字绑定一个固定的IP和端口,以便客户端能找到它。

    3. recvfrom(): 循环等待并接收来自客户端的数据。

    4. sendto(): (可选)向客户端发送响应。

    5. close(): 程序结束时关闭套接字。

  • 客户端:

    1. socket(): 创建套接字。

    2. sendto(): 向服务器的指定IP和端口发送数据。

    3. recvfrom(): (可选)接收来自服务器的响应。

    4. close(): 程序结束时关闭套接字。

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

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

相关文章

维度跃迁:当万物皆成电路,智能将从“拥有”变为“存在”

我们习以为常的电子世界&#xff0c;其本质是一个由电路构成的精密宇宙。而一场从二维到三维的终极变革&#xff0c;正在悄然酝酿&#xff0c;它将彻底颠覆我们创造和交互的方式。一、电子世界的本质&#xff1a;一切都是电路 在深入未来之前&#xff0c;我们首先要理解当下。电…

大语言模型预训练数据采集与清洗技术实践:从语料到知识库的全流程优化

大语言模型(LLM)的性能上限由 “数据质量 数据规模 数据多样性” 共同决定 —— 预训练阶段的海量语料决定模型的泛化能力与语言理解基础,而知识库数据则决定模型的知识准确性与领域专业性。当前 LLM 落地面临的核心痛点之一,便是 “数据脏、处理难、知识杂”:预训练语料…

模拟音频采集设备的制作

模拟音频程序与设备的制作 需要设备 esp32s3 pcm1808 pcm5102(非必须) 程序界面 程序代码 代码链接

Java Modbus通信实战(四):Modbus通信测试与故障排查

在工业现场&#xff0c;设备通信系统就像工厂的神经网络&#xff0c;连接着各种传感器、控制器和执行器。当你搭建好这套系统后&#xff0c;最关键的一步就是全面测试&#xff0c;确保每个环节都能正常工作。 就像汽车出厂前要经过严格的路试一样&#xff0c;Modbus RTU通信系统…

少儿编程C++快速教程之——1. 基础语法和输入输出

1. 欢迎来到C编程世界&#xff01; 1.1 什么是编程&#xff1f; 编程就像是给计算机写一份详细的"说明书"&#xff0c;告诉它该做什么、怎么做。C是一种强大的编程语言&#xff0c;可以用来创建游戏、应用程序和各种有趣的软件&#xff01; 1.2 第一个C程序&#xff…

arma::imat22

arma::imat22 是 Armadillo C 线性代数库中定义的一个固定大小的 2x2 有符号整数矩阵类型。它主要用于处理小型、维度在编译时已知的整数矩阵&#xff0c;因其在栈上分配内存&#xff0c;故通常比动态矩阵有更高的效率。 下面是一个汇总了 arma::imat22 主要特性的表格&#xf…

狗都能看懂的HunYuan3D 1.0详解

HunYuan3D 1.0 HunYuan3D 1.0是2024年9月发布的一篇论文。虽然站在现在的时间节点&#xff0c;HunYuan3D系列已经出到2.5了&#xff0c;但是1.0版本的改进思路&#xff0c;和它trick集成的做法&#xff0c;还是很值得学习的。由于文章用到了很多技术&#xff0c;由于篇幅有限&a…

踏脚迈入奇幻乐园

每天早上上班的路上都会经过一个小花园。它被夹在丁字路口的拐角&#xff0c;面积不大&#xff0c;匆匆而过的行人都不会注意到它。但如果顺着几个不起眼的入口走进去&#xff0c;里面却是别有洞天。清早的街道还没有车水马龙的喧哗&#xff0c;花园里静悄悄的。各式各样的花草…

内存越界引发线程函数调用堆栈回溯异常以及INT 3软中断实战分析案例分享

目录 1、问题说明 2、导出dump文件时只是遇到了INT 3软中断&#xff0c;并没有发生异常崩溃 3、函数中发生了栈内存越界&#xff0c;导致线程的栈回溯出异常&#xff0c;堆栈中只显示一行函数调用记录 3.1、处理Json数据时产生了异常 3.2、函数中发生栈内存越界&#xff0…

LeetCode 240: 搜索二维矩阵 II - 算法详解(秒懂系列

文章目录LeetCode 240: 搜索二维矩阵 II - 算法详解题目描述Java解决方案算法思路核心理念为什么选择右上角&#xff1f;可视化演示过程示例1&#xff1a;查找 target 5示例2&#xff1a;查找 target 20 (不存在)算法分析时间复杂度空间复杂度算法优势关键要点扩展思考LeetCo…

洛谷 B4071 [GESP202412 五级] 武器强化

思考难度低&#xff0c;但是代码难度相对较高的题&#xff0c;故做个记录。首先&#xff0c;题目说了要花费最少的钱&#xff0c;所以我们每次拿最便宜的材料给武器1思想&#xff1a;每次都拿最便宜的材料然后考虑一下这个思想是否正确&#xff0c;找一下反例&#xff0c;每次拿…

SQL工具30年演进史:从Oracle到Navicat、DBeaver,再到Web原生SQLynx

目录 一、1990s&#xff1a;厂商自带的数据库工具时代 二、2000s&#xff1a;Navicat等商业数据库管理工具崛起 三、2010s&#xff1a;DBeaver等开源SQL工具兴起 四、2020s&#xff1a;SQLynx&#xff0c;Web原生数据库管理工具 五、SQL工具30年时间线对比 六、总结&…

C语言制作扫雷游戏(拓展版赋源码)

目录 引言&#xff1a; 三个新功能实现 1.可以选择难度或自定义 实现难点解析 代码实现&#xff08;附源码&#xff09; 扫雷.c game.h game.c 2.对选择位置进行标记或取消标记 一.框架 我们先理一下思路 如何构造框架 二.取消标记函数 三.标记函数 四.加入清屏&#xff0c;进…

Python快速入门专业版(十):字符串特殊操作:去除空格、判断类型与编码转换

目录引1.去除空格&#xff1a;清理字符串的实用技巧1.1 三类去空格方法&#xff1a;strip()、lstrip()、rstrip()1.2 实战案例&#xff1a;处理用户输入的空格问题2.判断类型&#xff1a;验证字符串内容的特性2.1 常用类型判断方法2.2 实战案例&#xff1a;验证用户输入的合法性…

Gamma AI:AI演示文稿制作工具,高效解决PPT框架搭建难与排版耗时问题

你做 PPT 的时候是不是也常陷入 “两难”&#xff1f;要么对着空白幻灯片发呆&#xff0c;不知道怎么搭框架 —— 比如要做 “产品季度迭代复盘”&#xff0c;既想放数据又想讲问题&#xff0c;结果页面堆得像乱炖&#xff1b;要么好不容易凑完内容&#xff0c;又花两小时调排版…

【应用案例】AI 给医用过滤器 “找茬”:3 大难点 + 全流程解决方案

【应用案例】AI 给医用过滤器 “找茬”&#xff1a;3 大难点 全流程解决方案&#x1f3af;医用过滤器进行医疗AI检测&#x1f3af;先看痛点&#xff1a;医用过滤器检测难在哪&#xff1f;&#x1f3af;AI检测方案&#xff1a;3步实现“零漏检”1. 硬件定制&#xff1a;让缺陷“…

【数据库相关】TxSQL新增数据库节点步骤

TxSQL新增数据库节点步骤准备工作与注意事项具体操作步骤第 1 步&#xff1a;在主库上创建复制专用账号第 2 步&#xff1a;对主库进行锁表并获取二进制日志坐标第 3 步&#xff1a;备份主库数据并传输到新从库第 4 步&#xff1a;主库解锁第 5 步&#xff1a;在新从库服务器上…

Jmeter快速安装配置全指南

1、JDK安装(Java Development Kit) 1.1.JDK下载 JDK下载址&#xff1a; Java Downloads | Oracle &#xff08;jdk-8u211-windows-x64.exe&#xff09; Android 基于 Java 语言开发&#xff0c;所以必须安装Java环境&#xff0c;Java 环境分JDK 和JRE &#xff0c;JDK提…

设计模式最佳实践 - 模板模式 + 责任链模式

废话不多说&#xff0c;直接切入正题&#xff0c;本篇要讲的是 模板模式 责任链模式 实践。该最佳实践本身就是一种对 责任链模式的增强&#xff0c;模板模式通过 父类 强耦合&#xff0c;预定义好 责任链 next 方法 的前后一些切面行为&#xff0c;优雅简洁。先上示例&#x…

Python快速入门专业版(十一):布尔值与None:Python中的“真假”与“空值”(附逻辑判断案例)

目录引言&#xff1a;为什么“真假”与“空值”是编程的核心逻辑1.布尔值&#xff08;bool&#xff09;&#xff1a;Python中的“真”与“假”1.1 布尔值的基础特性1.2 布尔运算&#xff1a;and、or、not的逻辑规则代码示例&#xff1a;基础布尔运算进阶特性&#xff1a;短路求…