Linux网络编程 | socket介绍、网络字节序与主机字节序概念与两者的转换、TCP/UDP 连接中常用的 socket 接口

文章目录

  • 套接字
  • socket 地址
    • 通用 socket 地址
    • 专用 socket 地址
  • 网络字节序与主机字节序
    • 地址转换
  • TCP/UDP 连接中常用的 socket 接口


套接字

什么是套接字?

所谓 套接字 (Socket) ,就是对网络中 不同主机 上的应用进程之间进行双向通信的端点的抽象。

UNIX/Linux下一切皆文件。 socket 就是可读、可写、可控、可关的文件描述符。socket 最开始的含义是一个 (IP地址,端口)(IP, port) 。唯一地表示了使用 TCP 通信的一端。

为什么要用到套接字?

我们知道,数据链路层、网络层、传输层协议是在内核中实现的。而 socket 就是 操作系统 提供给 应用程序 通过 系统调用 访问这些 协议服务 的一组 APIsocket 不但可以访问内核中 TCP/IP 协议栈,而且访问其他网络协议栈。

socket 定义的 API 提供哪些功能?

  1. 应用程序数据用户缓冲区 中复制到 TCP/UDP 内核发送缓冲区 ,将发送数据交付内核。
  2. TCP/UDP 内核接收缓冲区 中复制数据到 用户缓冲区 ,以读取数据。
  3. 帮助 应用程序 修改 内核中各层协议的某些头部信息 或 其他数据结构,从而精确地控制底层通信行为。(如:通过 setsockopt函数 来设置 IP 数据报 在网络上的存活时间)

socket 的主要 API 都定义在 sys/socket.h 头文件中。Linux 提供了一套定义在 netdb.h 头文件中的网络信息 API ,以实现 主机名IP地址 之间的转换,以及服务名称和端口号之间的转换。


socket 地址

存储 socket 的地址信息的数据结构有三种:sockaddrsockaddr_insockaddr_un 。我们将 sockaddr 称为 通用 socket 地址 ,将 后两者 称为 专用 socket 地址 。这样划分的意义在于:在使用时,我们可以选择自己所需要的结构,通信时再将我们所使用的结构强转为 sockaddr ,这样就能保证数据格式的一致。
在这里插入图片描述

通用 socket 地址

sockaddr 的定义如下:

#include<bits/socket.h>
struct sockaddr
{sa_family_t 	sa_family; // 地址族类型(sa_family_t)的变量。char 			sa_data[14]; // 存放 socket 地址值。
}

地址族类型通常与协议族类型对应:

协议族地址族描述地址值含义和长度
PF_UNIXAF_UNIXUNIX本地协议族文件的路径名,长度可达108字节
PF_INETAF_INETTCP/IPv4协议族16 bit 端口号和 32 bit IPv4 地址,共6字节
PF_INET6AF_INET6TCP/IPv6协议族16 bit 端口号,32 bit 流标识,128 bit IPv6 地址,32 bit 范围ID,共26字节

PF_*AF_* 都定义在 bits/socket.h 头文件中,且有完全相同的值,因此二者经常混用。

然而,通用的 sockaddr 对于各个协议族而言适用性并不好—— sa_data目标IP地址端口信息 混在一起了。因此,Linux 为各个协议族提供了专门的 socket 地址结构体。


拓展: 由上表易知,14 字节的 sa_data 无法容纳多数协议族的地址值。因此,Linux 定义了一个新的通用 socket 地址结构体:

#include<bits/socket.h>
struct sockaddr_storage
{sa_family_t 		safamily;unsigned long int   __ss_align;char 				__ss_padding[128-sizeof(__ss_ag=lign)];
}

这个结构体不仅提供了足够大的空间用于存放地址值,而且是内存对齐的(这是 __ss_align 成员的作用)。


专用 socket 地址

sockaddr_un 的定义如下:

UNIX 本地域协议族 使用 sockaddr_un 地址结构体:

#include<sys/un.h>
struct sockaddr_un
{sa_family_t sin_family; // 地址族:AF_UNIXchar sun_path[108]; // 文件路径名
}

sockaddr_in 的定义如下:

TCP/IP协议族IPv4 使用 sockaddr_in 地址结构体:

struct sockaddr_in
{sa_family_t     sin_family; /* 地址族:AF_INET */u_int16_t       sin_port;   /* 端口号,要用网络字节序表示 */struct in_addr  sin_addr;   /* IPv4地址结构体 */char            sin_zero;   /* 不使用 */
}struct in_addr
{u_int32_t 		s_addr; /* 32位 IPv4 地址,要用网络字节序表示 */
}

可以清楚看到,该结构体解决了 sockaddr 的缺陷,把 portaddr 分开储存在两个变量中。sockaddr_insockaddr 长度一样,都是 16 个字节,即 占用的内存大小是一致的 ,因此可以互相转化。二者是并列结构,指向 sockaddr_in 结构的指针也可以指向 sockaddr

sockaddrsockaddr_in 是 Linux网络编程中最常用的 socket 结构体,sockaddr_in 用于 socket 定义和赋值;sockaddr 用于函数参数。 一般先把 sockaddr_in 变量赋值后,强制类型转换后传入 参数为 sockaddr 的函数。

sockaddr_in6 的定义如下:

TCP/IP协议族IPv6 使用 sockaddr_in6 地址结构体:

struct sockaddr_in6
{sa_family_t      sin6_family;  	 /* 地址族:AF_INET6 */u_int16_t        sin6_port;   	 /* 端口号,要用网络字节序表示 */u_int32_t 		 sin6_flowinfo;  /* 流信息,应设置为0 */struct in6_addr  sin6_addr;   	 /* IPv6地址结构体 */u_int32_t        sin6_scope_id;	 /* scope ID,尚处于实验阶段 */
}struct in6_addr
{unsigned char	 sa_addr[16];    /* 128位 IPv6 地址,要用网络字节序表示 */
}

网络字节序与主机字节序

在使用网络协议的编程中,在两台使用不同存储模式(大端/小端)的主机之间传递数据时,往往会产生歧义。解决问题的方法是:

  • 发送端总是把要发送的数据转化成大端字节序数据后再发送
  • 接收端根据自己的字节序决定是否将传送过来的数据进行转换(自身模式为小端则转换,为大端则不转换)

因此将 大端字节序 称为 网络字节序小端字节序 称为 主机字节序

Linux 提供了如下 4 个函数来完成 主机字节序网络字节序 之间的转换:

#include<netinet/in.h>
unsigned long int htonl(unsigned long int hostlong);
unsigned short int htons(unsigned short int hostlong);
unsigned long int ntotl(unsigned long int netlong);
unsigned short int ntohs(unsigned short int netshort);/* h代表主机字节序,n代表网络字节序,l代表长整型,s代表短整型 */

上述函数中,长整型函数 通常用来转换 IP地址短整型函数 用来转换 端口号


地址转换

记录日志时,我们习惯用 可读性好的字符串 来表示 IP地址;编程时,我们往往更需要以 整数(二进制数) 形式表示 IP地址。而这种频繁切换的需求需要通过函数来满足:

#include <arpa/inet.h>
in_addr_t inet_addr(const char *strptr); 
// 将点分十进制的 字符串IP地址 转换为网络字节序的 整数IP地址 ,失败时返回INADDR_NONE
char *inet_ntoa(struct in_addr in);
// 将网络字节序的 整数IP地址 转换为点分十进制的 字符串IP地址,该函数内部用一个静态变量存储转化结果
// 函数的返回值指向该静态内存,因此 inet_ntoa 是不可重入的。
int inet_aton(const char *cp, struct in_addr *inp);
// 将点分十进制的 字符串IP地址 转换为网络字节序的 整数IP地址(与addr的区别它会认为如255.255.255.255这类特殊地址有效),成功返回1,失败返回0。
in_addr_t inet_network(const char *cp); 
// 将点分十进制的 字符串IP地址 转换为主机字节序的 整数IP地址
struct in_addr inet_makeaddr(in_addr_t net, in_addr_t host);
in_addr_t inet_lnaof(struct in_addr in);
in_addr_t inet_netof(struct in_addr in);/* 同时适用于 IPv4 和 IPv6: */
int inet_pton(int af, const char* src, void* dst);
// 将 字符串 表示的IP地址src(用点分十进制表示的IPv4地址或用十六进制字符表示的IPv6地址)转换成 网络字节序整数 表示的IP地址
// 转换结果存在dst指向的内存中。af指定地址族(AF_INET 或 AF_INET6),成功返回1、失败返回0并设置errno。
const char* inet_ntop(int af, const void* src, char* dst, socklen_t cnt);
// 将 网络字节序的整数IP地址 转换为 点分十进制的字符串IP地址,成功返回目标存储单元的地址、失败返回NULL并设置errno。
// cnt指定目标存储单元的大小,通过下面两个宏来指定大小:
#include<netinet/in.h>
#define INET_ADDRSTRLEN 16 // 用于 IPv4
#define INET6_ADDRSTRLEN 46 // 用于 IPv6 

不可重入的 inet_ntoa 函数

struct in_addr addr1,addr2;
ulong l1,l2;
l1 = inet_addr("1.1.1.1");
l2 = inet_addr("127.0.0.1");
memcpy(&addr1, &l1, 4);
memcpy(&addr2, &l2, 4);
char *cp1 = inet_ntoa(addr1);
char *cp2 = inet_ntoa(addr2);
cout << "address 1: " << cp1 << endl;
cout << "address 2: " << cp2 << endl;

输出结果:
在这里插入图片描述
我们会发现,由于 inet_ntoa 的返回值指向一个函数内部的静态内存,因此最后一次传入的参数会掩盖之前的参数。


TCP/UDP 连接中常用的 socket 接口

1. 创建 socket

#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain, int type, int protocol);
domain:底层协议族
type:服务类型
/* 服务类型主要有 */
// SOCK_STREAM服务(流服务),对于 TCP/IP 协议族而言,表示传输层使用 TCP 协议。
// SOCK_UGRAM服务(数据报),对于 TCP/IP 协议族而言,表示传输层使用 UDP 协议。
/* 拓展 */
// Linux内核版本2.6.17起,type可以是上述两种类型与下面两个标志的 与值
// SOCK_NONBLOCK 将新创建的 socket 设置为非阻塞的。
// SOCK_CLOEXEC 用 fork 调用创建子进程时在子进程中关闭该 socket
// 2.6.17版本前,文件描述符的这两个属性都需要使用额外的系统调用(如 fcntl)来设置。
protocol:通常是唯一的(前两个参数已经完全确定了它的值),大部分情况下被设为0,表使用默认协议。返回值:系统调用成功返回一个 socket 文件描述符,失败返回 -1 并设置 errno。

2. 命名/绑定 socket

创建 socket 时,我们给他指定了地址族,但是 并未指定使用该地址族中哪个具体地址 。 将一个 socket 与具体的地址绑定称为给 socket 命名。

  • 我们通常要给服务器命名 socket ,因为只有命名后客户端才知道如何连接服务器。
  • 客户端不需要命名 socket ,它通常使用操作系统自动分配的 socket 地址。
#include<sys/types.h>
#include<sys/socket.h>
int bind(int sockfd, const struct sockaddr* my_addr, sklen_t addrlen);
// 将 my_addr 所指的 socket 地址分配给未命名的 sockfd 文件描述符,addrlen 指出该地址的长度。
// 成功返回0、失败返回-1并设置errno。
// 其中两种常见errno:
// EACCES:被绑定的地址是受保护的地址,仅超级用户能够访问。如:普通用户将 socket 绑定到知名服务端口(端口号0~1023)。
// EADDRINUSE:被绑定的地址正在使用中。如:将 socket 绑定到一个处于 TIME_WAIT 状态的 socket 地址。

3. 监听 socket

socket 被命名之后,还不能立马接受客户连接,需要使用系统调用来创建一个 监听队列 以存放待处理的客户连接(同时有 全连接半连接 ):

#include<sys/socket.h>
int listen(int sockfd, int backlog);
// sockfd 指定被监听的 socket;backlog 提示内核监听队列的最大长度(常设为5),超过 backlog+1 则不受理新的客户连接,客户端也收到 ECONNREFUSED 错误信息。
// Linux内核2.2版本之前,backlog 指的是所有处于 半连接状态(SYN_RCVD)和 完全链接状态(ESTABISHED)的 socket 总上限。
// 2.2版本之后,它中表示处于 完全连接状态 的 socket 的上限。半连接状态 的 socket 上限值由 /proc/sys/ipv4/tcp_max_syn_backlog 内核参数定义。
//  成功返回0;失败返回-1并设置errno

4. 接受连接

accept 可以从 全连接队列 中接受一个客户连接,但 accept 只是从监听队列中取出连接,而不关心连接处于何种状态(ESTABLISHEDCLOSE_WAIT),更不关心任何网络状态的变化(取出的客户端可能掉线了)。

#include <sys/types,h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
// 将 addr 所指的 socket 地址分配给执行过 listen 系统调用的 socket 文件描述符,地址长度为 addrlen。
// 成功时返回一个新的 连接socket,唯一地标识被接受的这个连接,服务器可通过读写该 socket 与被接受连接对应的客户端通信;失败返回-1并设置errno。

5. 发起连接

服务器 通过 listen 系统调用来 被动 接受连接,客户端 通过 connect 系统调用来 主动 与服务器建立连接。

#include<sys/types.h>
#include<sys/socket.h>
int connetct(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
// sockfd:socket 系统调用返回的文件描述符。
// serv_addr:处于服务器监听队列的客户端 socket 地址
// 成功返回0,sockfd 唯一标识这个链接,客户端可以通过读写 sockfd 与服务器通信;失败返回-1并设置errno。 
其中两种常见的errno是:
- ECONNREFUSED:目标端口不存在,连接被拒绝。服务器发送给客户端一个复位报文段(seq=0),客户端不必回复复位报文段,应关闭连接或重新连接。
- ETIMEDOUT:连接超时。进行若干次重连,每次重连超时时间都增加一倍。

6.断开连接

关闭连接就是关闭连接对应的 socker 。 可以通过 close 系统调用来关闭文件描述符。

#include<unistd.h>
int close(int fd);
// fd:待关闭的socket

值得一提的是, close 并非立即关闭一个链接,而是将 fd 的引用计数减 1 。当 fd 的引用计数为 0 时,才真正关闭连接。多进程程序中,一次 fork 系统调用默认将使父进程中打开的 socket 的引用计数加 1 ,因此必须在 父子进程中都 对该 socket 执行 close 调用才能关闭连接。

如果无论如何都要立刻终止连接,可以使用 shutdown 系统调用:

#include<sys/socket.h> // 从隶属头文件可以看出,它是专门为网络编程设计的
int shutdown(int sockfd, int howto);
// sockfd:待关闭的 socket。
// howto:shutdown 的行为。
// 成功返回0,失败返回-1并设置errno。

howto 可选值有:

可选值含义
SHUT_RD关闭 sockfd 上读的这一半。应用程序不能再执行读操作,并且该 socket 接收缓冲区中的数据都被丢弃。
SHUT_WR关闭 sockfd 上写的这一半。sockfd 的发送缓冲区中的数据会在真正关闭连接之前全部发送出去,应用程序不可再执行写操作。这种情况下,连接处于半关闭状态。
SHUT_RDWR同时关闭 sockfd 上的读和写

closeshutdown 最大的不同是,close 关闭连接时只能将 socket 上的读和写同时关闭,而 shutdown 可以分别(或同时)关闭。


7. 数据读写

对文件的读写 writeread 同样适用于 socke ,但 socket 还有专门用于 socket数据读写 的系统调用:

/* 用于TCP流数据读写的系统调用 */
#include<sys/types>
#include<sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
// recv 读 sockfd 上的数据,send 往 sockfd 上写数据。
// buf 和 len 分别指定缓冲区的位置和大小。
// flags 为数据收发提供额外控制,通常为0。
// 成功时返回读/写的数据长度,失败返回-1并设置errno。
// recv 成功时返回的长度可能小于 len,因此可能要多次调用 recv 以读取完整数据;recv 返回 0 意味着 通信对方已经关闭连接。/* UDP数据读写 */
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
// buf 和 len 参数分别指定读/写缓冲区的位置和大小
// 由于 UDP 通信没有连接的概念,所以我们每次读取数据都需要获取发送端的 socket 地址,即 src_addr 所指向的内容。
// dest_addr 指定接收端的 socket 地址。
/* 上述两个系统调用也可以用于面向连接(STREAM)的 socket 的数据读写,只需将最后两个参数都设置为 NULL *//* 兼容 TCP流数据 和 UDP数据报 的数据读写 */
#include<sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);struct msghdr
{void* msg_name;				// socket 地址,对于TCP协议,需设置为NULL。socklen_t msg_namelen;		// socket 地址的长度struct iovec* msg_iov;		// 分散内存块,详情见下文。int msg_iovlen;				// 分散内存块的数量void* msg_control;			// 指向辅助数据的起始位置socklen_t msg_controllen;	// 辅助数据的大小int msg_flags;				// 复制函数中的 flags 参数,recvmsg 还会在调用过程中将某些更新后的标志设置到 msg_flags 中。
}struct iovec
{void *iov_base; // 内存起始地址size_t iov_len; // 这块内存长度
}
对于 recvmsg 而言,数据将被读取并存放在 msg_iovlen 块分散的内存中,这种操作被称为 分散读(scatter read)。
对于 sendmsg 而言,分散内存中的数据将被一并发送,这称为 集中写(gather write)。

flags 可选值:

选项名含义sendrecv
MSG_CONFIRM指示数据链路层持续监听对方的回应,直到得到答复。仅能用于 SOCK_DGRAM 和 SOCK_RAW 类型的 socket。YN
MSG_DONTROUTE不查看路由表,直接将数据发送给本地局域网络内的主机。用于发送者确定目标主机就在本网络。YN
MSG_DONTWAIT对 socket 的此次操作将是非阻塞的YY
MSG_MORE告诉内核应用程序还有更多数据要发送,内核将 超时等待 新数据写入 TCP 发送缓冲区后一并发送。防止 TCP 发送过多小报文段,提高传输效率。YN
MSG_WAITALL读操作仅在读取到指定数量的字节后才返回NY
MSG_PEEK窥探读缓存中的数据,本次读操作不会清除这些数据。NY
MSG_OOB发送或接收紧急数据(带外数据)YY
MSG_NOSIGNAL往读端关闭的管道或 socket 连接中读写数据时不引发 SIGPIPE 信号YN

flags 参数只对 sendrecv 的当前调用生效,而后面讲到的 setsockopt 系统调用会永久地修改 socket 某些属性。


7.1 带外数据

实际应用中,通常无法预知 带外数据 何时到来,幸运的是 Linux 内核检测到 TCP 紧急标志(URG)时,将通知应用程序有带外数据需要接受。通知方法有两种:

  • I/O复用 产生的异常事件
  • SIGURG 信号

但应用程序接到通知后也只知道有带外数据要来,解决的时间的不确定性,但仍不知道带外数据在数据流中的具体位置。想要知道具体位置可以通过如下系统调用:

#include<sys/socket.h>
int sockatmark(int fockfd);

sockatmark 判断 sockfd 是否处于带外标记,即下一个被读取到的数据是否是带外数据。若是返回 1 ,此时就可以用带 MSG_OBB 标志的 recv 调用来接收带外数据;若不是返回 0


8. 地址信息函数

我们可以知道一个连接 socket 的 本端socket地址 ,以及 远端socket地址

#include<sys/socket.h>
int getsockname(int sockfd, struct sockaddr* address, socklen_t* address_len); // 获取 sockfd 对应的socket地址
int getpeername(int sockfd, struct sockaddr* address, socklen_t* address_len); // 远端socket地址
// 将获得的地址存于 address 指定的内存中,地址长度存于 address_len 中。
// 如果实际 socket 地址长度大于 address 所指内存区的大小,那么该 socket地址 将被截断。
// 成功返回0,失败返回-1并设置errno。

9. 总结

用一张图总结 TCP三次握手 过程中对 socket接口 的使用。
在这里插入图片描述

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

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

相关文章

网络协议分析 | 传输层 :史上最全UDP、TCP协议详解,一篇通~

文章目录UDP概念格式UDP如何实现可靠传输基于UDP的应用层知名协议TCP概念格式保证TCP可靠性的八种机制确认应答、延时应答与捎带应答超时重传滑动窗口滑动窗口协议后退n协议选择重传协议流量控制拥塞控制发送窗口、接收窗口、拥塞窗口快速重传和快速恢复连接管理机制三次握手连…

JDom,jdom解析xml文件

1.要解析的文件模板如下&#xff1a; <?xml version"1.0" encoding"GBK"?> <crsc> <data><举报信息反馈><R index"1"><举报编号>1</举报编号><状态>1</状态><答复意见>填写…

网络协议分析 | 应用层:HTTP协议详解、HTTP代理服务器

文章目录概念URLHTTP协议的特点HTTP协议版本格式请求报文首行头部空行正文响应报文首行头部空行正文Cookie与SessionHTTP代理服务器正向代理服务器反向代理服务器透明代理服务器概念 先了解一下 因特网&#xff08;Internet&#xff09; 与 万维网&#xff08;World Wide Web&…

MySQL命令(一)| 数据类型、常用命令一览、库的操作、表的操作

文章目录数据类型数值类型字符串类型日期/时间类型常用命令一览库的操作显示当前数据库创建数据库使用数据库删除数据库表的操作创建表显示当前库中所有表查看表结构删除表数据类型 mysql 的数据类型主要分为 数值类型、日期/时间类型、字符串类型 三种。 数值类型 数值类型可…

C++ 继承 | 对象切割、菱形继承、虚继承、对象组合

文章目录继承继承的概念继承方式及权限using改变成员的访问权限基类与派生类的赋值转换回避虚函数机制派生类的默认成员函数友元与静态成员多继承菱形继承虚继承组合继承 继承的概念 继承可以使得子类具有父类的属性和方法或者重新定义、追加属性和方法等。 当创建一个类时&…

博弈论 | 博弈论简谈、常见的博弈定律、巴什博弈

文章目录博弈论什么是博弈论&#xff1f;博弈的前提博弈的要素博弈的分类非合作博弈——有限两人博弈囚徒困境合作博弈——无限多人博弈囚徒困境常见的博弈定律零和博弈重复博弈智猪博弈斗鸡博弈猎鹿博弈蜈蚣博弈酒吧博弈枪手博弈警匪博弈海盗分金巴什博弈博弈论 什么是博弈论…

MySQL命令(二)| 表的增删查改、聚合函数(复合函数)、联合查询

文章目录新增 (Create)全列插入指定列插入查询 (Retrieve)全列查询指定列查询条件查询关系元素运算符模糊查询分页查询去重&#xff1a;DISTINCT别名&#xff1a;AS升序 or 降序更新 (Update)删除 (Delete)分组&#xff08;GROUP BY&#xff09;联合查询内连接&#xff08;inne…

MySQL | 数据库的六种约束、表的关系、三大范式

文章目录数据库约束NOT NULL&#xff08;非空约束&#xff09;UNIQUE&#xff08;唯一约束&#xff09;DEFAULT&#xff08;缺省约束&#xff09;PRIMARY KEY&#xff08;主键约束&#xff09;AUTO_INCREMENT 自增FOREIGN KEY&#xff08;外键约束&#xff09;CHECK&#xff08…

哈希 :哈希冲突、负载因子、哈希函数、哈希表、哈希桶

文章目录哈希哈希&#xff08;散列&#xff09;函数常见的哈希函数字符串哈希函数哈希冲突闭散列&#xff08;开放地址法&#xff09;开散列&#xff08;链地址法/拉链法&#xff09;负载因子以及增容对于闭散列对于开散列结构具体实现哈希表&#xff08;闭散列&#xff09;创建…

C++ 泛型编程(一):模板基础:函数模板、类模板、模板推演成函数的机制、模板实例化、模板匹配规则

文章目录泛型编程函数模板函数模板实例化隐式实例化显式实例化函数模板的匹配规则类模板类模板的实例化泛型编程 泛型编程旨在削减重复工作&#xff0c;如&#xff1a; 将一个函数多次重载不如将他写成泛型。 void Swap(int& left, int& right) {int temp left;lef…

你真的了解静态变量、常量的存储位置吗?

文章目录引言C对内存的划分如何落实在Linux上自由存储区和堆之间的问题栈常量区静态存储区静态局部变量静态局部变量、静态全局变量、全局变量的异同macOS系统的测试结果总结引言 在动态内存的博客中&#xff0c;我提到&#xff1a; 在Linux 内存管理的博客中&#xff0c;我提…

C++ 泛型编程(二):非类型模板参数,模板特化,模板的分离编译

文章目录非类型模板参数函数模板的特化类模板的特化全特化偏特化部分参数特化参数修饰特化模板分离编译解决方法非类型模板参数 模板的参数分为两种&#xff1a; 类型参数&#xff1a; 则是我们通常使用的方式&#xff0c;就是在模板的参数列表中在 class 后面加上参数的类型…

数据结构 | B树、B+树、B*树

文章目录搜索结构B树B树的插入B树的遍历B树的性能B树B树的插入B树的遍历B*树B*树的插入总结搜索结构 如果我们有大量的数据需要永久存储&#xff0c;就需要存储到硬盘之中。但是硬盘的访问速度远远小于内存&#xff0c;并且由于数据量过大&#xff0c;无法一次性加载到内存中。…

MySQL 索引 :哈希索引、B+树索引、全文索引

文章目录索引引言常见的索引哈希索引自适应哈希索引B树索引聚集索引非聚集索引使用方法联合索引最左前缀匹配规则覆盖索引全文索引使用方法索引 引言 为什么需要索引&#xff1f; 倘若不使用索引&#xff0c;查找数据时&#xff0c;MySQL必须遍历整个表。而表越大&#xff0c;…

服装店怎么引流和吸引顾客 服装店铺收银系统来配合

实体店的同城引流和经营是实体经济的一个重要的一环&#xff0c;今天我们来分享服装行业的实体店铺怎么引流和吸引、留住顾客&#xff0c;并实现复购。大家点个收藏&#xff0c;不然划走就再也找不到了&#xff0c;另外可以点个关注&#xff0c;下次有新的更好的招&#xff0c;…

MySQL 锁的相关知识 | lock与latch、锁的类型、简谈MVCC、锁算法、死锁、锁升级

文章目录lock与latch锁的类型MVCC一致性非锁定读&#xff08;快照读&#xff09;一致性锁定读&#xff08;当前读&#xff09;锁算法死锁锁升级lock与latch 在了解数据库锁之前&#xff0c;首先就要区分开 lock 和 latch。在数据库中&#xff0c;lock 和 latch 虽然都是锁&…

MySQL 存储引擎 | MyISAM 与 InnoDB

文章目录概念innodb引擎的4大特性索引结构InnoDBMyISAM区别表级锁和行级锁概念 MyISAM 是 MySQL 的默认数据库引擎&#xff08;5.5版之前&#xff09;&#xff0c;但因为不支持事务处理而被 InnoDB 替代。 然而事物都是有两面性的&#xff0c;InnoDB 支持事务处理也会带来一些…

MySQL 事务 | ACID、四种隔离级别、并发带来的隔离问题、事务的使用与实现

文章目录事务ACID并发带来的隔离问题幻读&#xff08;虚读&#xff09;不可重复读脏读丢失更新隔离级别Read Uncommitted (读未提交)Read Committed (读已提交)Repeatable Read (可重复读)Serializable (可串行化)事务的使用事务的实现Redoundo事务 事务指逻辑上的一组操作。 …

MySQL 备份与主从复制

文章目录备份主从复制主从复制的作用备份 根据备份方法的不同&#xff0c;备份可划分为以下几种类型&#xff1a; 热备(Hot Backup) &#xff1a; 热备指的是在数据库运行的时候直接备份&#xff0c;并且对正在运行的数据库毫无影响&#xff0c;这种方法在 MySQL 官方手册中又…

C++ 流的操作 | 初识IO类、文件流、string流的使用

文章目录前言IO头文件iostreamfstreamsstream流的使用不能拷贝或对 IO对象 赋值条件状态与 iostate 类型输出缓冲区文件流fstream类型文件模式文件光标函数tellg() / tellp()seekg() / seekp()向文件存储内容/读取文件内容string流istringstreamostringstream前言 我们在使用 …