Linux C/C++ socket

一、第一个网络通讯程序

  • 网络通讯是指两台计算机中的程序进行传输数据的过程
  • 客户程序(端):指主动发起通讯的程序。
  • 服务程序(端/器):指被动的等待,然后为向它发起通讯的客户端提供服务。
/** 程序名:demo1.cpp,此程序用于演示socket的客户端
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
using namespace std;int main(int argc,char *argv[])
{if (argc!=3){cout << "Using:./demo1 服务端的IP 服务端的端口\nExample:./demo1 192.168.101.139 5005\n\n"; return -1;}// 第1步:创建客户端的socket。  int sockfd = socket(AF_INET,SOCK_STREAM,0);if (sockfd==-1){perror("socket"); return -1;}// 第2步:向服务器发起连接请求。 struct hostent* h;    // 用于存放服务端IP的结构体。if ( (h = gethostbyname(argv[1])) == 0 )  // 把字符串格式的IP转换成结构体。{ cout << "gethostbyname failed.\n" << endl; close(sockfd); return -1;}struct sockaddr_in servaddr;              // 用于存放服务端IP和端口的结构体。memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family = AF_INET;memcpy(&servaddr.sin_addr,h->h_addr,h->h_length); // 指定服务端的IP地址。servaddr.sin_port = htons(atoi(argv[2]));         // 指定服务端的通信端口。if (connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr))!=0)  // 向服务端发起连接清求。{ perror("connect"); close(sockfd); return -1; }// 第3步:与服务端通讯,客户发送一个请求报文后等待服务端的回复,收到回复后,再发下一个请求报文。char buffer[1024];for (int ii=0;ii<3;ii++)  // 循环3次,将与服务端进行三次通讯。{int iret;memset(buffer,0,sizeof(buffer));sprintf(buffer,"这是第%d个超级女生,编号%03d。",ii+1,ii+1);  // 生成请求报文内容。// 向服务端发送请求报文。if ( (iret=send(sockfd,buffer,strlen(buffer),0))<=0){ perror("send"); break; }cout << "发送:" << buffer << endl;memset(buffer,0,sizeof(buffer));// 接收服务端的回应报文,如果服务端没有发送回应报文,recv()函数将阻塞等待。if ( (iret=recv(sockfd,buffer,sizeof(buffer),0))<=0){cout << "iret=" << iret << endl; break;}cout << "接收:" << buffer << endl;sleep(1);}// 第4步:关闭socket,释放资源。close(sockfd);
}
/** 程序名:demo2.cpp,此程序用于演示socket通信的服务端
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
using namespace std;int main(int argc,char *argv[])
{if (argc!=2){cout << "Using:./demo2 通讯端口\nExample:./demo2 5005\n\n";   // 端口大于1024,不与其它的重复。cout << "注意:运行服务端程序的Linux系统的防火墙必须要开通5005端口。\n";cout << "      如果是云服务器,还要开通云平台的访问策略。\n\n";return -1;}// 第1步:创建服务端的socket。 int listenfd = socket(AF_INET,SOCK_STREAM,0);if (listenfd==-1) { perror("socket"); return -1; }// 第2步:把服务端用于通信的IP和端口绑定到socket上。 struct sockaddr_in servaddr;          // 用于存放服务端IP和端口的数据结构。memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family = AF_INET;        // 指定协议。servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 服务端任意网卡的IP都可以用于通讯。servaddr.sin_port = htons(atoi(argv[1]));     // 指定通信端口,普通用户只能用1024以上的端口。// 绑定服务端的IP和端口。if (bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) != 0 ){ perror("bind"); close(listenfd); return -1; }// 第3步:把socket设置为可连接(监听)的状态。if (listen(listenfd,5) != 0 ) { perror("listen"); close(listenfd); return -1; }// 第4步:受理客户端的连接请求,如果没有客户端连上来,accept()函数将阻塞等待。int clientfd=accept(listenfd,0,0);if (clientfd==-1){perror("accept"); close(listenfd); return -1; }cout << "客户端已连接。\n";// 第5步:与客户端通信,接收客户端发过来的报文后,回复ok。char buffer[1024];while (true){int iret;memset(buffer,0,sizeof(buffer));// 接收客户端的请求报文,如果客户端没有发送请求报文,recv()函数将阻塞等待。// 如果客户端已断开连接,recv()函数将返回0。if ( (iret=recv(clientfd,buffer,sizeof(buffer),0))<=0) {cout << "iret=" << iret << endl;  break;   }cout << "接收:" << buffer << endl;strcpy(buffer,"ok");  // 生成回应报文内容。// 向客户端发送回应报文。if ( (iret=send(clientfd,buffer,strlen(buffer),0))<=0) { perror("send"); break; }cout << "发送:" << buffer << endl;}// 第6步:关闭socket,释放资源。close(listenfd);   // 关闭服务端用于监听的socket。close(clientfd);   // 关闭客户端连上来的socket。
}

输出:

[root@localhost 08demo_net]# g++ -o server demo2.cpp 
[root@localhost 08demo_net]# g++ -o client demo1.cpp
[root@localhost 08demo_net]# ./server 5005
客户端已连接。
接收:这是第1个超级女生,编号001。
发送:ok
接收:这是第2个超级女生,编号002。
发送:ok
接收:这是第3个超级女生,编号003。
发送:ok
iret=0
[root@localhost 08demo_net]# ./client 127.0.0.1 5005
发送:这是第1个超级女生,编号001。
接收:ok
发送:这是第2个超级女生,编号002。
接收:ok
发送:这是第3个超级女生,编号003。
接收:ok

二、基于Linux的文件操作

// demo3.cpp,本程序演示了Linux底层文件的操作-创建文件并写入数据。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
using namespace std;int main()
{int fd;    // 定义一个文件描述符/文件句柄。// 打开文件,注意,如果创建后的文件没有权限,可以手工授权chmod 777 data.txt。fd=open("data.txt",O_CREAT|O_RDWR|O_TRUNC);if (fd==-1){perror("open(data.txt)"); return -1;}printf("文件描述符fd=%d\n",fd);char buffer[1024];strcpy(buffer,"我是一只傻傻鸟。\n");if (write(fd,buffer,strlen(buffer))==-1)    // 把数据写入文件。{perror("write()"); return -1;}close(fd);  // 关闭文件。
}
// demo4.cpp,本程序演示了Linux底层文件的操作-读取文件。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>int main()
{int fd;    // 定义一个文件描述符/文件句柄。fd=open("data.txt",O_RDONLY); // 打开文件。if (fd==-1){perror("open(data.txt)"); return -1;}printf("文件描述符fd=%d\n",fd);char buffer[1024];memset(buffer,0,sizeof(buffer));if (read(fd,buffer,sizeof(buffer))==-1)    // 从文件中读取数据。{perror("write()"); return -1;}printf("%s",buffer);close(fd);  // 关闭文件。
}
[root@localhost 09demo_file]# g++ -o demo3 demo3.cpp 
[root@localhost 09demo_file]# g++ -o demo4 demo4.cpp 
[root@localhost 09demo_file]# ./demo3
文件描述符fd=3
[root@localhost 09demo_file]# ./demo4
文件描述符fd=3
我是一只傻傻鸟。
  • /proc/进程id/fd目录中,存放了每个进程打开的fd。
  • Linux进程默认打开了三个文件描述符:0-标准输入(键盘),1-标准输出(显示器),2-标准错误(显示器)。
  • 文件描述符的分配规则是:找到最小的,没有被占用的文件描述符。

查看进程打开的文件描述符

[root@localhost /]# cd /proc
[root@localhost proc]# ls
1     1388  16    22   29   36   415  50   6    689  acpi       driver       key-users   mtrr          sys
10    1391  1613  23   297  37   416  502  602  690  asound     execdomains  kmsg        net           sysrq-trigger
101   14    1627  24   298  38   417  51   604  691  buddyinfo  fb           kpagecount  pagetypeinfo  sysvipc
1066  1415  18    25   299  385  418  52   606  694  bus        filesystems  kpageflags  partitions    timer_list
1067  1439  1825  26   30   386  419  528  608  697  cgroups    fs           loadavg     sched_debug   timer_stats
1069  1445  1826  27   300  396  420  53   609  7    cmdline    interrupts   locks       schedstat     tty
11    1469  19    274  301  397  421  537  610  714  consoles   iomem        mdstat      scsi          uptime
12    1477  2     28   302  4    422  568  611  720  cpuinfo    ioports      meminfo     self          version
1216  1500  20    286  303  411  46   569  66   726  crypto     irq          misc        slabinfo      vmallocinfo
1226  1537  2073  287  305  412  48   570  663  763  devices    kallsyms     modules     softirqs      vmstat
1227  1547  21    288  306  413  49   571  687  8    diskstats  kcore        mounts      stat          zoneinfo
13    1591  2171  289  35   414  5    599  688  9    dma        keys         mpt         swaps
[root@localhost proc]# cd 688
[root@localhost 688]# ls
attr        cmdline          environ  io         mem         ns             pagemap      sched      stack    task
autogroup   comm             exe      limits     mountinfo   numa_maps      patch_state  schedstat  stat     timers
auxv        coredump_filter  fd       loginuid   mounts      oom_adj        personality  sessionid  statm    uid_map
cgroup      cpuset           fdinfo   map_files  mountstats  oom_score      projid_map   setgroups  status   wchan
clear_refs  cwd              gid_map  maps       net         oom_score_adj  root         smaps      syscall
[root@localhost 688]# cd fd
[root@localhost fd]# ls
0  1  2

关闭标准输入标准输出

close(0);
close(1);

三、万恶的结构体

  1. sockaddr结构体

存放协议族、端口和地址信息,客户端和connect()函数和服务端的bind()函数需要这个结构体。

struct sockaddr {unsigned short sa_family;	// 协议族,与socket()函数的第一个参数相同,填AF_INET。unsigned char sa_data[14];	// 14字节的端口和地址。
};
  1. sockaddr_in结构体

sockaddr结构体是为了统一地址结构的表示方法,统一接口函数,但是,操作不方便,所以定义了等价的sockaddr_in结构体,它的大小与sockaddr相同,可以强制转换成sockaddr。

struct sockaddr_in {  unsigned short sin_family;	// 协议族,与socket()函数的第一个参数相同,填AF_INET。unsigned short sin_port;		// 16位端口号,大端序。用htons(整数的端口)转换。struct in_addr sin_addr;		// IP地址的结构体。192.168.101.138unsigned char sin_zero[8];	// 未使用,为了保持与struct sockaddr一样的长度而添加。};struct in_addr {				// IP地址的结构体。unsigned int s_addr;		// 32位的IP地址,大端序。};
  1. gethostbyname函数

根据域名/主机名/字符串IP获取大端序IP,用于网络通讯的客户端程序中。

struct hostent *gethostbyname(const char *name);struct hostent { char *h_name;   	// 主机名。char **h_aliases;   // 主机所有别名构成的字符串数组,同一IP可绑定多个域名。 short h_addrtype; 	// 主机IP地址的类型,例如IPV4(AF_INET)还是IPV6。short h_length;   	// 主机IP地址长度,IPV4地址为4,IPV6地址则为16。char **h_addr_list; 	// 主机的ip地址,以网络字节序存储。 };#define h_addr h_addr_list[0] 	// for backward compatibility.

转换后,用以下代码把大端序的地址复制到sockaddr_in结构体的sin_addr成员中。

memcpy(&servaddr.sin_addr,h->h_addr,h->h_length);

4.字符串IP与大端序IP的转换

C语言提供了几个库函数,用于字符串格式的IP和大端序IP的互相转换,用于网络通讯的服务端程序中。

typedef unsigned int in_addr_t;  // 32位大端序的IP地址。// 把字符串格式的IP转换成大端序的IP,转换后的IP赋给sockaddr_in.in_addr.s_addr。
in_addr_t inet_addr(const char *cp); // 把字符串格式的IP转换成大端序的IP,转换后的IP将填充到sockaddr_in.in_addr成员。
int inet_aton(const char *cp, struct in_addr *inp);	// 把大端序IP转换成字符串格式的IP,用于在服务端程序中解析客户端的IP地址。
char *inet_ntoa(struct in_addr in);
/** 程序名:demo5.cpp,此程序用于演示socket的客户端
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
using namespace std;int main(int argc,char *argv[])
{if (argc!=3){cout << "Using:./demo5 服务端的IP 服务端的端口\nExample:./demo5 192.168.101.138 5005\n\n"; return -1;}// 第1步:创建客户端的socket。  int sockfd = socket(AF_INET,SOCK_STREAM,0);if (sockfd==-1){perror("socket"); return -1;}// 第2步:向服务器发起连接请求。 struct sockaddr_in servaddr;               // 用于存放协议、端口和IP地址的结构体。memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family = AF_INET;             // ①协议族,固定填AF_INET。servaddr.sin_port = htons(atoi(argv[2]));  // ②指定服务端的通信端口。struct hostent* h;                         // 用于存放服务端IP地址(大端序)的结构体的指针。if ( (h = gethostbyname(argv[1])) == nullptr )  // 把域名/主机名/字符串格式的IP转换成结构体。{ cout << "gethostbyname failed.\n" << endl; close(sockfd); return -1;}memcpy(&servaddr.sin_addr,h->h_addr,h->h_length); // ③指定服务端的IP(大端序)。//servaddr.sin_addr.s_addr=inet_addr(argv[1]); // ③指定服务端的IP,只能用IP,不能用域名和主机名。if (connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr))==-1)  // 向服务端发起连接清求。{ perror("connect"); close(sockfd); return -1; }// 第3步:与服务端通讯,客户发送一个请求报文后等待服务端的回复,收到回复后,再发下一个请求报文。char buffer[1024];for (int ii=0;ii<10;ii++)  // 循环3次,将与服务端进行三次通讯。{int iret;memset(buffer,0,sizeof(buffer));sprintf(buffer,"这是第%d个超级女生,编号%03d。",ii+1,ii+1);  // 生成请求报文内容。// 向服务端发送请求报文。if ( (iret=send(sockfd,buffer,strlen(buffer),0))<=0){ perror("send"); break; }cout << "发送:" << buffer << endl;memset(buffer,0,sizeof(buffer));// 接收服务端的回应报文,如果服务端没有发送回应报文,recv()函数将阻塞等待。if ( (iret=recv(sockfd,buffer,sizeof(buffer),0))<=0){cout << "iret=" << iret << endl; break;}cout << "接收:" << buffer << endl;sleep(1);}// 第4步:关闭socket,释放资源。close(sockfd);
}
/** 程序名:demo6.cpp,此程序用于演示socket通信的服务端
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
using namespace std;int main(int argc,char *argv[])
{if (argc!=2){cout << "Using:./demo6 通讯端口\nExample:./demo6 5005\n\n";   // 端口大于1024,不与其它的重复。cout << "注意:运行服务端程序的Linux系统的防火墙必须要开通5005端口。\n";cout << "      如果是云服务器,还要开通云平台的访问策略。\n\n";return -1;}// 第1步:创建服务端的socket。 int listenfd = socket(AF_INET,SOCK_STREAM,0);if (listenfd==-1) { perror("socket"); return -1; }// 第2步:把服务端用于通信的IP和端口绑定到socket上。 struct sockaddr_in servaddr;                // 用于存放协议、端口和IP地址的结构体。memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family=AF_INET;                // ①协议族,固定填AF_INET。servaddr.sin_port=htons(atoi(argv[1]));     // ②指定服务端的通信端口。servaddr.sin_addr.s_addr=htonl(INADDR_ANY); // ③如果操作系统有多个IP,全部的IP都可以用于通讯。//servaddr.sin_addr.s_addr=inet_addr("192.168.101.138"); // ③指定服务端用于通讯的IP(大端序)。// 绑定服务端的IP和端口。if (bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr))==-1){ perror("bind"); close(listenfd); return -1; }// 第3步:把socket设置为可连接(监听)的状态。if (listen(listenfd,5) == -1 ) { perror("listen"); close(listenfd); return -1; }// 第4步:受理客户端的连接请求,如果没有客户端连上来,accept()函数将阻塞等待。int clientfd=accept(listenfd,0,0);if (clientfd==-1){perror("accept"); close(listenfd); return -1; }cout << "客户端已连接。\n";// 第5步:与客户端通信,接收客户端发过来的报文后,回复ok。char buffer[1024];while (true){int iret;memset(buffer,0,sizeof(buffer));// 接收客户端的请求报文,如果客户端没有发送请求报文,recv()函数将阻塞等待。// 如果客户端已断开连接,recv()函数将返回0。if ( (iret=recv(clientfd,buffer,sizeof(buffer),0))<=0) {cout << "iret=" << iret << endl;  break;   }cout << "接收:" << buffer << endl;strcpy(buffer,"ok");  // 生成回应报文内容。// 向客户端发送回应报文。if ( (iret=send(clientfd,buffer,strlen(buffer),0))<=0) { perror("send"); break; }cout << "发送:" << buffer << endl;}// 第6步:关闭socket,释放资源。close(listenfd);   // 关闭服务端用于监听的socket。close(clientfd);   // 关闭客户端连上来的socket。
}

四、socket函数详解

人与人沟通的方式有很多种:书信、电话、QQ、微信。如果两个人想沟通,必须先选择一种沟通的方式,如果一方使用电话,另一方也应该使用电话,而不是书信。

协议是网络通讯的规则,是约定。

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

成功返回一个有效的socket,失败返回-1,errno被设置。

全部网络编程的函数,失败时基本上都是返回-1,errno被设置。

1)domain通讯的协议家族

PF_INET IPv4互联网协议族。

PF_INET6 IPv6互联网协议族。

PF_LOCAL 本地通信的协议族。

PF_PACKET 内核底层的协议族。

PF_IPX IPX Novell协议族。

IPv6尚未普及,其它的不常用。

2)typ数据传输的类型

SOCK_STREAM 面向连接的socket:1)数据不会丢失;2)数据的顺序不会错乱;3)双向通道。

SOCK_DGRAM 无连接的socket:1)数据可能会丢失;2)数据的顺序可能会错乱;3)传输的效率更高。

3)protocol最终使用的协议

在IPv4网络协议家族中,数据传输方式为SOCK_STREAM的协议只有IPPROTO_TCP,数据传输方式为SOCK_DGRAM的协议只有IPPROTO_UDP。

本参数也可以填0。

socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); // 创建tcp的sock

socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); // 创建udp的sock只要参数没填错,基本上不会失败。

不过,单个进程中创建的socket数量与受系统参数open files的限制。(ulimit -a )

五、TCP和UDP

1)TCP和UDP的区别

TCP

a)TCP面向连接,通过三次握手建立连接,四次挥手断开连接; 面试的重点

b)TCP是可靠的通信方式,通过超时重传、数据校验等方式来确保数据无差错,不丢失,不重复,并且按序到达;

c)TCP把数据当成字节流,当网络出现波动时,连接可能出现响应延迟的问题;

d)TCP只支持点对点通信;

e)TCP报文的首部较大,为20字节;

f)TCP是全双工的可靠信道。

UDP

a)UDP是无连接的,即发送数据之前不需要建立连接,这种方式为UDP带来了高效的传输效率,但也导致无法确保数据的发送成功;

b)UDP以最大的速率进行传输,但不保证可靠交付,会出现丢失、重复等等问题;

c)UDP没有拥塞控制,当网络出现拥塞时,发送方不会降低发送速率;

d)UDP支持一对一,一对多,多对一和多对多的通信;

e)UDP报文的首部比较小,只有8字节;

f)UDP是不可靠信道。

2)TCP保证自身可靠的方式

a)数据分片:在发送端对用户数据进行分片,在接收端进行重组,由TCP确定分片的大小并控制分片和重组;

b)到达确认:接收端接收到分片数据时,根据分片的序号向对端回复一个确认包;

c)超时重发:发送方在发送分片后开始计时,若超时却没有收到对端的确认包,将会重发分片;

d)滑动窗口:TCP 中采用滑动窗口来进行传输控制,发送方可以通过滑动窗口的大小来确定应该发送多少字节的数据。当滑动窗口为 0 时,发送方不会再发送数据;

e)失序处理:TCP的接收端会把接收到的数据重新排序;

f)重复处理:如果传输的分片出现重复,TCP的接收端会丢弃重复的数据;

g)数据校验:TCP通过数据的检验和来判断数据在传输过程中是否正确。

3)UDP不可靠的原因

没有上述TCP的机制,如果校验和出错,UDP会将该报文丢弃。

4)TCP和UDP使用场景

TCP 使用场景

TCP实现了数据传输过程中的各种控制,适合对可靠性有要求的场景。

UDP 使用场景

可以容忍数据丢失的场景:

视频、音频等多媒体通信(即时通信);

广播信息。

5)UDP能实现可靠传输吗?

这是个伪命题,如果用UDP实现可靠传输,那么,应用程序必须实现重传和排序等功能,非常麻烦,还不如直接用TCP。谁能保证自己写的算法比写TCP协议的人更牛?

六、主机字节序和网络字节序

  1. 大端序/小端序

如果数据类型占用的内存空间大于1字节,CPU把数据存放在内存中的方式有两种:

大端序(Big Endian):低位字节存放在高位,高位字节存放在低位。

小端序(Little Endia):低位字节存放在低位,高位字节存放在高位。

假设从内存地址0x00000001处开始存储十六进制数0x12345678,那么:

Bit-endian(按原来顺序存储)

0x00000001 0x12

0x00000002 0x34

0x00000003 0x56

0x00000004 0x78

Little-endian(颠倒顺序储存)

0x00000001 0x78

0x00000002 0x56

0x00000003 0x34

0x00000004 0x12

Intel系列的CPU以小端序方式保存数据,其它型号的CPU不一定。

操作文件的本质是把内存中的数据写入磁盘,在网络编程中,传输数据的本质也是把数据写入文件(socket也是文件描述符)。

这样的话,字节序不同的计算机之间传输数据,可能会出现问题。

  1. 网络字节序

为了解决不同字节序的计算机之间传输数据的问题,约定采用网络字节序(大端序)。

C语言提供了四个库函数,用于在主机字节序和网络字节序之间转换:

uint16_t h to n s(uint16_t hostshort); // uint16_t 2字节的整数 unsigned short

uint32_t htonl(uint32_t hostlong); // uint32_t 4字节的整数 unsigned int

uint16_t ntohs(uint16_t netshort);

uint32_t n to h l(uint32_t netlong);

h host(主机);

to 转换;

n network(网络);

s short(2字节,16位的整数);

l long(4字节,32位的整数);

  1. IP地址和通讯端口

在计算机中,IPv4的地址用4字节的整数存放,通讯端口用2字节的整数(0-65535)存放。

例如:192.168.190.134 3232284294 255.255.255.255

192 168 190 134

大端:11000000 10101000 10111110 10000110

小端:10000110 10111110 10101000 11000000

  1. 如何处理大小端序

在网络编程中,数据收发的时候有自动转换机制,不需要程序员手动转换,只有向sockaddr_in结体成员变量填充数据时,才需要考虑字节序的问题。


推荐一个零声学院项目课,个人觉得老师讲得不错,分享给大家:
零声白金学习卡(含基础架构/高性能存储/golang云原生/音视频/Linux内核)
https://xxetb.xet.tech/s/3Zqhgt

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

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

相关文章

洛谷P1305 新二叉树(树的基本遍历)

题目描述 输入一串二叉树&#xff0c;输出其前序遍历。 输入格式 第一行为二叉树的节点数 &#x1d45b;。(1≤&#x1d45b;≤26) 后面 &#x1d45b; 行&#xff0c;每一个字母为节点&#xff0c;后两个字母分别为其左右儿子。特别地&#xff0c;数据保证第一行读入的节点…

Apollo9.0 PNC源码学习之Control模块(一)—— 控制模块概览

0 前言 从planning的角度看control,首先需要了解的就是相关的数据接口,规划出的轨迹(路径+速度)发给Control模块去执行 modules/planning/planning_component/planning_component.cc planning模块发布轨迹信息 planning_writer_ = node_->CreateWriter<ADCTrajecto…

60行代码加速20倍: NEON实现深度学习OD任务后处理绘框

【前言】 本文版权属于GiantPandaCV&#xff0c;未经允许&#xff0c;请勿转载&#xff01; 最近在学neon汇编加速&#xff0c;由于此前OD任务发现在检测后处理部分使用OpenCV较为占用资源且耗时&#xff0c;遂尝试使用NEON做后处理绘框&#xff0c;以达到加速并降低CPU资源消耗…

Linux 中 “ 磁盘、进程和内存 ” 的管理

在linux虚拟机中也有磁盘、进程、内存的存在。第一步了解一下磁盘 一、磁盘管理 &#xff08;1.1&#xff09;磁盘了解 track&#xff08; 磁道 &#xff09; &#xff1a;就是磁盘上的同心圆&#xff0c;从外向里&#xff0c;依次排序1号&#xff0c;2号磁盘........等等。…

802.11中的各种帧

在无线网络中&#xff0c;802.11协议定义了三种类型的帧&#xff1a;管理帧&#xff08;Management Frames&#xff09;、控制帧&#xff08;Control Frames&#xff09;和数据帧&#xff08;Data Frames&#xff09;。每种类型的帧都有其特定的功能&#xff0c;帮助维护和管理…

QNX简述

文章目录 前言1. QNX简介1.1 什么是QNX1.2 QNX的应用场景1.3 QNX的优点1.4 QNX的发展史1.5 QNX的商业模式 2. QNX的技术特点3. QNX和其它操作系统的比较3.1 QNX VS LINUX3.2 QNX VS FreeRTOS3.3 QNX VS 鸿蒙操作系统 4. 我的疑问4.1 微内核看起来又稳定又容易调试&#xff0c;为…

【讯为Linux驱动开发】6.自旋锁spinlock

【自旋锁】 线程A获取自旋锁后&#xff0c;B假如想获取自旋锁则只能原地等待&#xff0c;仍占用CPU&#xff0c;不会休眠&#xff0c;直到获取自旋锁为止。 【函数】 DEFINE SINLOCK(spinlock t lock) 定义并初始化一个变量int spin lock init(spinlock t*lock) 初始化自…

技术速递|Java on Azure Tooling 5月更新 - Java 对 Azure 容器应用程序的入门指南支持

作者&#xff1a;Jialuo Gan 排版&#xff1a;Alan Wang 大家好&#xff0c;欢迎阅读 Java on Azure 工具 5 月份更新。在本次更新中&#xff0c;我们将介绍 Java 在 Azure 上的容器应用程序的入门指南。希望您喜欢这些更新&#xff0c;并享受使用 Azure 工具包的流畅体验。请下…

Python中用pip命令用稳定的国内源安装第三方库

近期发现python中安装三方库的最稳定的方式还是用pip命令&#xff0c;带上国内源的地址。 比如清华源&#xff1a; pip install 包名 -i https://pypi.tuna.tsinghua.edu.cn/simple/ 用这个带国内源的格式&#xff0c;非常稳定&#xff01;

audio标签怎么使用

<audio> 标签在 HTML 中用于嵌入音频内容&#xff0c;如音乐、歌曲、音效等。这个标签允许你在网页上直接播放音频&#xff0c;而无需依赖任何外部播放器或插件&#xff08;如过去的 Flash 插件&#xff09;。 以下是如何使用 <audio> 标签的基本示例&#xff1a;…

oss一个桶中如何创建多个文件夹并在上传文件时上传到相应指定的桶中

在阿里云OSS&#xff08;Object Storage Service&#xff09;中&#xff0c;文件夹的概念实际上是一个逻辑上的概念&#xff0c;因为OSS是一个基于对象的存储服务&#xff0c;而不是基于文件系统的。但是&#xff0c;你可以通过为对象指定特定的key来模拟文件夹结构。以下是如何…

《pvz植物大战僵尸杂交版》V2.0.88整合包火爆全网,支持安卓、ios、电脑等!

今天来给大家安利一款让人欲罢不能的游戏——《植物大战僵尸杂交版》2.0.88版。这可不是普通的植物大战僵尸&#xff0c;它可是席卷了B站&#xff0c;火爆全网的存在&#xff01; 先说说这个版本&#xff0c;它可是网络上现存最全的植物大战僵尸杂交版整合包。里面不仅有修改工…

什么是 OSI 模型?

OSI 模型&#xff08;开放式系统互联模型&#xff09;是一个由国际标准化组织&#xff08;ISO&#xff09;提出的概念模型&#xff0c;旨在为计算机网络的互联互通提供标准框架&#xff08;定义于 ISO/IEC 7498-1&#xff09;。该模型将通信系统中的数据流划分为七个层&#xf…

uni-app中添加路由拦截

uni-app中添加路由鉴权和路由拦截 在main.js中添加如下代码 let list ["navigateTo", "redirectTo", "reLaunch", "switchTab"] let routesWhitelist [/pages/tabs/classify,/pages/tabs/study,/pages/tabs/mine] // 可以直接跳转的…

torch.squeeze() dim=1 dim=-1 dim=2

对数据的维度进行压缩 使用方式&#xff1a;torch.squeeze(input, dimNone, outNone) 将输入张量形状中的1 去除并返回。 如果输入是形如(A1B1C1D)&#xff0c;那么输出形状就为&#xff1a; (ABCD) import torch x torch.rand(2, 1, 1, 3, 1, 4) print(x) print(x.shape) …

wms海外仓系统什么价格?中小海外仓怎么选到高性价比wms系统

随着海外仓业务复杂度的逐渐提升&#xff0c;现在中小海外仓对wms海外仓系统的需求也越来越强烈。但是对于预算有限的中小海外仓企业来说&#xff0c;怎么才能选到性价比比较高的wms海外仓系统呢&#xff1f; 今天我们就来聊一下这个问题&#xff0c;希望对有类似需求的海外仓…

Git基础指令(图文详解)

目录 Git概述Git基础指令Linux系统操作指令 Git软件指令1.配置信息2.名称和邮箱3.初始化版本库4.向版本库中添加文件5.修改版本库文件6. 查看版本库文件历史 7.删除文件8.恢复历史文件 Git概述 Git基础指令 Linux系统操作指令 Git是一款免费、开源的分布式版本控制系统&…

ORACLE中ROWNUM的机制和注意细节(避坑

问题背景 mybatis对接oracle数据库中会用ROWNUM做分页处理。 形如如下sql SELECT * FROM ( SELECT TMP.*, ROWNUM ROW_ID FROM ( SELECT * FROM YOUR_TABLE ) TMP WHERE ROWNUM < ?) WHERE ROW_ID > ?简单说&#xff0c;ROWNUM就是一个对查找结果分配行号的伪列。 问…

github ssh key的SHA256是什么

github ssh key的SHA256是什么 怎么知道github上自己的公钥指纹和本地的公钥是否一致&#xff1f; 计算方法如下&#xff1a; cat .ssh/id_rsa.pub |awk { print $2 } | # Only the actual key data without prefix or commentsbase64 -d | # decode as base64s…

Guava常用方法

目录 一、数学和数值操作 二、并发库 三、缓存 四、集合 五、I/O 与文件操作 六、网络 七、时间处理 八、事件总线 九、反射 十、范围和集合操作 十一、随机数和测试 十二、注解处理 十三、比较器和排序 十四、哈希和散列 Guava 是 Google 开源的一个 Java 工具库&#xff…