socket 通信(一)

news2024/11/25 12:28:13

一 基本示例

#include <stdio.h>
#include <sys/socket.h>  // socket()
#include <arpa/inet.h>   // inet_addr()
#include <netinet/in.h>  // sockaddr_in{} INADDR_ANY
#include <unistd.h>      // close()
#include <errno.h>       // errno
#include <string.h>      // strerror()
#include <stdbool.h>     // true

int main(){
  
  int bYes=1;
  struct sockaddr_in inAddr;
  
  printf("\n *** cpl time [%s]  *** \n",__TIME__);
  
  inAddr.sin_family = AF_INET;
  inAddr.sin_port = htons(10240);
  //inAddr.sin_addr.s_addr = inet_addr("0.0.0.0");
  inAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
  //inAddr.sin_addr.s_addr = inet_addr("192.168.28.56");
  
  int listenfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
  if (listenfd < 0) {
    printf("[%d][%s]",errno, strerror(errno));
    return -1;
  }
  
  if (setsockopt(listenfd,SOL_SOCKET,SO_REUSEPORT,&bYes,sizeof(bYes)) < 0) {
    printf("[%d][%s]",errno, strerror(errno));
    return -1;
  }
  
  if (setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&bYes,sizeof(bYes)) < 0) {
    printf("[%d][%s]",errno, strerror(errno));
    return -1;
  }
  

  if(0 > bind(listenfd,(struct sockaddr*)&inAddr,sizeof(struct sockaddr))) {
    printf("bind [%d][%s]",errno, strerror(errno));
    return -1;
  }
  
  if(0 > listen(listenfd,1)) {
    printf("listen [%d][%s]",errno, strerror(errno));
    return -1;
  }

  while(1) {
  
      struct sockaddr_in recvAddr;
      socklen_t stulen = sizeof(struct sockaddr);
      
      int fd = accept(listenfd,(struct sockaddr*)&recvAddr,&stulen);
      
      struct linger so_linger = {
          .l_onoff = true,    // 开启 linger 控制
          .l_linger = 0       // close_wait 时间 0s  
      };
      
      setsockopt(fd,SOL_SOCKET,SO_LINGER,&so_linger,sizeof(so_linger));
      
      if( fd > 0 ) {
      
          char buf[1024] = {0};
          
          int bt = recv(fd,buf,sizeof(buf),0);
          
          printf("recv [%d] from  [%s] :\n%s",bt,inet_ntoa(recvAddr.sin_addr),buf);
          
          send(fd,buf,sizeof(buf),0);
          
          close(fd);
          
      } else {
          printf("err [%d][%s]", errno, strerror(errno));
      }
      
  }  
  
  close(listenfd);
  return 0;
}

二 参数解析

2.1 结构体

struct sockaddr
内核 tcp / ip 协议栈实现:

// include\uapi\linux\socket.h
#define _K_SS_MAXSIZE	128	/* Implementation specific max size (see RFC2553) */
typedef unsigned short __kernel_sa_family_t;
// 这里本来 使用 匿名联合体 保证 内存对齐,这里简化了
struct __kernel_sockaddr_storage {
		struct {
			__kernel_sa_family_t	ss_family; /* address family */
			char __data[_K_SS_MAXSIZE - sizeof(unsigned short)];
		};
};

POSIX API 实现:

//include\linux\socket.h
typedef __kernel_sa_family_t	sa_family_t;
//1003.1g requires sa_family_t and that sa_data is char. 
struct sockaddr {
	sa_family_t	sa_family;	/* address family, AF_xxx	*/
	char sa_data_min[14];		/* Minimum 14 bytes of protocol address	*/
};

struct sockaddr_in

//include\uapi\linux\in.h
struct in_addr { __be32	s_addr; };

#define __SOCK_SIZE__	16		/* sizeof(struct sockaddr)	*/
struct sockaddr_in {
  __kernel_sa_family_t	sin_family;	/* Address family		*/
  __be16		sin_port;	/* Port number			*/
  struct in_addr	sin_addr;	/* Internet address		*/
  /* Pad to size of `struct sockaddr'. */
  unsigned char		__pad[__SOCK_SIZE__ - sizeof(short int) -
			sizeof(unsigned short int) - sizeof(struct in_addr)];
};

socket 不仅可以绑定 AF_INET 协议簇,还有 AF_UNIX 等各种通信域 ( communication domain );sockaddr.sa_data 含义会随 通信域 变化;sockaddr_in 就是 sockaddr 在 AF_INET 域内特化的形式;
使用 sockaddr_in 时要注意 保持网络字节序,big-endian,或者 bigger end,低位地址更高;

//include\linux\byteorder\generic.h
#define ___htonl(x) __cpu_to_be32(x)
#define ___htons(x) __cpu_to_be16(x)
#define ___ntohl(x) __be32_to_cpu(x)
#define ___ntohs(x) __be16_to_cpu(x)

字符串 到 整型 的转换可以使用 glibc 库函数;

int inet_aton (const char *name, struct in_addr *addr)
# MT-Safe locale | AS-Safe | AC-Safe
uint32_t inet_addr (const char *name)
# MT-Safe locale | AS-Safe | AC-Safe 

listen() 将 sockfd 标记为被动套接字,仅作为 accept 的入参 监听 来访连接;

SYNOPSIS
       int listen(int sockfd, int backlog);
DESCRIPTION
       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 using accept(2).
       The sockfd argument is a file descriptor that refers to a socket of type SOCK_STREAM or SOCK_SEQPACKET.
       The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow.  If  a
       connection request arrives when the queue is full, the client may receive an error with an indication of ECONNREFUSED
       or, if the underlying protocol supports retransmission, the request may be ignored so that a later reattempt at  con‐
       nection succeeds.
RETURN VALUE
       On success, zero is returned.  On error, -1 is returned, and errno is set to indicate the error.

几个常用 Inet 地址的内核实现;

//include\uapi\linux\in.h
/* Address to accept any incoming messages. */
#define	INADDR_ANY		((unsigned long int) 0x00000000)

/* Address to send to all hosts. */
#define	INADDR_BROADCAST	((unsigned long int) 0xffffffff)

/* Network number for local host loopback. */
#define	IN_LOOPBACKNET		127

/* Address to loopback in software to local host.  */
#define	INADDR_LOOPBACK		0x7f000001	/* 127.0.0.1   */
#define	IN_LOOPBACK(a)		((((long int) (a)) & 0xff000000) == 0x7f000000)

2.2 setsockopt

2.2.1 SO_REUSEADDR

一台设备,可以坐拥多个 IP(还有 127.0.0.1 环回口);对于 AF_INET 套接字,INADDR_ANY(0.0.0.0)作为 IP 通配符,能够指代当前设备所有 SrcIP(包括 127.0.0.1 和 192.168.*.*);

 man 7 socket
 
SO_REUSEADDR
              Indicates that the rules used in validating addresses supplied in a bind(2) call should allow reuse of local addresses.  For AF_INET
              sockets this means that a socket may bind, except when there is an active listening socket bound to the address.  When the listening
              socket  is bound to INADDR_ANY with a specific port then it is not possible to bind to this port for any local address.  Argument is
              an integer boolean flag.

SrcPort 一旦绑定到 活动的监听套接字 上,就无法再与其他套接字绑定;
SrcPort 绑定到 INADDR_ANY 上后就 无法 再绑定到 任何 本地其他 SrcIP 上;
SO_REUSEADDR 选项能够 打破这种限制 并运用在以下四种场景中;

2.2.1.1 非活动的监听套接字

  • SrcPort 没有 绑定到 活动的 监听套接字上时,开启 SO_REUSEADDR后,能够被再此绑定到 新监听套接字上;
  • 什么时候会出现 “ SrcPort 没有绑定到非活动的监听套接字上1 这种场景?:
    step 1. 启动了一个监听服务器;
    step 2. 接收 并 派生一个子进程处理请求(代理、负载均衡);
    step 3. 监听服务器 因 崩溃 等原因 终止运行;
    step 4. 守护进程 或 脚本重启 监听服务器,此时 SrcPort 正被子进程占用,但 SrcPort 并没有绑定到监听套接字;
    如果没有开启 SO_REUSEADDR,step 4 重启的监听服务器 会因为 SrcPort 被占用而导致 bind() 失败;

2.2.1.2 同一个端口多个服务器实例

  • 理论上只要每个服务器的 SrcIP 不同,如 INADDR_ANY 和 loopback(127.0.0.1),那么就应该允许重复 bind;
  • 然而实验结果打脸了… 树莓 pi 4 + Ubuntu,即便 设置了 SO_REUSEADDR 同样 bind 失败;
// server1 先启动
  int bNo = 0;
  inAddr.sin_port = htons(10240);
  inAddr.sin_addr.s_addr = INADDR_ANY; // = inet_addr("0.0.0.0");
  if (setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&bYes,sizeof(bYes)) < 0) {
    printf("[%d][%s]",errno, strerror(errno));
    return -1;
  }
  if(0 > bind(listenfd,(struct sockaddr*)&inAddr,sizeof(struct sockaddr))) {
    printf("bind [%d][%s]",errno, strerror(errno));
    return -1;
  }
  ...
// server2 后启动
  int bYes = 1;
  inAddr.sin_port = htons(10240);
  inAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 或者 192.168.28.56
  if (setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&bYes,sizeof(bYes)) < 0) {
    printf("[%d][%s]",errno, strerror(errno));
    return -1;
  }
  if(0 > bind(listenfd,(struct sockaddr*)&inAddr,sizeof(struct sockaddr))) {
    printf("bind [%d][%s]",errno, strerror(errno));
    return -1;
  }
  ...
> ./server1 &
> ./server2 &
bind [98][Address already in use] # bind 失败

目前 《UNIX网络编程 卷一》给出的解释是2 “很多操作系统已经不允许 对已经绑定了通配地址的端口 再绑定更为明确的地址”,为了防止某些 恶意服务 强行 “劫持” 正在提供服务的端口;所以即便设置了 SO_REUSEADDR,上述 server2 中的 127.0.0.1 依旧会绑定失败;
但问题是,我先启动的 server1 绑定到 127.0.0.1,后启动 server2 绑定到 INADDR_ANY,应该没有问题才对呀?
原因是啥?求求大佬们指条明路吧,这个问题让我备受煎熬 …

2.2.1.3 同一个端口多个IP

  • 实验的结果是 即便没有设置 SO_REUSEADDR,效果相同;
// server1 先启动
  int bNo = 0;
  inAddr.sin_port = htons(10240);
  inAddr.sin_addr.s_addr = inet_addr("192.168.28.56"); // 注意 IP
#if 0
  if (setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&bYes,sizeof(bYes)) < 0) {
    printf("[%d][%s]",errno, strerror(errno));
    return -1;
  }
#endif
  if(0 > bind(listenfd,(struct sockaddr*)&inAddr,sizeof(struct sockaddr))) {
    printf("bind [%d][%s]",errno, strerror(errno));
    return -1;
  }
  ...
// server2 后启动
  int bYes = 1;
  inAddr.sin_port = htons(10240);
  inAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 注意 IP
#if 0
  if (setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&bYes,sizeof(bYes)) < 0) {
    printf("[%d][%s]",errno, strerror(errno));
    return -1;
  }
#endif
  if(0 > bind(listenfd,(struct sockaddr*)&inAddr,sizeof(struct sockaddr))) {
    printf("bind [%d][%s]",errno, strerror(errno));
    return -1;
  }
  ...
> ./server1 &
> ./server2 &# bind 成功

2.2.1.4 完全重复的捆绑(completely duplicate binding)

  • 目前常见协议中,仅 UDP 协议支持;
  • 这个特性主要用于多播 (multicast)时,同一个主机上同时运行一个应用程序的多个副本;UDP 数据包是 多播(或者是 广播 broadcast)时,给每个套接字送一个副本;如果是单播,就只给单个套接字;详见 《UNIX网络编程 卷一》,这里不做讨论;

2.3 SO_REUSEPORT

	// ...
	if (setsockopt(listenfd,SOL_SOCKET,SO_REUSEPORT,&bYes,sizeof(bYes)) < 0) {
    printf("[%d][%s]",errno, strerror(errno));
    return -1;
  	}
  	// ...
 man 7 socket
  
SO_REUSEPORT (since Linux 3.9)
              Permits multiple AF_INET or AF_INET6 sockets to be bound to an identical socket address.  This option must be  set  on  each  socket
              (including the first socket) prior to calling bind(2) on the socket.  To prevent port hijacking, all of the processes binding to the
              same address must have the same effective UID.  This option can be employed with both TCP and UDP sockets.

              For TCP sockets, this option allows accept(2) load distribution in a multi-threaded server to be improved by using a  distinct  lis‐
              tener  socket  for  each thread.  This provides improved load distribution as compared to traditional techniques such using a single
              accept(2)ing thread that distributes connections, or having multiple threads that compete to accept(2) from the same socket.

              For UDP sockets, the use of this option can provide better distribution of incoming datagrams to multiple processes (or threads)  as
              compared to the traditional technique of having multiple processes compete to receive datagrams on the same socket.

这个是重量级!允许 真·完全重复的捆绑
只有一个要求,每个 套接字,都在 bind 前设置过 SO_REUSEPORT

// server1 
  int bNo = 0;
  inAddr.sin_port = htons(10240);
  inAddr.sin_addr.s_addr = INADDR_ANY;
  if (setsockopt(listenfd,SOL_SOCKET,SO_REUSEPORT,&bYes,sizeof(bYes)) < 0) {
    printf("[%d][%s]",errno, strerror(errno));
    return -1;
  }
  if(0 > bind(listenfd,(struct sockaddr*)&inAddr,sizeof(struct sockaddr))) {
    printf("bind [%d][%s]",errno, strerror(errno));
    return -1;
  }
  ...
// server2 
  int bYes = 1;
  inAddr.sin_port = htons(10240);
  inAddr.sin_addr.s_addr = inet_addr("192.168.28.56");
  if (setsockopt(listenfd,SOL_SOCKET,SO_REUSEPORT,&bYes,sizeof(bYes)) < 0) {
    printf("[%d][%s]",errno, strerror(errno));
    return -1;
  }
  if(0 > bind(listenfd,(struct sockaddr*)&inAddr,sizeof(struct sockaddr))) {
    printf("bind [%d][%s]",errno, strerror(errno));
    return -1;
  }
  ...
> ./server1 &
> ./server2 &
> ./server2 &
> netstat -lp | grep 10240
tcp        0      0 0.0.0.0:10240           0.0.0.0:*               LISTEN      3722/./server1
tcp        0      0 hk-desktop:10240        0.0.0.0:*               LISTEN      3670/./server2
tcp        0      0 hk-desktop:10240        0.0.0.0:*               LISTEN      3669/./server2

2.4 struct linger

struct linger so_linger = {
    .l_onoff = true,    // 开启 linger 控制
    .l_linger = 0       // close_wait 时间 0s  
};
setsockopt(fd,SOL_SOCKET,SO_LINGER,&so_linger,sizeof(so_linger));

四次挥手的最后,套接字会持续 TIME_WAIT 等待 2 个 MSL 时间后再 close 套接字;
设置 l_onoff = true 后,关闭套接字(或者进程崩溃),内核 只会等待 指定的 l_linger 时间,便抛弃 套接字 内核缓冲区中 残留的数据3,不等 2 个 MSL 时间, 也不重传;

三 过程解析

一个简单的 C-S 回显实验:
server.c:

#include <stdio.h>
#include <sys/socket.h>  // socket()
#include <arpa/inet.h>   // inet_addr()
#include <netinet/in.h>  // sockaddr_in{} INADDR_ANY
#include <unistd.h>      // close()
#include <errno.h>       // errno
#include <string.h>      // strerror()
#include <stdbool.h>     // true
#include <stdlib.h>	 // exit()

int main(){
  
  int bYes=1;
  int cnt = 4;
  struct sockaddr_in inAddr;
  
  printf("\n *** cpl time [%s]  *** \n",__TIME__);
  
  inAddr.sin_family = AF_INET;
  inAddr.sin_port = htons(10240);
  inAddr.sin_addr.s_addr = INADDR_ANY;
  
  int listenfd = socket(AF_INET,SOCK_STREAM|SOCK_CLOEXEC,IPPROTO_TCP); // fork 子进程时自动关闭 listenfd
  if (listenfd < 0) {
    printf("[%d][%s]",errno, strerror(errno));
    return -1;
  }
  
  setsockopt(listenfd,SOL_SOCKET,SO_REUSEPORT,&bYes,sizeof(bYes));

  if(0 > bind(listenfd,(struct sockaddr*)&inAddr,sizeof(struct sockaddr))) {
    printf("bind [%d][%s]",errno, strerror(errno));
    return -1;
  }
  
  if(0 > listen(listenfd,5)) {
    printf("listen [%d][%s]",errno, strerror(errno));
    return -1;
  }

  while(1) {
  
      struct sockaddr_in recvAddr;
      socklen_t stulen = sizeof(struct sockaddr);
      int fd = accept(listenfd,(struct sockaddr*)&recvAddr,&stulen);
      if( fd <= 0 ) continue;
      
      // child
      if(fork()==0) {
          	printf("sub server [%d] connect from [%s]\n",getpid(),inet_ntoa(recvAddr.sin_addr));
      	 	char szbuf[1024] = {0};
			int n = 0;
        	while( (n = recv(fd,szbuf,sizeof(szbuf),0)) > 0) {
        		printf("sub server [%d], recv [%d] from [%s]:%s\n",
        		        	getpid(),n,inet_ntoa(recvAddr.sin_addr),szbuf);
        		send(fd,szbuf,n); // 回显
        	}
        	
        	while(cnt--){
        		printf("sub server [%d] [%d]s before close fd [%d]\n", getpid(),cnt,fd);
        		sleep(1);
        	}
        	close(fd);
        	exit(0);
      }

	  // parent
	  printf("\nthis is listener, continue ...\n");
  }  
  
  close(listenfd);
  return 0;
}

client.c:

#include <stdio.h>
#include <sys/socket.h>  // socket()
#include <arpa/inet.h>   // inet_addr()
#include <netinet/in.h>  // sockaddr_in{} INADDR_ANY
#include <unistd.h>      // close()
#include <string.h>

int main() {
	char szBuf[1024]={0};
	struct sockaddr_in toAddr;
	printf("\n *** cpl time [%s]  *** \n",__TIME__);
	int sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
	
	toAddr.sin_family = AF_INET;
	toAddr.sin_port = htons(10240);
	toAddr.sin_addr.s_addr = INADDR_ANY;
	
	connect(sockfd ,(struct sockaddr *)&toAddr,sizeof(toAddr));
	
	while(fgets(szBuf,sizeof(szBuf),stdin) != NULL) { //
		
		write(sockfd,szBuf,strlen(szBuf));
		printf("client send to [0.0.0.0]: %s",szBuf);
		memset(szBuf,0,sizeof(szBuf));
	
		memset(szBuf,0,sizeof(szBuf));
		read(sockfd,szBuf,sizeof(szBuf));
		printf("client recv from [0.0.0.0]: %s",szBuf);
	}
	
 	return 0;
}

运行 server 和 client,并在过程中使用 netstat 和 ps 命令,查看 进程 和 套接字 的状态:

$$$$$$ gcc server.c -o server
$$$$$$ ./server &
[3] 6287
 *** cpl time [14:08:08]  ***
$$$$$$ netstat pl |grep 10240
tcp        1      0 localhost:42602         localhost:10240         CLOSE_WAIT
$$$$$$ ps -o pid,args,stat,wchan
   PID COMMAND                  STAT    WCHAN # wchan 参数表明当前进程 sleep 的内核具体函数命
   5564 -bash                     Ss  	do_wait
   6552 ./server                  S 	inet_csk_accept # 服务端正阻塞在 accpet
   6626 ps -o pid,args,stat,wchan       -	  # '-' 表示 当前进程正在 running 
$$$$$$ gcc client.c -o client
$$$$$$ ./client.out
 *** cpl time [14:08:31]  ***				# client 完三次握手第二步,收到 SCK 包后 阻塞在 fgets
this is listener, continue ... 				# server 完成三次握手,accept 返回 调用 fork
sub server [6334] connect from [127.0.0.1]  # fork() 得到 sub server 子进程
123456
client send to [0.0.0.0]: 123456 # 客户端发送 
sub server [6334], recv [7] from [127.0.0.1]:123456 # sub server 发送回显
client recv from [0.0.0.0]: 123456 # 客户端收到回显
^D 	# client 收到 EOF, client 退出前关闭套接字 向 subserver 发送四次挥手的第一个 FIN 包
$$$$$$ sub server [6334] [4]s before close fd [4] 
$$$$$$ sub server [6334] [3]s before close fd [4]
$$$$$$ sub server [6334] [2]s before close fd [4]
$$$$$$ netstat -ap |grep 10240 # 此时查看 套接字 状态
tcp        1      0 localhost:42602         localhost:10240         CLOSE_WAIT
tcp        0      0 localhost:39208         localhost:10240         FIN_WAIT2   -
# client 进程已退出,套接字(port=39208) 进入 FIN_WAIT2
tcp        0      0 localhost:10240         localhost:39208         CLOSE_WAIT  7364/./server
# subserver 收到 FIN 包, recv 返回 0, 向 client 回 ACK, 进入 CLOSE_WAIT
$$$$$$ sub server [6334] [1]s before close fd [4]
$$$$$$ sub server [6334] [0]s before close fd [4]
# subserver close(fd), 向 client 回 FIN, 进入 LAST_ACK, 子进程回 ACK 后应当进入 TIME_WAIT 状态
$$$$$$ ps -o pid,args,stat,wchan |grep server
   PID COMMAND                  STAT    WCHAN # wchan 参数表明当前进程 sleep 的内核具体函数命
   5564 -bash                     Ss  	do_wait
   6552 ./server                  S 	inet_csk_accept
   6334 [server] <defunct>        Z     -	  # server 没有调用 wait() sub server 成了“僵死”进程
   6626 ps -o pid,args,stat,wchan       -	  # '-' 表示 当前进程正在 running  

在这里插入图片描述


  1. UNIX 网络编程 卷一:套接字联网 API 第三版 P165 ↩︎

  2. UNIX 网络编程 卷一:套接字联网 API 第三版 P166 ↩︎

  3. https://www.cnblogs.com/schips/p/12553321.html ↩︎

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

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

相关文章

php7类型约束,严格模式

在PHP7之前&#xff0c;函数和类方法不需要声明变量类型 &#xff0c;任何数据都可以被传递和返回&#xff0c;导致几乎大部分的调用操作都要判断返回的数据类型是否合格。 为了解决这个问题&#xff0c;PHP7引入了类型声明。 目前有两类变量可以声明类型&#xff1a; 形参&a…

2023年贵州建筑八大员考试时间报名时间是什么时间呢?甘建二

2023年贵州建筑八大员考试时间报名时间是什么时候开始呢&#xff1f;贵州建筑八大员考试时间是什么时候呢&#xff1f;什么时候考试呢&#xff1f; 建筑八大员主要是用于企业资质、招投标、检查使用&#xff0c;目前很多企业急需这个证书&#xff0c;关于建筑八大员报名时间和考…

网络拥塞控制,对越远的流量越宽容

考虑下面的网络传输场景&#xff1a; ​ S1&#xff0c;S2&#xff0c;S3&#xff0c;S4 向 D 方向发送&#xff0c;R4 发生拥塞。R4 必须丢弃一些数据进行疏导反馈拥塞信号&#xff0c;否则谁也过不去。 优先丢掉离得最近的 S4 的数据&#xff0c;其次依次丢 S3&#xff0c;S2…

051:cesium加载mapbox的多种Style形式地图

第051个 点击查看专栏目录 本示例的目的是介绍如何在vue+cesium中加载mapbox的多种Style形式地图,包括dark-v11,streets-v12,navigation-night-v1,outdoors-v12,satellite-v9,satellite-streets-v12,light-v10。 直接复制下面的 vue+cesium源代码,操作2分钟即可运行实…

如何让心情保持平静?100多条禅修心法

静的层次和阶段 静首先是不要去争&#xff0c;没有任何争的心&#xff0c;没有任何杂念心。静有几个层次阶段&#xff1a; ⒈. 自己的心情相对于自己平静&#xff0c;是平静的第一个阶段。 ⒉. 第二个平静的阶段是&#xff1a;别人觉得你很静&#xff0c;自己也很静&#xf…

ArduPilot开源飞控系统之简单介绍

ArduPilot开源飞控系统之简单介绍1. 源由2. 了解&阅读2.1 ArduPilot历史2.2 关于GPLv32.3 ArduPilot系统组成2.4 ArduPilot代码结构3. 后续4. 参考资料ArduPilot是一个可信赖的自动驾驶系统&#xff0c;为人们带来便利。为此&#xff0c;提供了一套全面的工具&#xff0c;几…

人人看得懂的AI教程

人人看得懂的AI教程&#xff0c;从0开始入门AI教程&#xff0c;一步一步AI&#xff0c;人工智能学习笔记 现在写书真的方便&#xff0c;闲来无事写了本从0开始学AI的书籍&#xff0c;哈哈 一、基础知识 1.1 人工智能概览 1.2 机器学习 1.3 深度学习 1.4 数据科学 二、编程知…

Stable Diffusion - API和微服务开发

Stable Diffusion 是一种尖端的开源工具&#xff0c;用于从文本生成图像。 Stable Diffusion Web UI 通过 API 和交互式 UI 打开了许多这些功能。 我们将首先介绍如何使用此 API&#xff0c;然后设置一个示例&#xff0c;将其用作隐私保护微服务以从图像中删除人物。 推荐&…

CoreDNS 性能优化

CoreDNS 作为 Kubernetes 集群的域名解析组件&#xff0c;如果性能不够可能会影响业务&#xff0c;本文介绍几种 CoreDNS 的性能优化手段。合理控制 CoreDNS 副本数考虑以下几种方式:根据集群规模预估 coredns 需要的副本数&#xff0c;直接调整 coredns deployment 的副本数:k…

总结下Spring boot异步执行逻辑的几种方式

文章目录概念实现方式Thread说明Async注解说明线程池CompletableFuture&#xff08;Future及FutureTask&#xff09;创建CompletableFuture异步执行消息队列概念 异步执行模式&#xff1a;是指语句在异步执行模式下&#xff0c;各语句执行结束的顺序与语句执行开始的顺序并不一…

【高项】项目人力资源管理,沟通管理与干系人管理(十大管理)

【高项】项目人力资源管理&#xff0c;沟通管理与干系人管理&#xff08;十大管理&#xff09; 文章目录1、人力资源管理1.1 什么是人力资源管理&#xff1f;1.2 如何进行人力资源管理&#xff1f;&#xff08;过程&#xff09;1.3 人力资源管理工具1.4 人力资源管理文件2、沟通…

自动驾驶BEV感知系列算法整理总结

序论 之前一直做的lidar感知&#xff0c;现在感觉大趋势是多传感器融合&#xff0c;所以博主也在向BEV下的融合框架学习&#xff0c;希望大家后面可以多多交流&#xff0c;下面会分为两类进行介绍&#xff0c;后期的文章会在下面两类中以小标题的形式出现&#xff0c;BEV下的两…

《推荐PlumGPT:一款优秀的聊天机器人》

PlumGPT是一款仿照ChatGPT的聊天机器人&#xff0c;它使用了最先进的自然语言处理技术来实现人机交互。PlumGPT不仅能够进行普通的对话&#xff0c;还可以回答各种问题&#xff0c;提供各种建议和信息。它能够对不同的话题做出详细的回答&#xff0c;例如科技、健康、娱乐、新闻…

[element]element-ui框架下载

⭐作者介绍&#xff1a;大二本科网络工程专业在读&#xff0c;持续学习Java&#xff0c;努力输出优质文章 ⭐作者主页&#xff1a;逐梦苍穹 ⭐如果觉得文章写的不错&#xff0c;欢迎点个关注一键三连&#x1f609;有写的不好的地方也欢迎指正&#xff0c;一同进步&#x1f601;…

线程同步与互斥【Linux】

文章目录1. 引入2. 前导概念2.1 同步与异步2.2 互斥与并发2.3 原子性操作2.4 临界资源和临界区临界资源临界区如何管理3. 互斥锁3.1 引入3.2 概念3.3 示例pthread_mutex函数家族用法全局锁局部锁3.4 性能损耗3.5 串行执行3.6 补充4. 互斥锁的实现原理4.1 线程的执行和阻塞4.2 自…

Java虚拟机对象

对象的创建 当虚拟机遇到一条字节码new指令时&#xff0c;首先检查指令的参数能否在常量池中定位到一个类的符号引用&#xff0c;并且检查这个符号引用代表的类是否已被加载、解析和初始化&#xff0c;如果没有就先执行类加载过程。 在类加载检查完毕后&#xff0c;就要对这个…

常见的7种软件规模估算方法 优劣势比较

业内主要的软件规模估算方法&#xff1a;LOC估算方法、故事点估算法、FPA功能点估算方法、COSMIC功能点估算方法、快速功能点估算方法、IFPUG功能点估算方法和自动化功能点估算方法。 1、LOC估算方法 LOC是源代码的总行数。通过统计源代码中的行数&#xff0c;来估算软件规模。…

Spring项目的创建和使用

Spring&#xff1a;Spring是一个包含众多工具方法的IoC容器&#xff1b; Spring的核心功能&#xff1a; 将对象&#xff08;俗称Bean&#xff09;存储到Sping容器中从容器中取出对象目录 一&#xff0c;创建Spring项目 1&#xff0c;创建一个Maven项目 2&#xff0c;添加Spri…

[STL]string的使用+模拟实现

[STL]string的使用模拟实现 文章目录[STL]string的使用模拟实现一、STL1.什么是STL2.如何学习STL二、string1.string类的介绍2.string的常用接口string的构造string的迭代器string的容量操作string的访问string的修改string的其他接口string的非成员函数接口三、string的模拟实…

Node【七】初识Express框架

文章目录&#x1f31f;前言&#x1f31f;Express框架&#x1f31f;1.什么是框架&#x1f31f;2.express安装&#x1f31f;3.创建web服务基本遵循之前的四个步骤&#xff1a;&#x1f31f;4.路由&#x1f31f; 由 &#xff1a;请求方式请求路径&#xff08;1&#xff09;get发送…