【Linux系统化学习】网络套接字(编写简单的UDP服务端和客户端)

目录

理解源IP地址和目的IP地址

认识端口号

端口号和进程ID的区别

源端口号和目的端口号

认识TCP和UDP协议

TCP协议

UDP协议

网络字节序

socket编程接口

socket常见API

sockaddr结构

简单的UDP网络程序

UDP服务端

创建套接字

填充本地网络信息

绑定

收取消息

完整的服务端封装代码

UDP客户端

创建套接字

填充服务端信息

操作系统自动绑定

发送消息

 客户端完整代码

一些补充

本地回环测试

netstat指令


理解源IP地址和目的IP地址

  1. 源IP地址(Source IP Address):源IP地址是指发送数据包的设备或主机的IP地址。它是数据包的来源地址,标识了数据包从哪个设备发送出来。当你发送数据到网络上的其他设备时,你的设备会将数据包标记上源IP地址,以便接收设备知道数据来自哪里。

  2. 目的IP地址(Destination IP Address):目的IP地址是指接收数据包的设备或主机的IP地址。它是数据包的目标地址,标识了数据包应该被发送到哪个设备。当你发送数据到网络上的另一个设备时,你的设备会将数据包标记上目的IP地址,以便网络路由器和接收设备知道将数据包传送到哪里。


认识端口号

  • 端口号(port)是传输层协议的内容.
  • 端口号是一个2字节16位的整数;
  • 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
  • IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
  • 一个端口号只能被一个进程占用.

注:上篇文章我们说了两台主机可以通过网络进行通信,在准确一点就是两台主机中的进程通过网络进行通信,就像我们可以在QQ给自己的基友发消息,通过网络你的基友机会在自己主机的QQ上收到你的消息;两台主机上的两个QQ进程就通过网络这同一份资源进行远距离通信。

今天我们是通过网络,对于双方而言:

  1. 数据首先要到达目标主机(IP)
  2. 找到指定的进程
  3. IP地址的是用来表示互联网中唯一的主机,端口号用来标识该指定的机器中进程的唯一性;因此IP加端口号可以用来表示互联网中唯一的一个进程。IP加端口就是套接字(socket)

端口号和进程ID的区别

端口号和进程ID是两个不同层次、不同领域的标识符。端口号用于网络通信中标识不同的应用程序或服务,而进程ID用于操作系统中标识不同的进程。在某些情况下,可以将端口号和进程ID关联起来,例如查看特定端口上运行的进程,但它们仍然是不同的概念。

注:一个进程可以有多个端口号,但是一个端口号只能代表一个进程

源端口号和目的端口号

  1. 源端口号(Source Port Number):源端口号是发送数据包的设备或主机上的应用程序或服务使用的端口号。在建立连接时,发送端的应用程序会随机选择一个空闲端口作为源端口号,并将其包含在发送的数据包中。源端口号帮助目标设备知道从哪个端口接收到了数据包,以便回复响应。

  2. 目的端口号(Destination Port Number):目的端口号是接收数据包的设备或主机上的应用程序或服务期望接收数据包的端口号。在发送数据包时,发送端会指定目标设备的IP地址和目标端口号。接收设备根据目的端口号确定将数据包传递给哪个应用程序或服务。

其实我们可以简单理解为:就是在描述 "数据是谁发的, 要发给谁"


认识TCP和UDP协议

我们先对TCP(Transmission Control Protocol 传输控制协议)、UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识; 后面的文章我们再详细讨论TCP、UDP的一些细节问题.

TCP协议

  • 传输层协议
  • 有连接
  • 可靠传输
  • 面向字节流

UDP协议

  • 传输层协议
  • 无连接
  • 不可靠传输
  • 面向数据报 

网络字节序

我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?

  • 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
  • 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
  • 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
  • TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
  • 不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
  • 如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;

为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。 

  • 这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。
  • 例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
  • 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
  • 如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。 

注:简单来说就是通过网络必须传送的是大端序,因此在传送之前不清楚自己主机是大端还是小端必须进行转换为大端序 


socket编程接口

socket常见API

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

网络编程的时候,socket是有很多分类的:

  • unix socket(域间套接字) :同一台机器上的文件路径,类似于命名管道,用于本主机内部进行通信
  • 网络socket:ip+port用于网络通信
  • 原始socket:跳过运输层,直接访问网络层;用于编写一些网络工具

sockaddr结构

网络编程的时候,有不同的应用场景,理论上而言我们应该给每一种场景都设计一套编程接口,但是设计者想使用一套接口,因此sockaddr是一个通用的接口;

sockaddr就相当与基类,当传入不同的类型是判断每个结构体的前两个字节进行类型的强转换。

IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16位端口号和32位IP地址.

IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容.
socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数; 

简单的UDP网络程序

UDP服务端

初始化服务器

创建套接字

int socket(int domain, int type, int protocol);

返回值

  • socket 函数的返回值代表了新创建的套接字的文件描述符,如果创建失败则返回 -1。
  • 在成功创建套接字后,socket 函数会返回一个非负整数,表示新创建的套接字的文件描述符。

参数

domain(地址族)

  • 类型:int
  • 意义:指定套接字的地址族,即通信所采用的协议族。
  • 常见取值:
    • AF_INET:IPv4 地址族。
    • AF_INET6:IPv6 地址族。
    • AF_UNIXAF_LOCAL:Unix 域(本地)套接字。

上面提到有三种套接字的分类,我们是网络套接字并且只介绍IPv4因此选择第一个取值

type(套接字类型)

  • 类型:int
  • 意义:指定套接字的类型,即套接字的通信模式。
  • 常见取值:
    • SOCK_STREAM:流套接字,提供可靠的、面向连接的、基于字节流的服务,使用 TCP 协议。
    • SOCK_DGRAM:数据报套接字,提供不可靠的、无连接的、基于数据报的服务,使用 UDP 协议。
    • SOCK_RAW:原始套接字,允许对底层协议进行直接访问,常用于网络监控和特殊应用。

 编写的是UDP服务器,因此选择第二个参数

protocol(协议)

  • 类型:int
  • 意义:指定套接字所使用的协议,通常为 0,表示使用默认的协议。
  • 对于 SOCK_STREAMSOCK_DGRAM 类型的套接字,协议通常可以省略,因为它们分别与 TCP 和 UDP 相关联。
  • 对于 SOCK_RAW 类型的套接字,可以指定底层的协议,如 IP、ICMP 等。

一般为0; 

填充本地网络信息

因为我们是网络套接字,因此需要一个struct sockaddr_in的结构体;将我们的IP地址、端口号和套接字类型填充到结构体中;

注:

  • 创建好结构体后需要对结构体里的内容清零
  • 填充端口号时我们要将我们的主机序列转为网络序列
  • 填充IP地址时我我们要将原始的点分十进制的字符串转换为四字节的网络序列
  • 我们不可以使用我们的服务器的IP地址作为我们程序的IP地址,下一步绑死后只能收到该IP发送的报文。
  • 我更推荐使用任意IP地址,INADDR_ANY 是一个特殊的 IP 地址,在网络编程中经常用于绑定套接字到本地计算机的所有网络接口上。
  • 具体来说,INADDR_ANY 表示接受任何来自本地计算机所有网络接口(包括所有网卡)的数据包。这在服务器编程中非常有用,因为服务器通常需要监听来自所有网络接口的连接请求。

绑定

上篇文章我们提到传输层作用于内核中,我们上一步填充的各种信息只存在于栈中,因此我们需要告诉操作系统内核,某个特定的套接字对应于网络上的某个地址。这个过程就是绑定;

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

返回值

bind 函数的返回值代表了函数执行的结果,它通常有以下两种可能:

  1. 如果绑定成功,bind 函数返回值为 0。这表示套接字已成功绑定到指定的地址和端口上。

  2. 如果绑定失败,bind 函数返回值为 -1,并且会设置相应的错误码以指示失败的原因。这种情况可能

参数 

套接字描述符

  • 类型:int(整数)
  • 意义:要绑定的套接字的文件描述符。
  • 在调用绑定函数时,需要传递已经创建好的套接字的文件描述符。

地址结构指针

  • 类型:const struct sockaddr *
  • 意义:指向存储目标地址信息的结构体的指针。
  • 绑定函数需要知道要绑定的目标地址和端口号。通常使用的是 sockaddr 结构体或其派生结构体,如 sockaddr_in(IPv4 地址)或 sockaddr_in6(IPv6 地址)等。
  • 使用 const 关键字修饰指针,表示绑定函数不会修改该地址结构。

地址结构的长度

  • 类型:socklen_t(整数)
  • 意义:地址结构的长度。
  • 用于指定地址结构的实际长度,以确保绑定函数能够正确地解析地址结构。

启动服务器

收取消息

启动服务器就是启动一个程序且程序没收到特定的指令不退出,即就是一个死循环。

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);

 recvfrom 函数用于从指定的套接字接收数据,并将数据存储到指定的缓冲区中,同时还可以获取数据发送方的地址信息。这个函数通常在使用 UDP 协议进行通信时使用,因为 UDP 是面向数据报的,每个数据包都有自己的源地址和目标地址。

返回值

recvfrom 函数的返回值是接收到的数据的字节数,即实际读取到缓冲区中的数据量。如果发生错误,返回值为 -1。

参数 

套接字描述符

  • 类型:int(整数)
  • 意义:要接收数据的套接字的文件描述符。
  • 在调用 recvfrom 函数时,需要传递已经创建好的套接字的文件描述符。

缓冲区指针

  • 类型:void *
  • 意义:指向存储接收数据的缓冲区的指针。
  • 接收到的数据将被存储到这个缓冲区中。

缓冲区大小

  • 类型:size_t(无符号整数)
  • 意义:缓冲区的大小,即接收数据的最大长度。
  • 在调用 recvfrom 函数之前,应该确保缓冲区足够大,以容纳接收到的数据。

标志

  • 类型:int(整数)
  • 意义:用于指定接收数据的选项。
  • 可以设置为 0,表示没有特殊的选项。

发送方地址结构指针

  • 类型:struct sockaddr *
  • 意义:用于存储发送方的地址信息的结构体指针。
  • 如果不需要获取发送方的地址信息,可以将这个参数设置为 NULL

发送方地址结构的长度指针

  • 类型:socklen_t *
  • 意义:发送方地址结构的长度。
  • 在调用 recvfrom 函数之前,需要将这个参数设置为一个指向长度变量的指针,用于接收实际的地址结构长度。

完整的服务端封装代码

Udpserver.hpp

#pragma once 
#include<iostream>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<cerrno>
#include<unistd.h>
#include<cstring>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<strings.h>
using namespace std;
static const uint16_t defaultport = 8888;
static const int sockfd=-1;
static const int size=1024;
class UdpServer
{
public:UdpServer(uint16_t port=defaultport):_port(port),_sockfd(sockfd){}//初始化服务器void Init(){//创建套接字_sockfd = socket(AF_INET,SOCK_DGRAM,0);if(_sockfd<0){//表示创建失败cout<<"Fatal Error"<<errno<<strerror(errno)<<endl;exit(2);}else{cout<<"socket success "<<"sockfd : "<<_sockfd<<endl;}//绑定套接字struct sockaddr_in local;//初始化local //指定的一块内存清零bzero(&local,sizeof(local));//填充//进行网络通信local.sin_family = AF_INET;//端口号//转为网络序列local.sin_port = htons(_port);//1.4字节点分十进制字符串 2. 转为网络序列//local.sin_addr.s_addr = inet_addr(_ip.c_str());//使用地址任意//实现IP动态绑定local.sin_addr.s_addr = INADDR_ANY;//到此还没有设置到内核中,只存在于栈//绑定//强制类型转换int n = ::bind(_sockfd,(struct sockaddr*)&local,sizeof(local));if(n!=0){cout<<"Fatal Error , bind error "<<errno<<" "<<strerror(errno)<<endl;exit(3);}}//启动服务器//服务器永远不退出//是一个死循环void Start(){//收发消息//返回值实际收到的消息//第一个参数为文件描述符//第二个//第三个为期望//第四个参数为收数据的模式通常设置为0 阻塞式//最后两个参数为输出型参数//保存我们客户端的信息 ip portchar buffer[size];while(1){//预留一个\0struct sockaddr_in peer;socklen_t len = sizeof(peer);ssize_t n = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);if(n>0){//拿到客户端的信息//网络序列转主机序列uint16_t clientport = ntohs(peer.sin_port);string clientip = inet_ntoa(peer.sin_addr);string info = clientip;info+=":";info+=std::to_string(clientport);buffer[n]=0;cout<<"["<<info<<"]"<<buffer<<endl;//返回消息sendto(_sockfd,buffer,n,0,(struct sockaddr*)&peer,len);}}}~UdpServer(){if(_sockfd!=-1){close(_sockfd);}}
private:uint16_t _port;//端口号int _sockfd;
};

 Main.cc

#include<iostream>
#include<memory>
#include<string>
#include"Udpserver.hpp"
using namespace std;
enum comm
{Usage_Err=1
};
void Usage(string proc)
{cout<<"usage: \n\t"<<proc<<"locak_port"<<endl;
}
int main(int argc , char * argv[])
{//告诉如何使用if(argc!=2){Usage(argv[0]);return Usage_Err;}// string ip = argv[1];uint16_t port= stoi(argv[1]);UdpServer* usvr  = new UdpServer(port);usvr->Init();usvr->Start();delete usvr;return 0;
}

UDP客户端

作为客户端我们一定知道服务端的IP和端口号,作为服务端IP和端口号是不可能随便改变的;

创建套接字

和服务端类似

填充服务端信息

和服务端类似

操作系统自动绑定

客户端不需要显示使用bind函数进行绑定,而是操作系统自动隐式绑定;因为如果客户端能够绑定特定的端口号,那么可能会导致端口冲突的问题。假设多个客户端应用都试图绑定到相同的固定端口号上,那么在同一台计算机上运行这些应用时就会发生冲突。为了避免这种情况,操作系统会自动为客户端分配一个可用的临时端口。

发送消息

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

返回值

  • 如果成功,sendto 函数返回发送的字节数。这通常与 len 参数相同,但也可能更少(例如,如果套接字是非阻塞的,并且没有足够的缓冲区空间来容纳整个消息)。
  • 如果发生错误,sendto 函数返回 -1,并设置全局变量 errno 以指示错误类型。可能的错误包括 ECONNREFUSED(连接被拒绝)、EHOSTUNREACH(主机不可达)、EMSGSIZE(消息太大)、ENOBUFS(没有可用的缓冲区空间)等。

参数

sockfd这是一个打开的 socket 文件描述符,用于标识一个打开的 socket。

buf这是一个指向要发送数据的缓冲区的指针。

len这是要发送的数据的字节数。

flags这是可选的标志参数,可以用来指定不同的操作行为。常见的标志包括:

  • MSG_CONFIRM:告知底层传输层协议确认数据。
  • MSG_DONTROUTE:告知底层传输层协议不要路由数据。
  • MSG_EOR:表示发送的数据是一个消息的末尾。
  • 等等。具体的标志因操作系统和网络协议栈的不同而有所不同。

dest_addr这是一个指向目标地址信息的结构体的指针。它是一个 sockaddr 结构体,可以是 sockaddr_insockaddr_in6 结构体,具体取决于使用的网络协议版本。

addrlen这是目标地址结构体的大小,以字节为单位。通常情况下,它可以通过 sizeof(struct sockaddr) 来获取。

 客户端完整代码

Client.cc

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <cerrno>
#include <string.h>
#include <string>
#include <unistd.h>
#include<arpa/inet.h>
#include<netinet/in.h>using namespace std;
void Usage(const string &process) 
{cout<<"Usage : "<<process<<" server_ip server_port"<<endl;
}
int main(int argc, char*argv[])
{if (argc != 3){Usage(argv[0]);return 1;}string serverip = argv[1];uint16_t serverport = stoi(argv[2]);int sock = socket(AF_INET, SOCK_DGRAM, 0);if (sock < 0){cout << "socket error : " << strerror(errno) << endl;return 1;}cout << "socket success!" << endl;// 作为客户端必须知道服务器的ip和端口号// 作为客户端一定要进行绑定,但是不是显示绑定,客户端会在首次发送数据的时候进行自动绑定// 服务端端口号,一定是总所周知的不可改变的,// 客户端需要prot,绑定随机端口// why?// 因为客户端非常的多// 绑定确定的端口号,可能会在某一时刻启动失败// 让本地操作系统自动随机绑定,随机选择端口struct  sockaddr_in server;memset(&server,0,sizeof(server));server.sin_family =AF_INET;server.sin_port = htons(serverport);server.sin_addr.s_addr = inet_addr(serverip.c_str());// 发消息while (1){//我们要发的数据string inbuffer;cout << "plase Entre#: ";std::getline(std::cin, inbuffer);//发给谁ssize_t n = sendto(sock,inbuffer.c_str(),inbuffer.size(),0,(struct sockaddr*)&server,sizeof(server));if(n>0) {//收消息//UDP支持全双工通信char buffer[1024];struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t m = recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&len);if(m>0){buffer[m]='\0';cout<<"server echo# "<<buffer<<endl;}else{break;}}else {break;}}//套接字类型为文件描述符//使用结束后需要关闭close(sock);return 0;
}

一些补充

本地回环测试

我们可以让封装的服务端带上IP地址,使用127.0.0.1这个IP地址在本地的一台服务器上开上两个窗口启动服务端和客户端进行本地回环测试,检查代码和程序;上面的代码可以做到一些简单的交互联动。

netstat指令

netstat 命令,你可以查看本地计算机上的网络连接、路由表、网络接口统计信息等

常用选项

  • -a:显示所有连接和监听端口。
  • -t:显示 TCP 协议的连接。
  • -u:显示 UDP 协议的连接。
  • -l:显示监听状态的连接。
  • -n:以数字形式显示地址和端口。
  • -p:显示与连接相关的进程 ID。
  • -r:显示路由表。
  • -i:显示网络接口的统计信息。
  • -s:显示网络协议的统计信息。

今天对网络套接字的分享到这就结束了,希望大家读完后有很大的收获,也可以在评论区点评文章中的内容和分享自己的看法;个人主页还有很多精彩的内容。您三连的支持就是我前进的动力,感谢大家的支持!!! 

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

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

相关文章

Amine-PEG-Amine,956496-54-1在生物成像、生物传感器等领域具有广泛的应用

【试剂详情】 英文名称 Amine-PEG-Amine&#xff0c;NH2-PEG-NH2 中文名称 氨基-聚乙二醇-氨基&#xff0c;氨基PEG氨基&#xff0c; 双端氨基聚乙二醇 CAS号 956496-54-1 外观性状 由分子量决定&#xff0c;液体或者固体 分子量 0.4k&#xff0c;0.6k&#xff0c;1k&…

千古一帝秦始皇的一生

在中国历史上&#xff0c;秦始皇绝对是最有资格被称之为‘千古一帝’的皇帝。 这不光是因为&#xff0c;他是中国的第一个皇帝&#xff0c;更是因为他奠定了中国两千多年的政治格局&#xff0c;让中国从此有了大一统的意识。 1、赵国生涯 公元前259年&#xff0c;秦始皇嬴政…

利用PS中Lab颜色模式进行简单调色?

【原图】 详细步骤如下&#xff1a; Step 1 : 打开PS&#xff0c;打开素材&#xff0c;点菜单栏&#xff0c;【图像】-【模式】-【Lab颜色】&#xff0c;效果如下图 Step2&#xff1a;ctrl(或command)m打开曲线工具&#xff0c;选择a通道&#xff0c;效果如下图。 Step3: 把标…

SpringBoot---------Swagger

第一步&#xff1a;引入依赖 <!-- swagger--><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId></dependency> 第二步&#xff1a;配置文件 import com.sky.intercept…

【刷题】代码随想录算法训练营第二十九天|491、递增子序列,46、全排列,47、全排列II

目录 491、递增子序列46、全排列47、全排列II 491、递增子序列 讲解&#xff1a;https://programmercarl.com/0491.%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97.html class Solution { private:vector<vector<int>> result;vector<int> path;void backt…

pandas读取文件导致jupyter内核崩溃如何解决

读取execl文件出现以下问题: str_name "D:\\cao_use\\2017_2021(new).xlsx" train_df pd.read_excel(str_name, usecols[0])崩溃的指示图如下所示: bug原因:读入的文件太大&#xff0c;所需时间过长&#xff0c;在读取的过程中&#xff0c;使用中断按钮暂停会直…

OpenAI神秘模型,再次被Sam Altman提及

5月6日&#xff0c;OpenAI首席执行官Sam Altman在社交平台分享了一条推文“我是一个优秀的GPT-2聊天机器人”。 而在4月30日&#xff0c;Altman就提起过该模型非常喜欢GPT-2。按道理说一个只有15亿参数在2019年发布的开源模型&#xff0c;被反复提及两次就很不寻常。 更意外的…

volatile原理

文章目录 如何保证可见性如何保证有序性double-checked locking 问题double-checked locking 解决 volatile 的底层实现原理是内存屏障&#xff0c;Memory Barrier&#xff08;Memory Fence&#xff09; 对 volatile 变量的写指令后会加入写屏障对 volatile 变量的读指令前会加…

正则表达式_字符匹配/可选字符集

正则表达式&#xff08;Regular Expression&#xff09;也叫匹配模式(Pattern)&#xff0c;用来检验字符串是否满足特 定规则&#xff0c;或从字符串中捕获满足特定规则的子串。 字符匹配 最简单的正则表达式由“普通字符”和“通配符”组成。比如“Room\d\d\d”就这样 的正则…

短网址短链接哪个好用?2024年最好的缩短链接短网址推荐

短网址&#xff0c;又称短链接&#xff0c;英文名为Short URL&#xff0c;是一种形式上比较短的网址&#xff0c;使用跳转到方式代替长网址链接&#xff0c;形式美观&#xff0c;而且更容易分享。最出名的短网址服务有国外的bit.ly和谷歌goo.gl&#xff0c;以及国内的百度短网址…

AI+客服行业落地应用

一、客服行业变迁 1.传统客服时代 &#xff08;1&#xff09;客服工作重复性高&#xff0c;技术含量低 &#xff08;2&#xff09;呼出效率低&#xff0c;客服水平参差不齐 &#xff08;3&#xff09;管理难度高&#xff0c;情绪不稳定 &#xff08;4&#xff09;服务质量…

《视觉十四讲》例程运行记录(1)—— 课本源码下载和3rdparty文件夹是空的解决办法

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、第二版十四讲课本源码下载1. 安装git工具 二、Pangolin下载和安装1. 源码下载2. Pangolin的安装(1) 安装依赖项(2) 源码编译安装(2) 测试是否安装成功 二、…

4:分配器测试

文章目录 分配器作用容器中默认的分配器分配器测试程序这节课并没有总结各种分配器的使用结果 分配器作用 负责分配和管理容器的空间的 不需要用户手动创建 容器中默认的分配器 第二个参数表示默认的分配器 每一个容器初始化的时候 带一个默认的分配器 分配器测试程序 右边的…

商城数据库88张表结构完整示意图61~70(十四)

六十一&#xff1a; 六十二&#xff1a; 六十三&#xff1a; 六十四&#xff1a; 六十五&#xff1a; 六十六&#xff1a; 六十七&#xff1a; 六十八&#xff1a; 六十九&#xff1a; 七十&#xff1a;

深度学习之基于YOLOv5的山羊行为识别系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 深度学习之基于YOLOv5的山羊行为识别系统是一个创新的项目&#xff0c;旨在通过深度学习和目标检测技术&#xff0c…

【数据结构(邓俊辉)学习笔记】列表04——排序器

文章目录 0. 统一入口1. 选择排序1.1 构思1.2 实例1.3 实现1.4 复杂度 2. 插入排序2.1 构思2.2 实例2.3 实现2.4 复杂度分析2.5 性能分析 3. 归并排序3.1 二路归并算法3.1.1 二路归并算法原理3.1.2 二路归并算法实现3.1.3 归并时间 3.2 分治策略3.2.1 实现3.2.2 排序时间 4. 总…

【Java】基本程序设计结构(二)

前言&#xff1a;上一篇我们详细介绍了Java基本程序设计结构中前半部分&#xff0c;一个简单的Java应用&#xff0c;注释&#xff0c;数据类型&#xff0c;变量与常量&#xff0c;运算符&#xff0c;字符串。包括本篇将延续上篇内容介绍后续内容&#xff0c;包括输入输出&#…

AutoTable, Hibernate自动建立表替代方案

痛点 之前一直使用JPA为主要ORM技术栈&#xff0c;主要是因为Mybatis没有实体逆向建表功能。虽然Mybatis有从数据库建立实体&#xff0c;但是实际应用却没那么美好&#xff1a;当实体变更时&#xff0c;往往不会单独再建立一个数据库重新生成表&#xff0c;然后把表再逆向为实…

渠道管控治理思路建议

品牌在做控价时&#xff0c;一定要有渠道一体化的治理想法&#xff0c;不能只能打击某一家店铺为想法进行治理&#xff0c;比如只打击非授权&#xff0c;只不去管理授权体系的经销商店铺&#xff0c;这显然是不行的&#xff0c;管理非授权的同时&#xff0c;授权也要管理好&…