【计算机网络】socket 网络套接字
网络套接字
- 一、端口号
- 1. 认识端口号
- 2. socket
- 二、认识TCP协议和UDP协议
- 1. TCP协议
- 2. UDP协议
- 三、网络字节序
- 四、socket 编程
- 1. socket 常见API
- 2. sockaddr 结构
- 3. 编写 UDP 服务器
- (1)socket()
- (2)bind()
- (3)recvfrom()
- (4)sendto()
- (5)udp 服务端和客户端
- 4. 地址转换函数
- (1)相关接口
- (2)关于 inet_ntoa
- 5. 编写 TCP 服务器
- (1)listen()
- (2)accept()
- (3)con
- (4)守护进程
- (5)tcp 服务端和客户端
一、端口号
1. 认识端口号
实际上我们两台机器在进行通信时,是应用层在进行通信,应用层必定会推动下层和对方的上层进行通信。
其实网络协议栈中的下三层,主要解决的是数据安全可靠的送到远端机器。而用户使用应用层软件,完成数据发送和接收的。那么用户要使用软件,首先需要把这个软件启动起来!所以软件启动起来,本质就是进程!所以两台机器进行通信,本质是两台机器之上的应用层在通信,也就是两个进程之间在互相交换数据!所以网络通信的本质就是进程间通信!只不过在网络通信中的公共资源是网络,通过网络协议栈利用网络资源,让两个不同的进程看到了同一份资源!
在网络协议栈中,在传输层怎么把数据正确交给上层应用层呢?怎么知道交给哪一个应用呢?所以就要求上层应用层和传输层之间必须协商一种方案,让我们把数据准确交给上层,这个方案我们称为端口号。所以在传输层的报头中,必须要有原端口号和目的端口号,也就是根据目的端口号就可以决定这个数据的有效载荷要交给上层应用的哪一个!所以对于端口号无论对于客户端和服务端,都能唯一的标识该主机上的一个网络应用层的进程!
我们可以这样理解,其实在传输层当中,操作系统会形成一张哈希表,哈希表中的类型是 task_struct*,每一个应用层都要和该哈希表绑定端口号,本质就是根据端口号在哈希表里做哈希运算,如果该位置已经被占用了,就不能被绑定了,因为一个端口号只能被一个进程绑定;如果该位置没有被使用,就把该进程的pcb地址放在该位置上。
2. socket
因为在公网上,IP地址 能表示唯一的一台主机,端口号 port,用来标识该主机上的唯一的一个进程,所以 IP + port 就可以标识全网唯一的一个进程!那么我们在网络通信时,只需要在对应的报头上填上原IP和目的IP,原port和目的port,就可以将报文交给另一个主机的进程,这种基于 IP + port 的通信方式,我们称为 socket.
那么端口号和进程pid有什么区别呢?进程pid也能标识一台主机上的唯一进程啊?因为首先,不是所有的进程都要通信,但是所有的进程都要有pid!其次是为了使系统和网络功能解耦!
二、认识TCP协议和UDP协议
下面我们先认识一下两个传输层协议:
1. TCP协议
此处我们先对TCP(Transmission Control Protocol 传输控制协议)有一个直观的认识;后面我们再详细讨论 TCP 的一些细节问题。
- 传输层协议
- 有连接
- 可靠传输
- 面向字节流
2. UDP协议
此处我们也是对UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识;后面再详细讨论。
- 传输层协议
- 无连接
- 不可靠传输
- 面向数据报
三、网络字节序
我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分,网络数据流同样有大端小端之分。那么如何定义网络数据流的地址呢?
- 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
- 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
- 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
- TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
- 不管这台主机是大端机还是小端机,都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
- 如果当前发送主机是小端, 就需要先将数据转成大端;否则就忽略,直接发送即可;
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换:
- 这些函数名很好记,h 表示 host;n 表示 network,l 表示 32 位长整数,s 表示16位短整数;
- 例如 htonl 表示将 32 位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送;
- 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
- 如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。
四、socket 编程
1. 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);
2. sockaddr 结构
socket API 是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及后面要讲的 UNIX Domain Socket;然而,各种网络协议的地址格式并不相同
- 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 结构体指针做为参数。
3. 编写 UDP 服务器
(1)socket()
下面我们编写一个 UDP 服务器。首先需要做的是创建套接字,使用到的接口是 socket()
:
第一个参数是我们创建的套接字的域,即使用 IPv4 的网络协议还是 IPv6 的网络协议,目前我们只需要关注这两个即可,如下图:
第二个参数表示当前 socket 对应的类型,也就是相当于这个套接字未来给我们提供什么服务,是面向字节流的还是面向用户数据报的,如下:
第三个参数表示的是协议类型,目前我们不需要传这个参数。
而返回值相当于是一个文件描述符,所以创建一个套接字的本质,在底层就相当于是打开一个文件,只不过以前的 struct file 指向的是键盘、显示器这样的设备;而现在指向的是网卡设备。
(2)bind()
创建套接字成功之后,接下来就要绑定端口号,使用到的接口是 bind()
,如下:
其中第一个参数就是创建套接字时的返回值;第二个参数是一个结构体;第三个参数是结构体的长度。但是我们在网络套接字编程的时候不用第二个参数类型的结构体,这个结构体它只是设计接口用,我们实际用的是 sockaddr_in 类型的结构体,只需要在传参的时候进行强转即可。我们可以使用 bzero() 接口将该结构体清0;
我们是要使用 bind 来让套接字和我们往该结构体中填充的网络信息要关联起来,所以我们需要想该结构体中填充对应的字段。该结构体中有如下字段:
对应下图:
其中 sin_zero 为该结构体的填充字段,也就是这些字段不用填充,当作占位符即可;sin_addr 代表 ip 地址;sin_port 代表服务器所使用的端口号;sin_family 代表该结构体对应的网络协议类型,IPv4 或者 IPv6.
因为我们在给对方发送数据的时候,我们也一定需要让对方知道我们是谁,所以我们需要将端口号携带上,发送给对方,这样对方把数据处理完,就可以给我们响应回来。所以端口号是要在网络里来回发送的,也就是需要保证我们的端口号是网路字节序列,因为该端口号是要给对方发送的。所以这里我们就需要用到主机序列转网络序列的接口,由于端口号是两个字节,所以用到的接口为 htons()
:
由于我们用户一般用的都是点分十进制字符串风格的 IP 地址,也就是 0.0.0.0 这种风格,每个点分的范围是 0~255,每个字符一个字节,远远超过结构体中要求的 32 位 ip 地址,也就是四字节。所以我们需要将该字符串类型转换为 uint32_t 的类型,那么用到的接口是 inet_addr()
,它的作用就是将字符串风格的 ip 地址转化为网络风格的 uint32_t 类型,如下图:
同端口号一样,IP 地址也需要保证是网络字节序列。那么它的返回值类型 in_addr_t 其实就是符合网路字节序列的 uint32_t 的类型。
上面我们已经把准备工作做好了,接下来我们就需要使用 bind() 接口进行绑定,本质就是把我们定义的 struct 结构体设置进内核,设置进指定的套接字内部。
(3)recvfrom()
接下来我们就需要在指定的一个套接字里获取数据内容,使用到的接口是 recvfrom()
,如下图:
第一个参数就是网络文件描述符;第二个参数和第三个参数分别表示我们提供的缓冲区和它的长度,读到的数据就会放在缓冲区中;第三个参数设为0就是默认使用阻塞方式;最后两个参数又是熟悉的结构体,由于我们需要知道这些数据是谁给我们发的,因为我们有可能也要将数据给对方返回。所以最后两个参数其实是输出型参数。
返回值成功就是对应的长度,否则就是-1,如下:
(4)sendto()
将数据发送回给对方使用到的接口为 sendto()
,如下:
参数和 recvfrom() 的参数类似,这里不再介绍了。而最后两个参数是输入型参数,我们要将数据发回给对方,首先需要知道对方是谁,而我们上面已经通过 recvfrom() 获取到了对方的结构体信息,所以直接使用该结构体信息即可。
(5)udp 服务端和客户端
其中通过使用上面的接口编写的一个简单的接收客户端的字符串信息,并进行简单的加工的 udp 服务器代码链接为:UDP.
其中 udp server 的代码如下:
#pragma once#include <iostream>#include <string>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <strings.h>#include <unistd.h>#include <cstring>#include <functional>#include "log.hpp"using func_t = std::function<std::string(const std::string&)>;//typedef std::function<std::string(const std::string&)> func_t;std::string default_ip = "0.0.0.0";uint16_t default_port = 8080;log lg;class UdpServer{public:UdpServer(const uint16_t &port = default_port, const std::string &ip = default_ip): _port(port), _ip(ip), _isrunning(false), _sockfd(0){}void Init(){// 1.创建 udp 套接字_sockfd = socket(AF_INET, SOCK_DGRAM, 0); // AF_INET == PF_INETif (_sockfd < 0){lg(Fatal, "socket create faild, sockfd: %d", _sockfd);exit(1);}lg(Info, "socket create success, sockfd: %d", _sockfd);// 2.绑定端口号// 2.1 准备数据struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port); // 主机序列转网络序列// local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 1.string -> uint32_t 2.保证uint32_t是网络序列local.sin_addr.s_addr = htonl(INADDR_ANY);// 2.2 开始bindint n = bind(_sockfd, (const sockaddr *)&local, sizeof(local));if (n < 0){lg(Fatal, "bind faild, errno: %d, err message: %s", errno, strerror(errno));exit(2);}lg(Info, "bind success, errno: %d, err message: %s", errno, strerror(errno));}void Run(func_t func){_isrunning = true;char buffer[1024];while (_isrunning){// 记录客户端发来时的结构体信息struct sockaddr_in client;socklen_t len = sizeof(client);ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (sockaddr *)&client, &len);if (n < 0){lg(Warning, "recvfrom error, errno: %d, err message: %s", errno, strerror(errno));continue;}buffer[n] = 0;// 对数据进行简单的加工std::string info = buffer;std::string echo_string = func(info);// 发送回给对方sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, (const sockaddr *)&client, len);}}~UdpServer(){if (_sockfd > 0)close(_sockfd);}private:int _sockfd;uint16_t _port;std::string _ip;bool _isrunning;};
udp client 的代码如下:
#include <iostream>#include <cstdlib>#include <unistd.h>#include <strings.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>using namespace std;void Usage(string proc){cout << "\n\rUsage: " << proc << " serverip serverport\n" << endl;}int main(int argc, char* argv[]){if(argc != 3){Usage(argv[0]);exit(0);}string server_ip = argv[1];uint16_t server_port = stoi(argv[2]);sockaddr_in server;bzero(&server, sizeof(server));server.sin_family = AF_INET;server.sin_addr.s_addr = inet_addr(server_ip.c_str());server.sin_port = htons(server_port);socklen_t len = sizeof(server);// client 也需要 bind,只不过不需要用户显示 bind,一般由OS自由随机选择// 系统会在首次发送数据的时候给我们bindint sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd < 0){cout << "socker error" << endl;return 1;}string message;char buffer[1024];while(true){cout << "Plase Enter@ ";getline(cin, message);// 发送数据sendto(sockfd, message.c_str(), message.size(), 0, (sockaddr*)&server, len);// 当服务器进行简单的加工处理后会发送回来,此时客户端再次获取sockaddr_in temp;socklen_t size = sizeof(temp);ssize_t s = recvfrom(sockfd, buffer, 1023, 0, (sockaddr*)&temp, &len);if(s > 0){buffer[s] = 0;cout << buffer << endl;}}close(sockfd);return 0;}
main 函数:
#include <iostream>#include <vector>#include <memory>#include <cstdio>#include "UdpServer.hpp"using namespace std;void Usage(string proc){cout << "\n\rUsage: " << proc << " port[1024+]\n" << endl;}// 处理字符串的方法string Handler(const std::string& str){string res = "Server get a message: ";res += str;cout << res << endl;return res;}// 远程执行指令的方法string ExcuteCommand(const string& cmd){FILE* fp = popen(cmd.c_str(), "r");if(nullptr == fp){perror("popen");return "error";}string result;char buffer[4096];while(true){char* tmp = fgets(buffer, sizeof(buffer), fp);if(tmp == nullptr) break;result = buffer;}pclose(fp);return result;}int main(int argc, char* argv[]){if(argc != 2){Usage(argv[0]);exit(0);}uint16_t port = stoi(argv[1]);unique_ptr<UdpServer> svr(new UdpServer(port, "127.0.0.1"));svr->Init();svr->Run(ExcuteCommand);return 0;}
有关代码中的细节:
- 有关 IP 地址
云服务器禁止直接bind公网ip;bind ip 地址为0,表示的含义是任意地址绑定,这种是比较推荐的做法。当 IP 地址为 127.0.0.1 时,表示进行的是本地传输测试,不会进行跨网传输。
- 有关 port
其中 0~1023 的端口号是系统内定的端口号,一般都要有固定的应用层协议使用,例如 http:80,https:443;所以我们一般绑端口号,一般绑1024以上的。
- popen() 系统调用
popen() 是一个被封装起来的管道和子进程执行命令的应用。
它的第一个参数就是需要执行的命令,在底层它会帮我们进行 fork() 创建子进程,并让父子进程建立管道,然后让子进程把它的运行结果通过管道再返回给调用方。如果调用方想得到 command 指令的运行结果,可以通过文件指针的方式读取。第二个参数相当于是打开这个命令的方式,我们使用 “r” 即可。使用完毕后使用 pclose() 关闭该文件指针即可。
其中,我们可以使用 netstat -nlup
查看系统中所有的 udp 信息,并且把进程信息也显示出来。
我们还可以将以上代码修改成为多线程代码,链接为:多线程UDP.
4. 地址转换函数
(1)相关接口
我们只介绍基于 IPv4 的 socket 网络编程,sockaddr_in 中的成员 struct in_addr sin_addr 表示32位 的 IP 地址,但是我们通常用点分十进制的字符串表示 IP 地址,以下函数可以在字符串表示和 in_addr 表示之间转换。我们在上面的 bind() 中也使用了地址转换函数 inet_addr().
-
字符串转 in_addr 的函数:
#include <arpa/inet.h>int inet_aton(const char* strptr, struct in_addr* addrptr);in_addr_t inet_addr(const char* strptr);int inet_pton(int family, const char* strptr, void* addrptr);
-
in_addr 转字符串的函数:
char* inet_ntoa(struct in_addr inaddr);const char* inet_ntop(int family, const void* addrptr, char* strptr, size_t len);
其中 inet_pton 和 inet_ntop 不仅可以转换 IPv4 的 in_addr,还可以转换 IPv6 的 in6_addr,因此函数接口是 void* addrptr.
(2)关于 inet_ntoa
inet_ntoa 这个函数返回了一个 char*,很显然是这个函数自己在内部为我们申请了一块内存来保存 ip 的结果,那么是否需要调用者手动释放呢?
man 手册上说,inet_ntoa 函数,是把这个返回结果放到了静态存储区。这个时候不需要我们手动进行释放。
5. 编写 TCP 服务器
(1)listen()
TCP 是面向连接的,服务器一般是比较被动的,所以服务器一直处于一种等待连接到来的状态,这个工作叫做监听状态,使用到的接口是 listen()
,如下:
第一个参数为指定的套接字,通过该套接字等待新连接的到来。第二个参数我们后面再介绍,暂时设为10左右即可。返回值,成功返回0,失败返回-1.
(2)accept()
因为 TCP 是面向连接的,所以在正式通信之前,先要把连接建立起来,使用到的接口为 accept()
,该接口的作用是获取一个新的连接,如下:
第一个参数为我们刚刚设置为监听状态的套接字;后两个参数和 recvfrom()
的后两个参数一样,都是输出型参数,也就是谁给我们发的 TCP 报文,那么对应的套接字信息就会通过这两个参数返回出来。
而返回值成功返回一个文件描述符;否则返回-1;那么返回值也是一个文件描述符,我们原本也有一个文件描述符,为什么会有两个 sockfd 呢?我们该用哪个呢?其实它们分工是明确的,我们原本定义的 sockfd,即被创建的,被 bind 的,被监听的套接字,它的工作是从底层获取新的连接;而未来真正提供通信服务的,是 accept() 返回的套接字!
至此,我们可以使用 telnet
进行指定服务的一个远程连接,后面跟上 IP 地址和端口号即可;它在底层默认使用的就是 TCP.
(3)con
由于在 TCP 中,客户端是要连接服务器的,所以服务端需要有一个能够向服务器发起连接的接口,该接口为 connect()
,如下:
该接口的作用是通过指定的套接字,向指定的网络目标地址发起连接。后两个参数和 sendto() 的后两个参数一样。返回值成功返回0,失败返回-1.
TCP 客户端也需要 bind,但是和 UDP 一样,不需要显示的 bind,系统会在客户端发起 connect 的时候,进行自动随机 bind.
我们可以使用 netstat -nltp
查看系统中所有 TCP 的信息,并把进程信息显示出来。
(4)守护进程
在我们登录 Linux 的时候,Linux 系统会给我们形成一个会话,而且会为每个会话创建一个 bash 进程,这个 bash 就可以为用户提供命令行服务。每个会话中只能存在一个前台进程,但是可以存在多个后台进程,而键盘信号只能发送给前台进程。前台和后台进程的区别就是是否拥有键盘文件,它们都可以向显示器打印,而只有前台进程才能从键盘,即标准输入获取数据!
如果我们不想后台进程向显示器打印的数据影响我们,我们可以将它的打印数据重定向到文件中,例如:
其中 [1]
表示后台任务号,后面数字表示进程 PID.
而查看后台任务的指令为:jobs
,如下:
如果我们想把后台进程提到前台,可以使用 fg 任务号
,如下:
如果想把它重新放回后台,我们可以使用 ctrl + z
将该进程暂停。然后使用 bg 任务号
将该进程重新启动,如下:
接下来我们再运行几个后台进程,例如使用 sleep,方便观察 Linux 中的进程间关系,使用 ps axj | head -1 && ps axj | grep -Ei 'a.out|sleep'
查看它们的进程信息:
其中 PPID、PID 我们都认识,而 PGID 表示的是进程组ID,SID 表示 session id,即会话 id.
而系统中可能会存在多个 session,所以系统需要管理多个 session.
我们可以看到,./a.out
进程的 PID 和 PGID 是一样的,所以它就是自成进程组的。而三个 sleep 分别是三个不同的进程,但是它们的 PGID 却是同一个,而且是用管道建立的进程的第一个进程的 PID,所以它们三个自成一组,而组长是多个进程中的第一个。那么进程组和任务有什么关系呢?任务是要指派给进程组的!所以我们需要校正一下以前的说法,我们把前台进程称为前台任务,后台进程称为后台任务,因为可能某一个后台任务里面,可能会包含多个进程。但是无论有几个进程组完成对应的任务,在同一个会话内启动的,SID 是一样的!那么上面中的 SID 到底是谁呢?我们可以查看一下:
如上图,我们可以看到,它是 bash!所以就是以 bash 的 pid 去构建了一个 session!
这种后台进程会收到用户登录和退出的影响,如果我们不想受到任何用户登录和注销的影响,我们可以将进程守护进程化。什么是守护进程呢?我们把自成进程组自成会话的进程称为守护进程!那么我们该如何做到呢?下面我们认识一个接口:setsid()
,如下:
该接口的作用就是,哪个进程调用该接口,就把该进程的组ID设置为会话ID,也就是让进程独立成会话。
返回值成功返回进程的ID,否则返回-1.
注意,该接口不能由进程组的组长直接调用,那么怎么才能保证不是组长调用呢?所以我们可以使用 fork() 创建子进程调用!所以守护进程的本质,也是孤儿进程!
(5)tcp 服务端和客户端
接下来我们结合上面所学的知识,编写一个 TCP 服务器,并将它守护进程化,代码链接:
其中在守护进程中,我们的代码中是充满大量的打印的,而这些打印默认是向标准输出打的,也就是向显示器上打了,而对于守护进程来说,就不应该向显示器上打了,所以我们需要一个解决方案。而 Linux 中存在一种字符文件,叫做 /dev/null
,只要我们向该文件写入,都会被该文件丢弃掉,如果我们向该文件读取,什么也读取不到。所以我们只需要将所有的输出向该文件写入即可。我们也可以将打印信息写入文件中。
另外,TCP 在通信时是全双工的,也就是可以同时读写的。在底层操作系统给 TCP 提供两个缓冲区,一个发送缓冲区,一个接收缓冲区,我们在用 TCP 的同时,别人也在用,所以别人也会有上面两个缓冲区,所以当我们发送数据,是先把我们的数据拷贝到我们的 TCP 的发送缓冲区,然后通过网络会发送到对方的接收缓冲区,反过来也同理,如下图:
相关文章:

【计算机网络】socket 网络套接字
网络套接字 一、端口号1. 认识端口号2. socket 二、认识TCP协议和UDP协议1. TCP协议2. UDP协议 三、网络字节序四、socket 编程1. socket 常见API2. sockaddr 结构3. 编写 UDP 服务器(1)socket()(2)bind()(3࿰…...

Eclipse的Java Project的入口main函数
在使用Eclipse创建java project项目的时候,一个项目里面通常只有一个main,那么一个项目里面是否可以有多个main函数呢?其实可以的,但是运行java application的时候要选择执行哪个main函数。 下面举个例子: 1、创建一个…...

JVM内存分析工具-Arthas 教程[详细]
一、概述 Arthas(阿尔萨斯)是阿里巴巴开源的一款Java诊断工具,用于实时检测、诊断Java应用程序的性能问题。它是一个命令行工具,提供了丰富的功能,包括查看类加载信息、方法执行耗时、线程堆栈、内存分析等。Arthas 的…...

Google发布开放的模型Gemma
今天,Google 发布了一系列最新的开放式大型语言模型 —— Gemma!Google 正在加强其对开源人工智能的支持,我们也非常有幸能够帮助全力支持这次发布,并与 Hugging Face 生态完美集成。 Gemma 提供两种规模的模型: 7B …...

谷歌掀桌子!开源Gemma:可商用,性能超过Llama 2!
2月22日,谷歌在官网宣布,开源大语言模型Gemma。 Gemma与谷歌最新发布的Gemini 使用了同一架构,有20亿、70亿两种参数,每种参数都有预训练和指令调优两个版本。 根据谷歌公布的测试显示,在MMLU、BBH、GSM8K等主流测试…...

http缓存?强制缓存和协商缓存?
HTTP缓存是一种优化网络资源加载速度的技术,通过减少从服务器获取相同资源的次数来实现。HTTP缓存机制包括强制缓存和协商缓存(对比缓存)两种类型。 强制缓存 强制缓存是指浏览器在接收到服务器返回的响应后,会将响应内容和相关…...

技术心得--如何成为优秀的架构师
关注我,持续分享逻辑思维&管理思维; 可提供大厂面试辅导、及定制化求职/在职/管理/技术辅导; 有意找工作的同学,请参考博主的原创:《面试官心得--面试前应该如何准备》,《面试官心得--面试时如何进行自…...

【Unity】【VR开发】Unity云同步功能使用心得
【背景】 有时出差,旅行等等也带着电脑,晚上想要继续编辑项目,就需要用到云同步功能。目前实践下来,发现有些内容可以同步,有些内容则是不可以同步的,总结如下。 【如何云同步一个本地项目】 UnityHub的项目面板中有两个选项卡:项目和云端项目。 鼠标挪动到想要云同步…...

vscode侧边框关掉了怎么打开
View - Appearance - Secondary Side Bar 就可以显示出来了,例如 :(CodeGeeX不显示主界面)...

YOLO-NAS浅析
YOLO-NAS(You Only Look Once - Neural Architecture Search)是一种基于YOLO(You Only Look Once)的目标检测算法,结合神经架构搜索(NAS)技术来优化模型性能。 YOLO是一种实时目标检测算法&…...

LeetCode 2656.K个元素的最大和
给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。你需要执行以下操作 恰好 k 次,最大化你的得分: 从 nums 中选择一个元素 m 。 将选中的元素 m 从数组中删除。 将新元素 m 1 添加到数组中。 你的得分增加 m 。 请你返回执行以上操作恰好 k 次后…...

【最新Dubbo3深入理解】Dubbo3核心Tripple协议详解
欢迎关注公众号(通过文章导读关注:【11来了】),及时收到 AI 前沿项目工具及新技术的推送! 在我后台回复 「资料」 可领取编程高频电子书! 在我后台回复「面试」可领取硬核面试笔记! 文章导读地址…...

神秘人暗访:行政窗口为什么要开展神秘顾客调研
在竞争日益激烈的服务市场中,行政窗口作为公共服务的直接提供者,其服务质量的好坏直接关系到政府的形象和公众对政府的信任度。为了更好地满足市民的需求,提升服务质量,开展神秘顾客调查显得尤为重要。神秘顾客调查的必要性包括以…...

Spring之AOP
文章目录 初步实现通知执行顺序 各个通知获取细节信息重用切点表达式切点表达式语法细节环绕增强切面的优先级没有接口的情况基于XML的AOP[了解] 初步实现 先导入Spring和Junit4的依赖 <dependency><groupId>org.springframework</groupId><artifactId&g…...

Git详解及 github与gitlab使用
目录 1.1 关于版本控制 1.1.1 本地版本控制 1.1.2 集中化的版本控制系统 1.1.3 分布式版本控制系统 1.2 Git简介 1.2.1 Git历史 1.3 安装git 1.3.1 环境说明 1.3.2 Yum安装Git 1.3.3 编译安装 1.4 初次运行 Git 前的配置 1.4.1 配置git 1.4.2 获取帮助 1.5 获取 G…...
政安晨:【完全零基础】认知人工智能(二)【超级简单】的【机器学习神经网络】—— 底层算法
如果小伙伴第一次看到这篇文章,可以先浏览一下我这个系列的上一篇文章: 政安晨:【完全零基础】认知人工智能(一)【超级简单】的【机器学习神经网络】 —— 预测机https://blog.csdn.net/snowdenkeke/article/details/…...

基于springboot+vue的美发门店管理系统(前后端分离)
博主主页:猫头鹰源码 博主简介:Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战,欢迎高校老师\讲师\同行交流合作 主要内容:毕业设计(Javaweb项目|小程序|Pyt…...

C语言第二十八弹---整数在内存中的存储
✨个人主页: 熬夜学编程的小林 💗系列专栏: 【C语言详解】 【数据结构详解】 目录 1、整数在内存中的存储 2、大小端字节序和字节序 2.1、什么是大小端? 2.2、为什么有大小端? 2.3、练习 2.3.1、练习1 2.3.2、练习2 2.…...

java开源xml工具类介绍
在Java中处理XML的常用开源工具有很多,以下是一些流行的库以及简单的示例代码: DOM4J DOM4J 是一个非常流行的Java库,用于处理XML,DOM4J 易于使用,并且提供了很好的性能。 Maven 依赖 …...

Go 语言一些常用语法编写和优化指南
Go 语言以其简洁的语法和强大的并发性能而受到开发者的喜爱。然而,为了充分利用 Go 的潜力,我们需要了解如何优化 Go 程序。本文将介绍一些常见的 Go 语言优化技巧,并通过实际例子进行说明。 推荐系列 来来来,老铁们,男人女人都需要的技术活…...

Golang 语法系列:结构体
结构体:相当于"类" 1.结构体声明 type [name] struct {[field_name] [field_type][field_name] [field_type]... }//例子:type Person struct {name stringage int }其中field_name可以省略 2.结构体的使用 1) 格式1 var person Person p…...

关于iPad中的密码和触控ID的使用,看这篇文章就差不多了
序言 许多苹果iPad型号都有熟悉的密码系统和触控ID,这需要指纹扫描才能解锁设备。本指南向你展示如何使用iPad Air 2或更高版本、iPad Mini 3或更新版本以及iPad Pro设置或更改密码和触控ID指纹。 一些iPad Pro型号支持面部识别,并配备了面容ID而不是触控ID作为安全功能。面…...

Vue3之ref与reactive的基本使用
ref可以创建基本类型、对象类型的响应式数据 reactive只可以创建对象类型的响应式数据 接下来让我为大家介绍一下吧! 在Vue3中,我们想让数据变成响应式数据,我们需要借助到ref与reactive 先为大家介绍一下ref如何使用还有什么注意点 我们需…...

wsl内置Ubuntu使用 Dinky 与 Flink 集成
Dinky 与 Flink 集成 说明 本文档介绍 Dinky 与 Flink 集成的使用方法, 如果您是 Dinky 的新用户, 请先阅读 本文档, 以便更好的搭建 Dinky 环境 如果您已经熟悉 Dinky 并已经部署了 Dinky, 请跳过本文档的前置要求部分, 直接阅读 Dinky 与 Flink 集成部分 注意: 本文档基…...

”戏说“ 交换机 与 路由器
一般意义上说 老哥 这文章发表 的 东一榔头 西一锤 呵呵, 想到哪里就啰嗦到哪里 。 交换机: 其实就是在通道交换 路由器: 不光是在通道交换还要在协议上交换 下图你看懂了吗? (仅仅数据交换-交换机 协议…...

Linux pageset
1. 引言 在用户进程发生缺页异常时,Linux内核需要分配所需物理页面以及建立也表映射,来维持进程的正常内存使用需求。而对于分配物理页面仅依赖于buddy系统,对于小order页面的分配效率较低。因此Linux通过在每个cpu维护一个page链表ÿ…...

【C++之语法篇003】
C学习笔记---003 C知识开篇1、内联函数1.1、什么是内联函数?1.2、解决外部头文件,重复定义问题1.3、内联函数的总结 2、auto关键字2.1、auto的作用2.2、auto的总结 3、范围for3.1、什么是范围for?3.2、范围for的循环应用 4、指针空值关键字nullptr4.1、…...

Github代码仓库SSH配置流程
作者: Herman Ye Auromix 测试环境: Ubuntu20.04 更新日期: 2024/02/21 注1: Auromix 是一个机器人爱好者开源组织。 注2: 由于笔者水平有限,以下内容可能存在事实性错误。 相关背景 在为Github代码仓库配…...

Arrays工具类的常见方法总结
一、Arrays.asList( ) 1.作用:Arrays.asList( )方法的作用是将数组转换成List,将List中的全部集合对象添加至ArrayList集合中 2.参数:动态参数 (T... a) 3.返回值:List 集合 List<T> 4.举例: package com…...

物联网和人工智能的融合
物联网和人工智能的融合 1. 物联网和人工智能的融合2. 芯片技术的进步3. 安全和隐私保护挑战4. 软件开发和调试技术的创新5. 自动化和智能化趋势 1. 物联网和人工智能的融合 随着物联网和人工智能技术的快速发展,嵌入式系统将更多地与物联网设备和人工智能算法相结…...