当前位置: 首页 > news >正文

Linux网络 UDP socket

背景知识

我们知道, IP 地址用来标识互联网中唯一的一台主机, port 用来标识该主机上唯一的一个网络进程,IP+Port 就能表示互联网中唯一的一个进程。所以通信的时候,本质是两个互联网进程代表人来进行通信,{srcIp,srcPort, dstIp dstPort} 这样的 4 元组就能标识互联网中唯二的两个进程。所以,网络通信的本质,也是进程间通信,我们把 ip+port 叫做套接字 socket。
socket
n.(电源 ) 插座; ( 电器上的 ) 插口,插孔,管座;槽;窝;托座;臼;孔穴
vt.把… 装入插座;给 配插座
套接字(socket)是一种通信机制,它提供了一种在网络上进行进程间通信的方法。套接字可以被看作是网络通信的端点,它允许不同主机上的进程通过网络进行通信。套接字屏蔽了各个协议的通信细节,提供了TCP/IP协议的抽象,对外提供了一套接口,通过这个接口就可以统一、方便地使用TCP/IP协议的功能。
传输层的典型代表
如果我们了解系统,也了解网络协议栈,我们就会清楚,传输层是属于内核的,那么我们要通过网络协议栈进行通信,必定调用的是传输层提供的系统调用来进行的网络通信。
TCP 协议
我们先对 TCP(Transmission Control Protocol 传输控制协议 ) 有一个直观的认识
  • 传输层协议
  • 有连接
  • 可靠传输
  • 面向字节流
UDP 协议
我们也对 UDP(User Datagram Protocol 用户数据报协议 ) 有一个直观的认识
  • 传输层协议
  • 无连接
  • 不可靠传输
  • 面向数据报

sockaddr 结构

套接字有很多类型,主要分为以下几种

  • Unix Socket域套接字:用于本地通信,通常用于同一台机器上的不同进程间通信。
  • Inet Socket网络套接字:用于网络通信,支持多种协议,如TCP和UDP。
  • Raw Socket原始套接字:用于网络管理和底层网络编程。

sockaddr 结构是在网络编程中用于表示套接字地址的通用数据结构, 它的作用是存储网络地址信息,供套接字函数使用,此时套接字函数就知道要对哪一台主机进行网络操作,它被设计为一个抽象层,允许应用程序通过同一接口处理不同类型的网络协议和地址族。

但是sockaddr结构体不能直接存储 IPv4 或 IPv6 的地址信息,在实际使用中,通常会用到它的具体子类型,如 sockaddr_in(用于 IPv4)和 sockaddr_in6(用于 IPv6),sockaddr_un(用于域套接)。

为了管理多种套接字,所有套接字的头部都是一个16位的地址类型,用于辨别这个结构体表示哪一个套接字。当操作sockaddr的时候,读取前16位就知道这个sockaddr具体是哪一种套接字,随后再进行类型转化,变成对应套接字类型的结构体,此时就能对具体的套接字做操作了。

socket API 可以都用 struct sockaddr * 类型表示 , 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性 , 可以接收 IPv4, IPv6, 以及 UNIX Domain Socket 各种类型的 sockaddr 结构体指针做为参数 ;

sockaddr 结构体定义在 <sys/socket.h> 头文件中,其基本定义如下:

struct sockaddr {sa_family_t sin_family; /* 地址家族,AF_XXX */char sin_zero[14]; /* 填充字段,实际用途取决于具体的地址家族 */
};

其中,sin_family 字段用来指定协议族,即协议类型,常见的取值有 AF_INET(IPv4)、AF_INET6(IPv6)和 AF_UNIX(UNIX 域套接字)等。

其中最常用的就是 AF_INET 进行IPv4通信。其对应的具体结构体为struct sockaddr_in,定义如下:

struct sockaddr_in {sa_family_t sin_family; /* 协议族,AF_INET */uint16_t sin_port; /* TCP 或 UDP 端口号 */struct in_addr sin_addr; /* 32 位 IPv4 地址 */
};

此处有一个小细节,IPv4的地址占32位,用一个int类型即可存储,sin_addr的类型却是struct in_addr,这其实是Linux对其进行了额外的一层封装:

struct in_addr {uint32_t s_addr;
};

所以存储地址的时候,要用sockaddr_in.sin_addr.s_addr,此处嵌套了两层结构体。基于IP地址和端口号,此时就可以定位到全世界的一个主机上的一个具体进程,此时就可以进行后续的网络通信了。

bzero

我们知道struct sockaddr_in 的内部还有8字节填充,这是为了以确保struct sockaddr_in的大小与struct sockaddr一致,所以我们需要一开始时将其初始化为0。除此,创建结构体时分配到的内存原先有可能存储了其他数据,为了保证不被之前的数据影响,我们也要把整个结构体的内存全部置为0

所以我们可以使用bzero函数。

void bzero(void* s, size_t n);
  • s:要初始化内存的地址
  • n:要初始化的字节数

示例如下

struct sockaddr_in socket;
bzero(&socket,sizeof(socket));

网络字节序(填sin_port)

我们知道 , 内存中的多字节数据相对于内存地址有大端和小端之分 , 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分。 那么如何定义网络数据流的地址呢 ?
发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出,接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存,因此, 网络数据流的地址应这样规定 : 先发出的数据是低地址 , 后发出的数据是高地址. TCP/IP 协议规定 , 网络数据流应采用大端字节序 , 即低地址高字节 .
不管这台主机是大端机还是小端机, 都会按照这个 TCP/IP 规定的网络字节序来发送/ 接收数据,如果当前发送主机是小端, 就需要先将数据转成大端 ; 否则就忽略 , 直接发送即可。
为使网络程序具有可移植性 , 使同样的 C 代码在大端和小端计算机上编译后都能正常运行, 可以调用以下库函数做网络字节序和主机字节序的转换,使用以下函数需要包含头文件<arpa/inet.h>。
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
这些函数名很好记 ,h 表示 host,n 表示 network,l 表示 32 位长整数 ,s 表示 16 位短整数。例如 htonl 表示将 32 位的长整数从主机字节序转换为网络字节序 , 例如将 IP 地址转换后准备发送。如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;如果主机是大端字节序,这些函数不做转换 , 将参数原封不动地返回。
假设我们有一个类型为 struct sockaddr_in 的套接字socket,在填写内部端口号时,内部数据的字节序就要使用网络字节序。因为 sin_port 类型为 uint16_t,所以使用 htons 函数。
struct sockaddr_in socket;
socket.sin_port=8080;//错误
socket.sin_port=htons(8080);//正确

IP地址转换(填sin_addr)

在给 struct sockaddr_in 结构体填入数据时,其IP地址 sin_addr 的格式也需要遵循特定的规则。我们知道,IP地址有两种基本格式,4字节序列,以及点分十进制,如果拿到的IP地址格式与自己所需的类型不符,此时就要考虑两种格式之间转化的问题了。

inet_addr函数用于将一个点分十进制的IP地址字符串转换为网络字节序的32位整数。

in_addr_t inet_addr(const char *cp);

参数cp是一个指向以点分十进制格式表示的IP地址字符串的指针,例如"127.0.0.1"。函数返回一个32位的无符号整数,表示转换后的IP地址。如果输入的字符串不是一个合法的IP地址,函数将返回INADDR_NONE,通常定义为-1。

示例如下

struct sockaddr_in socket;
socket.sin_addr.s_addr = inet_addr("127.0.0.1");

我们知道存入 struct sockaddr_in 中的数据必须是网络字节序,此处将点分十进制转化为四字节序列后,应该还需要转成网络字节序。的确如此,不过我们不需要手动转换,因为 inet_addr 函数已经帮我们完成转换。

inet_ntoa函数用于将一个网络字节序的32位整数IP地址转换为点分十进制的字符串格式。这个函数的原型如下

char *inet_ntoa(struct in_addr in);

参数in是一个struct in_addr类型的结构体,其中包含了一个32位的无符号整数,表示IP地址。inet_ntoa函数返回一个指向静态存储区的字符串,该字符串包含了以点分十进制格式表示的IP地址。由于返回的字符串存储在静态存储区,因此在多线程环境中可能会出现问题,因为后续的调用可能会覆盖之前的结果,所以在多线程环境下推荐使用ntop函数。

char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

参数:

  • af:协议族,可以是AF_INET(IPv4)或AF_INET6(IPv6)。
  • src:指向要转换的网络字节序IP地址的指针。
  • dst:指向存储转换后字符串的缓冲区的指针。
  • size:缓冲区的大小。

返回值:成功时,返回指向dst的非空指针。失败时,返回NULL,并且可以通过errno获取错误码。

综上,我们就有一个类型为 struct sockaddr_in 比较完整的初始化过程了:

struct sockaddr_in socket;
bzero(&socket,sizeof(socket));
socket.sin_family=AF_INET;
socket.sin_port=htons(8080);
socket.sin_addr.s_addr=inet_addr("127.0.0.1");

UDP socket

UDP(User Datagram Protocol)套接字是一种网络通信协议,它提供了一种无连接、不可靠的传输服务。UDP套接字通常用于需要快速传输和实时响应的应用场景,如在线游戏、视频会议和实时监控等。

UDP套接字的特点

  1. 无连接性:UDP不需要在发送数据之前建立连接,因此减少了通信延迟。
  2. 不可靠性:UDP不提供数据传输的可靠性保证,数据包可能会丢失或乱序到达。
  3. 面向数据报:UDP以数据报为单位进行传输,每个数据报都是独立的。
  4. 全双工:UDP支持双向通信,允许同时进行数据的发送和接收。

socket 创建套接字

socket函数用于创建一个新的套接字,需要头文件<sys/types.h><sys/socket.h>,函数原型如下:

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

参数:

  • domain:指定协议族,对于UDP套接字,通常使用AF_INET(IPv4)或者AF_INET6(IPv6)。
  • type:指定套接字类型,创建UDP套接字时使用SOCK_DGRAM,DGRAMdatagram缩写,即数据报。
  • protocol:指定协议类型,一般设置为0,表示根据前面两个参数自动选择合适的协议(对于AF_INETSOCK_DGRAM,会自动选择UDP协议)。

返回值:如果成功创建套接字,返回一个非负的套接字描述符,其本质也是一个文件描述符,后续对网络的操作就是对这个文件的操作。比如向网络中发送消息,其实就是向文件中写入数据;如果失败,返回 - 1,并设置errno来指示错误类型。

使用示例如下

int sockfd=socket(AF_INET,SOCK_DGRAM,0);

bind 绑定地址

当创建完套接字后,这个套接字还没有指定和哪一个主机通信,此时就需要IP地址和端口号,之前讲的sockaddr_in就派上用场了!bind函数用于给套接字绑定IP地址和端口号,指定和哪一台主机通信,函数原型如下:

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

参数:

  • sockfd:由socket()函数返回的套接字描述符。
  • addr:一个指向sockaddr结构(或者sockaddr_in对于IPv4或者sockaddr_in6对于IPv6)的指针,包含了要绑定的地址和端口信息。
  • addrlen:是addr所指向结构的长度。

返回值:如果绑定成功,返回0;如果失败,返回 - 1,并设置errno来指示错误类型。

此处注意传入的是struct sockaddr *,所以sockaddr_in类型的变量传入的时候要进行类型转化。

//创建UDP套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);//初始化套接字要通信的目标主机地址
struct sockaddr_in socket;
bzero(&socket, sizeof(socket));
socket.sin_family = AF_INET;
socket.sin_port = htons(8080);
socket.sin_addr.s_addr = inet_addr("127.0.0.1");//绑定地址到套接字
bind(sockfd, (struct sockaddr*)&socket, sizeof(socket));

sendto 发送数据

sendto函数用于发送数据报,函数原型如下:

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

参数:

  • sockfd:是要发送数据的套接字描述符。
  • buf:是一个指向要发送数据的缓冲区的指针。
  • len:是要发送数据的长度(以字节为单位)。
  • flags:一般设置为0,或者可以使用一些特定的标志(如MSG_DONTWAIT等)。
  • dest_addr:是一个指向sockaddr结构(或者sockaddr_in对于IPv4或者sockaddr_in6对于IPv6)的指针,包含了目标地址和端口信息。
  • addrlen:是dest_addr所指向结构的长度。

返回值:如果成功发送数据,返回实际发送的字节数;如果失败,返回 - 1,并设置errno来指示错误类型。

//创建UDP套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);//初始化套接字要通信的目标主机地址
struct sockaddr_in socket;
bzero(&socket, sizeof(socket));
socket.sin_family = AF_INET;
socket.sin_port = htons(8080);
socket.sin_addr.s_addr = inet_addr("127.0.0.1");//给目标主机发送消息
const char* message = "hello";
sendto(sockfd,message,sizeof(message),(struct sockaddr*)&socket,sizeof(socket));

此处给地址为127.0.0.1端口为8080发送了一个报文,内容是”hello“。
我们可以看到以上代码中没有bind绑定地址,因为该操作已经由操作系统自动完成了,Linux会自动为其分配端口号,并完成绑定,随后通过随机分配的端口发送数据,这种行为称为隐式绑定。在实际开发中,一般服务端占用指定的端口,这样客户端才知道往哪一个端口发送请求,所以服务端要显式bind指定端口,不能让操作系统分配。而客户端往往不在意端口号,只需要能与服务端通信即可,所以客户端一般不bind,而是让系统随机分配一个端口。

recvfrom 接收数据

在Linux系统下,recvfrom函数用于在UDP套接字上接收数据,其函数原型如下:

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

参数:

  • sockfd:是要接收数据的套接字描述符。
  • buf:是一个指向用于接收数据的缓冲区的指针。
  • len:是缓冲区的长度(以字节为单位)。
  • flags:一般设置为0,或者可以使用一些特定的标志(如MSG_DONTWAIT等)。
  • src_addr:是一个指向sockaddr结构(或者sockaddr_in对于IPv4或者sockaddr_in6对于IPv6)的指针,如果不为NULL,则用于存储发送方的地址和端口信息。
  • addrlen:是一个指向socklen_t类型的指针,如果src_addr不为NULL,则在函数调用前,*addrlen应设置为src_addr所指向结构的长度;函数返回时,*addrlen被更新为实际存储发送方地址信息的结构的长度。

返回值:如果成功接收数据,返回实际接收的字节数;如果失败,返回 - 1,并设置errno来指示错误类型。

使用示例

//创建UDP套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);//初始化套接字要通信的目标主机地址
struct sockaddr_in socket;
bzero(&socket, sizeof(socket));
socket.sin_family = AF_INET;
socket.sin_port = htons(8080);
socket.sin_addr.s_addr = inet_addr("127.0.0.1");//接收消息
struct sockaddr_in sendsock;
socklen_t len;
char* buf[1024];
recvfrom(sockfd,buf,sizeof(buf)-1,(struct sockaddr*)&sendsock,sizeof(len));

close 关闭套接字

在Linux系统下,close函数用于关闭文件,我们知道实际上在网络中通信其实也是对文件进行操作,所以通信结束后我们需要关闭套接字,其函数原型如下:

int close(int fd);

参数:fd:是要关闭的套接字描述符(也就是由socket函数创建的套接字描述符)。

返回值:如果关闭成功,返回0;如果失败,返回 - 1,并设置errno来指示错误类型。

案例:echosever

简单的回显服务器和客户端代码
makefile
.PHONY:all
all:server client
server:UdpServermain.ccg++ -o $@ $^ -std=c++17
client:UdpClientmain.ccg++ -o $@ $^ -std=c++17
.PHONY:clean
clean:rm -f server client

UdpServer.hpp

#include "common.hpp"const uint16_t default_port = 8080;
class UdpServer
{
public:UdpServer(uint16_t port = default_port): _port(port), _sockfd(-1){// 创建socket_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){cout << "create socket error" << endl;exit(SOCKET_ERROR);}// 将socket绑定到ip和端口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());//云服务器不允许直接 bind 公有IP,我们也不推荐编写服务器的时候,bind 明确的 IP,推荐直接写成 INADDR_ANY//在网络编程中,当一个进程需要绑定一个网络端口以进行通信时,可以使用INADDR_ANY 作为 IP 地址参数。//这样做意味着该端口可以接受来自任何 IP 地址的连接请求,无论是本地主机还是远程主机。例如,如果服务//器有多个网卡(每个网卡上有不同的 IP 地址),使用 INADDR_ANY 可以省去确定数据是从服务器上具体哪个//网卡/IP 地址上面获取的。local.sin_addr.s_addr = INADDR_ANY;int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));if (n < 0){cout << "bind socket error" << endl;exit(BIND_ERROR);}}~UdpServer(){if (_sockfd > 0)close(_sockfd);_sockfd = -1;cout << "socket closed" << endl;}void start(){// 循环接收数据while (true){struct sockaddr_in from;socklen_t len = sizeof(from);char buf[1024];int n = recvfrom(_sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr *)&from, &len);if (n > 0){buf[n] = 0;string ip = inet_ntoa(from.sin_addr);int port = ntohs(from.sin_port);cout << "receive from [" << ip << ":" << port << "]#" << buf << endl;sendto(_sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&from, len);}}}private:uint16_t _port;int _sockfd;
};

UdpServermain.cc

#include "UdpServer.hpp"
#include <iostream>
using namespace std;
//./server localport
int main(int argc, char *argv[])
{if (argc != 2){cout << "Usage:./server localport" << endl;return Usage_ERROR;}int port = stoi(argv[1]);UdpServer server(port);server.start();return 0;
}

UdpClientmain.cc

#include "common.hpp"
//./client server_ip server_port
int main(int argc, char *argv[])
{if (argc != 3){cout << "Usage: " << argv[0] << " sever_ip sever_port" << endl;return Usage_ERROR;}// 创建socketint sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){cout << "create socket error" << endl;exit(SOCKET_ERROR);}// 填充一下 server 信息string serverip = argv[1];int serverport = stoi(argv[2]);struct sockaddr_in serveraddr;bzero(&serveraddr, sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(serverport);serveraddr.sin_addr.s_addr = inet_addr(serverip.c_str());// client 要不要进行 bind? 一定要 bind 的!!// 但是不需要显示 bind,client 会在首次发送数据的时候会自动进行bind// 为什么?server 端的端口号,一定是众所周知,不可改变的,client非常多,需要 bind 随机端口.while (true){cout << "please input message:";string message;getline(cin, message);sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&serveraddr, sizeof(serveraddr));char buf[1024];struct sockaddr_in tmp;socklen_t len = sizeof(tmp);int n = recvfrom(sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr *)&tmp, &len);if (n > 0){buf[n] = 0;cout << "server say:" << buf << endl;}elsebreak;}return 0;
}

以上为Linux版本,Windows版本如下:

#include <iostream>
#include <cstdio>
#include <thread>
#include <string>
#include <cstdlib>
#include <WinSock2.h>
#include <Windows.h>
#pragma warning(disable : 4996)
#pragma comment(lib, "ws2_32.lib")
using namespace std;
string serverip = "110.41.138.70";// 填写云服务器ip
int serverport = 8080;// 填写云服务开放的端口
int main( )
{WSADATA wsa;WSAStartup(MAKEWORD(2, 2), &wsa);//创建socketint sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){cout << "create socket error" << endl;return 1;}//填充server信息struct sockaddr_in serveraddr;memset(&serveraddr, sizeof(serveraddr),0);serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(serverport);serveraddr.sin_addr.s_addr = inet_addr(serverip.c_str());while (true){cout << "please input message:";string message;getline(cin, message);sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&serveraddr, sizeof(serveraddr));char buf[1024];struct sockaddr_in tmp;int len = sizeof(tmp);int n = recvfrom(sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&tmp, &len);if (n > 0){buf[n] = 0;cout << "server say:" << buf << endl;}}closesocket(sockfd);WSACleanup();return 0;
}
WinSock2.h Windows Sockets API (应用程序接口)的头文件,用于在 Windows 平台上进行网络编程。它包含了 Windows Sockets 2 Winsock2 )所需的数据类型、函数声明和结构定义,使得开发者能够创建和使用套接字(sockets )进行网络通信。
在编写使用 Winsock2 的程序时,需要在源文件中包含 WinSock2.h 头文件。这样,编译器就能够识别并理解 Winsock2 中定义的数据类型和函数,从而能够正确地编译和链接网络相关的代码。此外,与 WinSock2.h 头文件相对应的是 ws2_32.lib 库文件。在链接阶段,需要将这个库文件链接到程序中,以确保运行时能够找到并调用 Winsock2 API 中实现的函数。
WinSock2.h 中定义了一些重要的数据类型和函数,如:
  • WSADATA:保存初始化 Winsock 库时返回的信息。
  • SOCKET:表示一个套接字描述符,用于在网络中唯一标识一个套接字。
  • sockaddr_inIPv4 地址结构体,用于存储 IP 地址和端口号等信息。
  • socket():创建一个新的套接字。
  • bind():将套接字与本地地址绑定。
  • listen():将套接字设置为监听模式,等待客户端的连接请求。
  • accept():接受客户端的连接请求,并返回一个新的套接字描述符,用于与客户端进行通信。
WSAStartup 函数是 Windows Sockets API 的初始化函数,它用于初始化 Winsock 库。该函数在应用程序或 DLL 调用任何 Windows 套接字函数之前必须首先执行,它扮演着初始化的角色。
以下是 WSAStartup 函数的一些关键点:
它接受两个参数: wVersionRequested lpWSAData wVersionRequested 用于指定所请求的 Winsock 版本,通常使用 MAKEWORD(major, minor) 宏,其中 major 和 minor 分别表示请求的主版本号和次版本号。 lpWSAData 是一个指向 WSADATA 结构的指针,用于接收初始化信息。
如果函数调用成功,它会返回 0 ;否则,返回错误代码。
WSAStartup 函数的主要作用是向操作系统说明我们将使用哪个版本的 Winsock 库,从而使得该库文件能与当前的操作系统协同工作。成功调用该函数后,Winsock 库的状态会被初始化,应用程序就可以使用 Winsock 提供的一系列套接字服务,如地址家族识别、地址转换、名字查询和连接控制等。这些服务使得应用程序可以与底层的网络协议栈进行交互,实现网络通信。在调用 WSAStartup 函数后,如果应用程序完成了对请求的 Socket 库的使用,应调用WSACleanup 函数来解除与 Socket 库的绑定并释放所占用的系统资源。

相关文章:

Linux网络 UDP socket

背景知识 我们知道&#xff0c; IP 地址用来标识互联网中唯一的一台主机&#xff0c; port 用来标识该主机上唯一的一个网络进程&#xff0c;IPPort 就能表示互联网中唯一的一个进程。所以通信的时候&#xff0c;本质是两个互联网进程代表人来进行通信&#xff0c;{srcIp&…...

如何持续优化呼叫中心大模型呼入机器人的性能?

如何持续优化呼叫中心大模型呼入机器人的性能&#xff1f; 原作者&#xff1a;开源呼叫中心FreeIPCC&#xff0c;其Github&#xff1a;https://github.com/lihaiya/freeipcc 持续优化呼叫中心大模型呼入机器人的性能是一个复杂而细致的过程&#xff0c;它涉及到数据、模型结构…...

鸿蒙项目云捐助第四讲鸿蒙App应用的登陆注册页实现

根据app的操作流程可以知道&#xff0c;当启动页启动后&#xff0c;点击启动页中的页面就进入到了登录页。本讲就是针对于登录注册页的实现&#xff0c;实现的界面参考下图。 这里根据这个素材的参考实现鸿蒙Next云捐助的登录页。 一、鸿蒙Next云捐助登录页的实现 在项目中继…...

Windows本地搭建Redis集群(集群模式)

手打不易&#xff0c;如果转摘&#xff0c;请注明出处&#xff01; 注明原文&#xff1a;https://blog.csdn.net/q258523454/article/details/144477957 前言 Redis版本&#xff1a;redis 5.0.14.1 Windows版本&#xff1a;Windows10 本文只讲集群模式 1. 安装Redis 1.1 …...

使用FastGPT制做一个AI网站日志分析器

越来越的多网站面临每天上千次的扫描和各类攻击&#xff0c;及时发现攻击IP&#xff0c;并有效的屏蔽不良访问成为网站安全的重要保障&#xff0c;这里我们使用AI来完成对网站日志的日常分析。 我们来使用FastGPT来制做一个AI网站日志析器&#xff0c;下面就开始&#xff1a; …...

探索 Echarts 绘图:数据可视化的奇妙之旅

目录 一、Echarts 初印象 二、搭建 Echarts 绘图环境 三、绘制第一个图表&#xff1a;柱状图的诞生 四、图表的美化与定制&#xff1a;让数据更具吸引力 1. 主题切换&#xff1a;一键变换风格 2. 颜色调整&#xff1a;色彩搭配的艺术 3. 标签与提示框&#xff1a;丰富信…...

网络基础(IP和端口)

网络连接的核心-TCP/IP体系结构&#xff08;IP和端口&#xff09; 什么是IP地址 1.IP地址是电子设备&#xff08;计算机&#xff09;在互联网上的唯一标识 2.用来在互联网中寻找电脑 IP 地址就像是你家的地址一样&#xff0c;不过它是在网络世界里用来找到一台电脑或者其他网…...

UE4与WEB-UI通信

前端HTML代码 <!DOCTYPE html><html><head><meta charset"utf-8"><meta name"viewport" content"widthdevice-width, initial-scale1"><title>test web ui</title><script src"https://cdn.b…...

前缀和与差分算法详解

定义 前缀和是一种数据预处理技术&#xff0c;它指的是从数组的第一个元素开始&#xff0c;到当前元素为止的所有元素的和。这种技术可以快速计算任意区间内元素的和&#xff0c;而不需要每次都从头开始累加。 差分则是前缀和的逆运算&#xff0c;它主要用于处理对数组某个区…...

《深入探究:C++ 在多方面对 C 语言实现的优化》

目录 一、C 在 C 上进行的优化二、C 关键字&#xff08;C 98&#xff09;三、C 的输入输出1. cin 和 cout 的使用2. cin、cout 和 scanf()、printf() 的区别 三、命名空间1. 命名空间的使用2. 嵌套命名空间3. 在多个头文件中使用相同的命名空间 四、函数缺省值1. 缺省值的使用2…...

React 第十六节 useCallback 使用详解注意事项

useCallback 概述 1、useCallback 是在React 中多次渲染缓存函数的 Hook&#xff0c;返回一个函数的 memoized的值&#xff1b; 2、如果多次传入的依赖项不变&#xff0c;那么多次定义的时候&#xff0c;返回的值是相同的,防止频繁触发更新&#xff1b; 3、多应用在 父组件为函…...

使用C#和OPenCV实现圆形检测

文章目录 霍夫变换使用 OpenCV 和 C# 实现圆形检测 霍夫变换 在计算机视觉中&#xff0c;圆形检测是一个常见且有用的任务&#xff0c;特别是在物体识别、图像分析和图形处理等领域。OpenCV 是一个强大的开源计算机视觉库&#xff0c;它提供了许多工具来实现不同的图像处理功能…...

评估一套呼叫中心大模型呼入机器人的投入回报比?

评估一套呼叫中心大模型呼入机器人的投入回报比&#xff1f; 原作者&#xff1a;开源呼叫中心FreeIPCC&#xff0c;其Github&#xff1a;https://github.com/lihaiya/freeipcc 评估一套呼叫中心大模型呼入机器人的投入回报比&#xff08;ROI&#xff09;&#xff0c;是一个多…...

十八、Label 和 Selector

Label 是键值对,用来标识 Kubernetes 资源(如 Pod、Node、Service 等)的属性。它们并不直接影响资源的行为,但可以帮助用户快速组织、查询和操作这些资源。标签可以用于选择、过滤和分组。 Label: 标签对 k8s 中各种资源进行分类、分组,如Pod和节点进行分组。通过添加kev…...

实现按键按下(低电平)检测到下降沿

按照流程进行编程 步骤1&#xff1a; 初始化函数 包括时基工作参数配置 输入通道配置 更新中断使能 使能捕获、捕获中断及计数器 HAL_TIM_IC_Init(&ic_handle) //时基参数配置 HAL_TIM_IC_ConfigChannel(&ic_handle,&ic_config,TIM_CHANNEL_2) //输…...

解析 SSM 垃圾分类系统,助力生态平衡

前 言 垃圾分类系统&#xff0c;传统的垃圾分类系统模式还处于线下管理阶段&#xff0c;管理效率极低。随着垃圾分类系统信息的不断增多&#xff0c;传统基于线下管理模式已经无法满足当前用户需求&#xff0c;随着信息化时代的到来。通过该系统的设计&#xff0c;管理员可以管…...

软件工程 设计的复杂性

复杂性代表事件或事物的状态&#xff0c;它们具有多个相互关联的链接和高度复杂的结构。在软件编程中&#xff0c;随着软件设计的实现&#xff0c;元素的数量以及它们之间的相互联系逐渐变得庞大&#xff0c;一下子变得难以理解。 如果不使用复杂性指标和度量&#xff0c;软件…...

Nginx 限制只能白名单 uri 请求的配置

实际生产项目中&#xff0c;大多数时候我们会将后端的 http 接口通过前置 nginx 进行反向代理&#xff0c;对互联网用户提供服务。往往我们后端服务所能提供的接口服务是大于互联网用户侧的实际请求的接口地址数量的&#xff08;例如后端服务一共有100个api接口&#xff0c;经过…...

QT c++ 同时使用sqlite 和mysql数据库的问题

在项目开发中&#xff0c;同时使用了sqlite 和mysql数据库&#xff0c;分开这两部分运行功能都正常&#xff0c;但是一起运行&#xff0c;就异常&#xff0c;sqlite部分不能使用。 现象&#xff1a;出现如下提示 QSqlDatabasePrivate::addDatabase: duplicate connection nam…...

redis集群 服务器更换ip,怎么办,怎么更换redis集群的ip

redis集群 服务器更换ip&#xff0c;怎么办&#xff0c;怎么更换redis集群的ip 1、安装redis三主三从集群2、正常状态的redis集群3、更改redis集群服务器的ip 重启服务器 集群会down4、更改redis集群服务器的ip 重启服务器 集群down的原因5、更改redis集群服务器的ip后&#xf…...

【C++习题】19.数组中第K个大的元素

题目&#xff1a;数组中第K个大的元素 链接&#x1f517;&#xff1a;数组中第K个大的元素 题目&#xff1a; 代码&#xff1a; class Solution { public:int findKthLargest(vector<int>& nums, int k) {// 将数组中的元素先放入优先级队列中priority_queue<i…...

JIS-CTF: VulnUpload靶场渗透

JIS-CTF: VulnUpload来自 <https://www.vulnhub.com/entry/jis-ctf-vulnupload,228/> 1,将两台虚拟机网络连接都改为NAT模式 2&#xff0c;攻击机上做namp局域网扫描发现靶机 nmap -sn 192.168.23.0/24 靶机IP地址192.168.23.162&#xff0c;攻击机IP地址192.168.23.140…...

BGP-面试

简单介绍一下BGP BGP&#xff0c;边界网关协议&#xff0c;属于路径矢量路由协议。属于触发式更新或者增量更新。具有丰富的路由策略&#xff0c;能够灵活的进行路由选择。重心不是在路由学习&#xff0c;而是路由优选、更高效的传递路由和维护大量的路由信息。基于TCP&#xf…...

Git-安装与常用命令

目录 1.Git环境配置 1.1下载 1.2配置 1.2.1基本配置 1.2.2常用指令配置别名 1.2.3获取本地仓库 git命令在git bash中演示&#xff0c;会用到一些Linux命令。 1.Git环境配置 1.1下载 Git下载地址&#xff1a;https://git-scm.com/download 傻瓜式安装就可以了。 安装…...

回归预测 | Matlab实现基于BiLSTM-Adaboost双向长短期记忆神经网络结合Adaboost集成学习回归预测

目录 效果一览基本介绍模型设计程序设计参考资料效果一览 基本介绍 回归预测 | Matlab实现基于BiLSTM-Adaboost双向长短期记忆神经网络结合Adaboost集成学习回归预测 模型设计 基于BiLSTM-Adaboost的回归预测模型结合了双向长短期记忆神经网络(BiLSTM)和Adaboost集成学习的…...

微信小程序跳转其他小程序以及跳转网站

一、跳转其他小程序 1.1 知道appid和页面路径 wx.navigateToMiniProgram({appId: appid, // 替换为目标小程序 AppIDpath: pathWithParams, // 小程序路径envVersion: release, // 开发版、体验版或正式版success(res) {console.log("跳转到其他小程序成功&#xff01;&q…...

Not using native diff for overlay2, this may cause degraded performance……

问题现象 案例&#xff1a;Anolis 8.9&#xff08;4.19.91-26.an8.x86_64&#xff09; Overlay2存储驱动程序&#xff09; 当我们安装好Docker之后&#xff0c;通过systemctl status docker -l 会发现有一个告警信息&#xff1a;levelwarning msg"Not using native dif…...

【自用】管材流转项目 数据库恢复之 PIPE 表 二维码相关 各个表恢复 SQL

总览 1.后端前端和数据库 PIPE 页面的关系 2.后端批量生成二维码 jpg 图片 3.为了保证 PIPE 正常使用的调整 4.TRANSFORM&#xff08;流转表&#xff09; 一、后端前端和数据库 PIPE 页面的关系 1.前端 关于PIPE页面&#xff0c;首先&#xff0c;在前端&#xff0c;我们已经…...

【渗透测试】信息收集二

其他信息收集 在渗透测试中&#xff0c;历史漏洞信息收集是一项重要的工作&#xff0c;以下是相关介绍&#xff1a; 历史漏洞信息收集的重要性 提高效率&#xff1a;通过收集目标系统或应用程序的历史漏洞信息&#xff0c;可以快速定位可能存在的安全问题&#xff0c;避免重复…...

测试工程师八股文04|计算机网络 和 其他

一、计算机网络 1、http和https的区别 HTTP和HTTPS是用于在互联网上传输数据的协议。它们都是应用层协议&#xff0c;建立在TCP/IP协议栈之上&#xff0c;用于客户端&#xff08;如浏览器&#xff09;和服务器之间的通信。 ①http和https的主要区别在于安全性。http是一种明…...