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

Linux——基于socket编程实现简单的Tcp通信

前言1:想要实现一个简单的Tcp通信不难,对于初学者而言,难点在于使用了大量未曾接触过的函数调用,所以本篇重点在于详解每部分代码中相关函数的功能。

1. 简单认识一下TCP传输

TCP通信协议是面向字节流的、可靠的、有连接的传输,在实现TCP协议通信时:

①:必须先建立客户端和服务端间的连接这部分由系统函数实现

②:其次因为TCP是面向字节流的,所以它的很多操作和文件操作是一致的

2. 实现思路

 编写一个服务端:TcpServer;

①. 初始化服务端,设置 IP 、 端口号 and 执行方法

②. 运行服务端,接收客户端发来的数据,交由其他线程来处理

 编写一个客户端:TcpClient;

①. 确定目标 ip and 端口号

②. 客户端发送数据到服务端,同时接收服务端的消息

3. 各部分涉及的相关函数详解

3.1 初始化服务端

步骤1:网络也是文件,一种特殊的文件,因此一开始需要打开文件(创建套接字)

相关函数:

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

功能:

        创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器):

参数:

domain:用于标识是网络通信还是本地通信

        AF_INET:网络通信  ☆☆☆

        AF_UNIX:本地通信

type:用于标识不同的套接字类型

        SOCK_DGRAM:基于UDP协议的通信

        SOCK_STREAM:基于TCP协议的通信

protocol:设置为0时;

        对于 AF_INET 和 SOCK_STREAM,操作系统会选择 TCP 协议

        对于 AF_INET 和 SOCK_DGRAM,操作系统会选择 UDP 协议​​​

返回值:int sockfd = socket(AF_INET, SOCK_STREAM, 0);

        sockfd(套接字网络文件描述符),用于表述对应描述符表的唯一性。

步骤2:套接字创建完毕时,sockfd需要和套接字描述符表进行绑定,就像打开文件时,文件描述符fd会和文件描述符表结构体相关联。

相关函数:

int bind(int sockfd, const struct sockaddr *address, socklen_t address_len);

功能

绑定端口号 (TCP/UDP, 服务器),将当前套接字与ip和端口号绑定

参数

sockfd

        套接字

address

        对于AF_INET通信,strcut sockaddr_in* 结构体指针内部保存的ip

len

        上述结构体对应的大小

注1☆☆☆

        对于服务端而言,将当前服务端所创建的套接字和服务端本地的ip以及指定端口绑定

        在次过程之前,需要将主机字节序转为网络字节序!

        这里能够通过面向对象的方式,来完成一步骤。相关代码如下

class InetAddr
{
public://主机转网络InetAddr(u_int16_t port):_port(port){memset(&_addr, 0, sizeof(_addr));_addr.sin_addr.s_addr = INADDR_ANY;_addr.sin_family = AF_INET;_addr.sin_port = htons(_port);}const struct sockaddr* NetAddrptr() { return CONV(_addr); } //获取sockaddr* 地址socklen_t NetAddrLen(){ return sizeof(_addr); }private:struct sockaddr_in _addr;std::string _ip;uint16_t _port;
};#define CONV(addr) ((struct sockaddr*)&addr)//类外可以通过相应函数进行构造
//  InetAddr local(_port);
//  int n = bind(sockfd, local.NetAddrptr(), local.NetAddrLen());

注2

        对于服务端,需要显式调用bind,bind的过程中,ip为当前服务器下所有可用ip

        对于客户端,不需显式调用bind,本地服务器在第一次进行网络通信时,操作系统会将本地IP and 随机端口号 分配给 sockfd。

        原因:因为用户不知道哪个端口号现在被占用了,所以需要等待系统去给你分配

        一个端口号只能被一个进程占用!

步骤3 :服务端显式调用bind后,需将当前sockfd设置为监听状态

相关函数:

int listen(int sockfd, int backlog);

sockfd

        当前套接字

backlog

        最大等待连接数

:之所以将当前套接字设置为listen的目的是:可能会有多个客户端向服务端进行通信。

举个简单例子:这里的socket被称为监听套接字,他充当一个在外部揽客的角色。每当有一个客户端向服务端进行通信时,都会被 监听套接字纳入到连接队列。下面会介绍 accpet 函数,该函数会从连接队列中取出一个,和客户端进行通信,这部分由多线程完成。

补充1线程更安全的 字符串 and 网络字节序 转换

相关函数:

int inet_pton(int af, const char *src, void *dst);

功能: 点分字符串 转为 网络字节序  p → n   presentation → network

af:地址族,通常为(AF_INET、AF_INET6)

src: 字符串风格的ip地址

dst:转换后的 网络字节序保存的位置

相关函数:

uint16_t htons(uint16_t hostshort);

功能: 将主机字节序转为网络字节序

hostshort:主机字节序

3.2 运行服务端

步骤1 :多个客户端向服务端发起通信时,此时需从连接队列取出一个客户端(报文)建立通信

相关函数:

int accept(int listen_sockfd, struct sockaddr *addr, socklen_t *addrlen);

功能

        从连接队列取出一个 连接 进行通信

 参数

listen_sockfd:

        从监听套接字中取出,该套接字是唯一的

addr

         用于保存客户端地址信息!!!!

addrlen

        地址结构体大小的指针

:accpet取出的 连接,是客户端的地址信息,因此在accpet前,需要创建一个 struct sockaddr_in 结构体来保存该信息,该信息中包含客服端的ip and 端口号

步骤2:因为接收到的是网络字节序,所以需要将 网络字节序 转为 主机字节序

相关代码如下:

class InetAddr
{
public://网络转主机InetAddr(struct sockaddr_in &addr) : _addr(addr){_port = ntohs(_addr.sin_port);           // 从网络中拿到的!网络序列char ipbuffer[64];inet_ntop(AF_INET,&_addr.sin_addr, ipbuffer,sizeof(_addr));// 4字节网络风格的IP -> 点分十进制的字符串风格的IP_ip = ipbuffer;}
private:struct sockaddr_in _addr;std::string _ip;uint16_t _port;
};//InetAddr addr(peer);  // peer 为accept接收到的客户端的 struct sockaddr_in 结构体

补充: 线程更安全的 字符串 and 网络字节序 转换

相关函数:

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

功能: 网络字节序 转为 字符串    p → n   presentation → network

af:地址族,通常为(AF_INET、AF_INET6)

src: 二进制格式的ip地址

dst:输出的字符串缓冲区

size:缓冲区的大小

相关函数:

uint16_t ntohs(uint16_t Netshort);

功能: 网络字节序 转 主机字节序

Netshort:网络字节序

3.3 客户端初始化

步骤1:客户端需要提供服务端的 ip and 端口号

步骤2:客户端需要通过socket创建套接字,这部分和客户端是一样的

:客户端是否需要bind?

答:需要!但是不是显式的bind,客户端在和服务端通过TCP建立连接 or UDP直接发送报文时,操作系会自动给当前进程绑定一个端口号,将客户端的 ip and 端口号 发给 服务端,因此客户端不需要显式的bind

步骤3:客户端向服务端发送连接请求

相关函数:

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

• 功能:

        用于发起客户端到服务端的连接请求

• 参数

sockfd:通过socket创建的文件描述符

addr:目标客服端的结构体

addrlen:addr的大小

:需将主机字节序转为网络字节序才能发送

4.图解TCP传输流程

假设:主机A是客户端、主机B是服务端

        服务端B通过上述函数初始化后,并调用accept接口阻塞等待客户端A发送数据。

        客户端A初始化并通过send/write接口发送数据时,send所发送的数据并不是直接通过网络传输给客服端B,send/witre是将数据拷贝给了发送缓冲区,os会在合适的时候将数据通过网络拷贝给服务端B的接收缓冲区。

        当客户端A向服务端Bconnect建立通信连接时,服务端B会将客户端丢入到连接队列中,并返回一个新的网络文件描述符sockfd来进行业务处理,同样客户端recv/read不是直接从网络中获取数据,而是在服务端B的接收缓冲区中将数据拷贝到上层。

认知

        结论1:对于Tcp协议,通信的双方在传输层都有发送缓冲区和接收缓冲区,因此是全双工通信,二者相互独立

        结论2:以服务端为例,操作系统向缓冲区拷贝数据,上层向缓冲区读数据,这不就是生产消费者模型吗? Tcp协议本质就是四组生产消费者模型

4.1 应用层再谈协议

• 先前结论:协议 → 结构体

        上述图解TCP传输流程是从底层来说的,对于处在应用层的用户,在将数据send/write 进发送缓冲区时,需要将结构体转成一个大的字符串,这一过程叫做序列化,目的是为了方便网络传输。

        对于服务端,接收缓冲区在收到网络中对应客户端的大字符串时,应用层调用recv/read 后,需要反序列化,方便上册的那个处理/阅读

:客户端和服务端都认识该结构体,该结构体就是被称为客户端和服务端约定好的协议。该模式被称为C(client)/S(server)模式

:为什么不直接发送结构体对象?而是将结构体对象序列化转成大字符串?再反序列化读取

:为了兼容性,因为不同语言结构体的对齐方式不同。

• 结论:所谓的协议定制,本质就是在定制双方都能认识的,符合通信和业务需要的结构化数据,所谓的结构化数据,其实就是struct 或者 class

5. 定制自定义协议来实现基于Tcp通信的网络计算器服务

• Tcp协议通信的双方会存在以下现象

1. 收发双方发送和接收次数不对等

2.收发双方是否选择发送和接收由操作系统决定

3.读方一定得读到一个完整的报文,才能进行反序列化的操作,如果在编程过程中没有读到一个完整的报文就处理,会出BUG

:因读取报文不完整导致的问题叫做“粘报“问题

• 因此定制一个协议需要满足

1.结构化的字段,提供 序列化 and 反序列化 的方案

2.解决因为字节流问题,导致读取报文不完整的问题(只用处理读取)

5.1 实现思路

对于服务端,主函数中通过分层结构实现

① 业务层

② 协议层

③ 通信连接层

对于客户端,主函数主要实现

① 初始化

② 请求建立通信连接

③ 发送数据 + 接收结果

5.1.1 业务层

主函数中,通过智能指针创建一个Cal对象

std::unique_ptr<Cal> cal = std::make_unique<Cal>();

Cal类如图如下所示

#pragma once
#include "Protocol.hpp"
#include <iostream>class Cal
{
public:Response Excute(Request &req){Response resp(0, 0);switch (req.Oper()){case '+':resp.SetResult(req.X() + req.Y());break;case '-':resp.SetResult(req.X() - req.Y());break;case '*':resp.SetResult(req.X() * req.Y());break;case '/':{if (req.Y() == 0)resp.SetCode(1); // 除零错误elseresp.SetResult(req.X() / req.Y());break;}case '%':{if (req.Y() == 0)resp.SetCode(2); // 余零错误elseresp.SetResult(req.X() % req.Y());break;}default:resp.SetCode(3); // 传参错误break;}return resp;}
};

5.1.2 协议层

 主函数中,同样通过创建一个对象Protocol对象来调用接口

std::unique_ptr<Protocol> p = std::make_unique<Protocol>([&cal](Request& req)->Response{return cal->Excute(req);});

客户端发送的数据经协议处理后,需要将数据交由上层业务处理,因此需通过lamda表达式来实现回调。

规定1:用户端发送给服务端的数据称为请求 , 服务端发送给用户端的数据称为回复

规定2:大字符串以  "len + \r\n + 数据 + \r\n"  的方式通过网络传递,其中len为数据的长度

\r\n为分隔符,通过上述来解决"粘报"问题。

Protocol类中主要需要实现的函数

①. 用户端将请求序列化 or 服务端将回复序列化, 将结构化的数据转为字符串形式,通过json库来实现,这是一个三方库

②. 用户端 加密序列化后的请求 or 客户端 加密序列化后的回复,将数据转成  "len + \r\n + 数据 + \r\n" 的形式

③. 服务端 解密用户端的请求 or 用户端 解密服务端的回复   从网络中收到的 "len + \r\n + 数据 + \r\n" 的形式的大字符串

客户端和服务端间通信流程

👉. 服务端创建服务器,等待客户端通信,(建立套接字、bind、设置listen、accept)

👉. 客户端发送通信请求,双方建立通信连接 (建立套接字、connect)

👉. 客户端将结构化数据转为字符串形式

👉. 客户端将数据加密为 "len + \r\n + 数据 + \r\n"形式的数据

👉. 客户端发送数据  (send/write)

👉. 服务端接收数据  (recv/read)

👉. 服务器解密收到的数据

👉. 服务端将有效数据反序列化 

👉. 服务器调用回调进行业务处理

👉. 服务端将处理的结果序列化

👉. 服务端将序列化后的结果加密

👉. 服务端发送数据

👉. 客户端接收数据、解密数据、反序列化数据得到结果。

:请求的序列化和反序列化 与 回复的序列化和反序列化是分开的。两者的参数和结果均不同,当然需要分开写。

代码如下

#pragma once
#include <functional>
#include "common.hpp"
#include "Socket.hpp"using namespace SocketModule;// client -> server  客户的请求:客户需要将请求序列化到服务端,服务端需要将客户的请求反序列化
class Request
{
public:Request(){}Request(int x, int y, char oper): _x(x),_y(y),_oper(oper){}std::string Serialize(){Json::Value root;root["x"] = _x;root["y"] = _y;root["oper"] = _oper;Json::FastWriter Writer;std::string s = Writer.write(root);return s;}bool Deserialize(std::string& Package){Json::Value root;Json::Reader reader;bool ok = reader.parse(Package, root); // 将Json串读到root中if (ok){_x = root["x"].asInt();_y = root["y"].asInt();_oper = root["oper"].asInt();}return ok;}int X() { return _x; }int Y() { return _y; }char Oper() { return _oper; }~Request(){}private:int _x;int _y;char _oper;
};// server -> client  服务端需要将业务处理的结果序列化到客户端,客户端需要反序列业务处理的结果
class Response
{
public:Response() {}Response(int result, int code) : _result(result), _code(code){}// 服务端向用户端序列化std::string Serialize(){Json::Value root;root["result"] = _result;root["code"] = _code;Json::FastWriter writer;return writer.write(root);}// 用户端反序列从服务端接收到的数据bool Deserialize(std::string &in){Json::Value root;Json::Reader reader;bool ok = reader.parse(in, root);if (ok){_result = root["result"].asInt();_code = root["code"].asInt();}return ok;}void SetResult(int result){_result = result;}void SetCode(int code){_code = code;}void ShowResult(){std::cout << "计算结果是:" << _result << " [" << _code << "]" << std::endl;}~Response() {}private:int _result;int _code;
};const std::string seq = "\r\n";
using func_t = std::function<Response(Request &req)>;class Protocol
{
public:Protocol(func_t func): _func(func){}Protocol(){}// 编码 Encode      len/r/n json码 /r/n  约定通信的双方以这种方式(协议)进行通信,json码为有效数据std::string Encode(std::string &jsonstr){std::string len = std::to_string(jsonstr.size());       //return (len + seq + jsonstr + seq);}// 解码 Decode 通信的双方以该协议进行通信bool Decode(std::string &buffer, std::string *Json_Package){// 通信的双方通过 数据长度 + /r/n + json串 + /r/n 进行加密// 1.先找第一个 /r/n,没找到说明数据不完整ssize_t pos = buffer.find(seq);if (pos == std::string::npos){   return false;}// 到这说明该报文有数据长度// 2.获得数据长度std::string package_len_str = buffer.substr(0, pos);int len = std::stoi(package_len_str);// 3.根据加密协议 以及数据长度,得到有效报文的总长度int size = package_len_str.size() + 2 * seq.size() + len;// 4.如果当前服务端收到的数据长度小于 size  说明收到的消息不完整if (buffer.size() < size){   return false;}// 5.到这说明buffer内至少包含一条完整的加密后数据,对加密数据进行解密*Json_Package = buffer.substr(pos + 2, len);// 6.将解密后的数据从buffer中删除buffer.erase(0, size);return true;}void GetRequest(std::shared_ptr<Socket> &sock, InetAddr &client){std::string buffer_queue;while (true){int n = sock->Recv(&buffer_queue);if (n > 0){// 读取不完整,避免粘包问题// 服务端从用户端读取用户请求  → 经客户端序列化的数据且加密的数据// 1. 服务端解密std::string Json_Package;while (Decode(buffer_queue, &Json_Package)){// 2.服务端对客户请求的反序列化Request req;bool ret = req.Deserialize(Json_Package);if (!ret)continue;// 3. 解密反序列化完毕后 调用上层业务Response resp = _func(req); // 要将业务处理的结果返回给客户端,所以需要返回一个Response对象// 将得到的结果序列化传递给 用户端// 4.先序列化  服务端 → 用户端的序列化std::string send_str = resp.Serialize();// 5.序加密std::string package = Encode(send_str);// 6.发送sock->Send(package);}}else if (n == 0){LOG(LogLevel::INFO) << "用户退出了";break;}else{LOG(LogLevel::WARNING) << "client" << client.StringAddr() << ": recv error";break;}}}bool GetResponse(std::shared_ptr<Socket> client, std::string &resp_buffer, Response *resp){while (true){int n = client->Recv(&resp_buffer);if (n > 0){std::string json_package;while (Decode(resp_buffer, &json_package)){resp->Deserialize(json_package);}return true;}else if (n == 0){return false;}else{return false;}}}std::string BuildRequestString(int x, int y, char oper){Request req(x, y, oper);// 将客户端的请求序列化std::string json_str = req.Serialize();// 将序列化的json串加密return Encode(json_str);}~Protocol(){}private:// 因为我们用的是多进程// Request _req;// Response _resp;func_t _func;
};

5.1.3 通信连接层

主函数中,通过智能指针创建一个TcpServer对象,当服务端收到客户端的通信请求时,创建子进程的子进程来执行回调方法实现通信双方数据的交互

std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(std::stoi(argv[1]),[&p](std::shared_ptr<Socket> &sock, InetAddr &client){p->GetRequest(sock, client);});tsvr->Start();

通信连接层通过模板方法模式进行初始化

模板方法模式:将基类中的虚函数方法在派生类中重写,同时基类中有一套固定方法,创建派生类对象时,调用该方法来初始化派生类对象的过程

补充:套接字的创建、绑定、监听、数据的发送、接收和连接,都和网络文件描述符相关,因此我们可以创建一个专门用来调用这些接口的自定义类,该类由书上的模板方法模式实现。具体代码如下所示:
 

#pragma once
#include "common.hpp"
#include "InetAddr.hpp"
#include <memory>namespace SocketModule
{using namespace LogModule;class Socket{public:virtual void SocketOrDie() = 0;virtual void BindOrDie(uint16_t port) = 0;virtual void ListenOrDie(int backlog) = 0;virtual std::shared_ptr<Socket> Accept(InetAddr* Client) = 0;virtual bool Connect(std::string& ip, uint16_t port) = 0;virtual void Close() = 0;virtual int Recv(std::string* out) = 0;virtual int Send(const std::string& message) = 0;public:void BuildTcpSocketMethod(int port, int backlog = 16){SocketOrDie();BindOrDie(port);ListenOrDie(backlog);}void BulidClientSocketMethod(){SocketOrDie();}};const int DefaultNum = -1;class TcpSocket : public Socket{public:TcpSocket(): _sockfd(DefaultNum){}TcpSocket(int fd): _sockfd(fd){}void SocketOrDie() override{_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);if (_sockfd < 0){LOG(LogLevel::FATAL) << "socket error";exit(SOCKET_ERROR);}LOG(LogLevel::INFO) << "socket sunccess";}void BindOrDie(uint16_t port) override{InetAddr localAddr(port);int n = ::bind(_sockfd, localAddr.NetAddrptr(), localAddr.NetAddrLen());if (n < 0){LOG(LogLevel::FATAL) << "bind error";exit(BIND_ERROR);}LOG(LogLevel::INFO) << "bind sunccess";}void ListenOrDie(int backlog) override{int n = ::listen(_sockfd, backlog);if (n < 0){LOG(LogLevel::FATAL) << "listen error";exit(LISTEN_ERROR);}LOG(LogLevel::INFO) << "listen sunccess";}std::shared_ptr<Socket> Accept(InetAddr* Client) override{struct sockaddr_in peer;socklen_t len = sizeof(len);int fd = ::accept(_sockfd, CONV(peer), &len);if (fd < 0){LOG(LogLevel::WARNING) << "accept warning ...";return nullptr; // TODO}Client->SetAddr(peer);// 解耦,让另一个对象来管理客户端的ip and 端口号信息return std::make_shared<TcpSocket>(fd);}bool Connect(std::string& ip, uint16_t port) override{InetAddr client(ip,port);//初始化客户端int n = ::connect(_sockfd,client.NetAddrptr(),client.NetAddrLen());if(n < 0){LOG(LogLevel::FATAL) << "connnect error";return false;}return true;}int Recv(std::string* out) override{char buffer[1024];ssize_t n = ::recv(_sockfd, buffer, sizeof(buffer) - 1, 0);if(n > 0){buffer[n] = 0;*out += buffer;}return n;}virtual int Send(const std::string& message) override{return send(_sockfd, message.c_str(), message.size(), 0);}void Close() override{if(_sockfd >= 0)::close(_sockfd);}~TcpSocket(){}private:int _sockfd;};
}

主函数在实例化时,默认的构造函数如图所示

TcpServer(u_int16_t port, ioservice_t func):_ListenSockfd(std::make_unique<TcpSocket>()),_port(port),_isrunning(false),_service(func){_ListenSockfd->BuildTcpSocketMethod(port);}

在创建过程中,会自动将服务端初始化

5.1.4 客户端

#include "Protocol.hpp"
#include "Socket.hpp"
#include "InetAddr.hpp"
#include "common.hpp"void GetDataFromStdin(int* x,int* y,char* opre)
{std::cout << "please enter x:";std::cin >> *x;std::cout << "please enter y:";std::cin >> *y;std::cout << "please enter oper:";std::cin >> *opre;
}int main(int argc, char* argv[])
{if(argc != 3){exit(1);}std::string ip = argv[1];uint16_t port = std::stoi(argv[2]);std::shared_ptr<Socket> client = std::make_shared<TcpSocket>();client->BulidClientSocketMethod();if(!client->Connect(ip,port)){exit(2);}std::unique_ptr<Protocol> protocol = std::make_unique<Protocol>();std::string resp_buffer;while(true){int x, y;char oper;GetDataFromStdin(&x,&y,&oper);std::string req_str = protocol->BuildRequestString(x,y,oper);//发送client->Send(req_str);//获取应答Response resp;bool res = protocol->GetResponse(client,resp_buffer,&resp);resp.ShowResult();}client->Close();return 0;
}

相关文章:

Linux——基于socket编程实现简单的Tcp通信

前言1&#xff1a;想要实现一个简单的Tcp通信不难&#xff0c;对于初学者而言&#xff0c;难点在于使用了大量未曾接触过的函数调用&#xff0c;所以本篇重点在于详解每部分代码中相关函数的功能。 1. 简单认识一下TCP传输 TCP通信协议是面向字节流的、可靠的、有连接的传输&a…...

STL C++详解——priority_queue的使用和模拟实现 堆的使用

priority_queue的使用 std::priority_queue 是 C 标准模板库&#xff08;STL&#xff09;中的一个容器适配器&#xff0c;提供了优先队列的功能。 优先队列&#xff1a;是一种特殊的队列&#xff0c;队列中的每个元素都有与之关联的优先级&#xff0c;优先级高的元素会先出队…...

是否可以使用非被动 S4P 文件进行反嵌?

AEDT 电路去嵌入算法使用假定线性时不变 &#xff08;LTI&#xff09; 行为的转换。如果非被动 S 参数块不是 LTI&#xff0c;则倒数函数将无法按预期工作。...

GAEA的技术优势:分层加密与去中心化数据治理

GAEA采用分层加密架构&#xff0c;将用户数据分为三个层级&#xff1a; 基础层&#xff08;链上数据&#xff09;&#xff1a;用户身份哈希、资源贡献记录等核心数据通过零知识证明&#xff08;ZK-SNARKs&#xff09;进行链上加密&#xff0c;确保不可篡改和匿名性。 情感层…...

使用ZYNQ芯片和LVGL框架实现用户高刷新UI设计系列教程(第九讲)

这一期讲解GUI_guider中的容器控件的使用以及相关函数&#xff0c;容器本质上是具有布局和自动调整大小功能的基本对象 &#xff0c;通常用来装载其他子控件。 打开上一期的项目&#xff0c;在工具栏中选中容器控件拖拽到界面中&#xff0c;具体如图所示&#xff1a; 容器默认…...

SparkStreaming概述

SparkStreaming主要用于流式计算&#xff0c;处理实时数据。 DStream是SparkStreaming中的数据抽象模型&#xff0c;表示随着时间推移收到的数据序列。 SparkStreaming支持多种数据输入源&#xff08;如Kafka、Flume、Twitter、TCP套接字等&#xff09;和数据输出位置&#xf…...

LeetCode---整数反转

整数反转 给你一个 32 位的有符号整数 x &#xff0c;返回将 x 中的数字部分反转后的结果。 如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] &#xff0c;就返回 0。 示例 示例 1&#xff1a; 输入&#xff1a;x 123 输出&#xff1a;321 示例 2&#xf…...

Conceptrol: Concept Control of Zero-shot Personalized Image Generation(个性化图像生成)

文章目录 一、论文介绍二、项目部署三、效果展示3.1ipadapter plus sd1.5的效果3.2ipadapter plus sd1.5 plus concept的效果3.3两者结果的比较&#xff1a;原本的ipadapter、加了concept的ipadapter 一、论文介绍 个性化图像生成中的平衡问题&#xff1a;现有的zero-shot adap…...

【Harmony】常用工具类封装

文章目录 一&#xff0c;简介二&#xff0c;网络请求工具类2.1、鸿蒙原生http封装2.2、第三方axios封装(需提前下载依赖) 三、录音笔相关工具类3.1、录音封装(录入)3.2、录音封装(放音/渲染)3.3、文件写入封装(针对录音/放音功能) 四、RDB关系型数据库4.1、relationalStore简答…...

大模型部署到本地就是私有化部署吗?

大模型私有化的定义需要从部署方式和数据/模型控制权两个维度来理解&#xff0c;不能简单地仅以“部署位置”或“数据训练”单一条件判断。以下是具体分析&#xff1a; 1. 大模型私有化的核心定义 根据知识库中的描述&#xff08;[1][2][3][8]&#xff09;&#xff1a; 私有化…...

C语言高频面试题——嵌入式系统中中断服务程序

在嵌入式系统中&#xff0c;中断服务程序&#xff08;ISR&#xff09;的设计需遵循严格的规则以确保系统稳定性和实时性。以下是对这段代码的分析及改进建议&#xff1a; 代码分析 __interrupt double compute_area (double radius) { double area PI * radius * radius; pri…...

JavaFX 实战:从零打造一个功能丰富的英文“刽子手”(Hangman)游戏

大家好&#xff01;今天我们要挑战一个经典的单词猜谜游戏——“刽子手”&#xff08;Hangman&#xff09;&#xff0c;并使用 JavaFX 这个强大的 GUI 工具包来赋予它现代化的交互体验。这个项目不仅有趣&#xff0c;而且是学习和实践 JavaFX 核心概念的绝佳途径&#xff0c;涵…...

第 2.1 节: 机器人仿真环境选择与配置 (Gazebo, MuJoCo, PyBullet)

在真实机器人硬件上进行开发和测试既耗时又存在风险&#xff08;硬件损坏、安全问题&#xff09;。机器人仿真环境提供了一个虚拟的沙盒&#xff0c;让开发者能够在计算机中模拟机器人的物理行为、传感器读数和环境互动&#xff0c;极大地加速了开发、测试和调试过程。特别是对…...

网络开发基础(游戏)之 粘包分包

粘&#xff08;nin&#xff09;包、分包 在网络通信中&#xff0c;TCP协议是面向流的协议&#xff0c;没有消息边界概念&#xff0c;粘包和分包是常见的问题。在某种情况下&#xff08;例如网络环境不稳定&#xff09;就会导致"粘包"和"分包"问题&#xf…...

联邦元学习实现个性化物联网的框架

随着数据安全和隐私保护相关法律法规的出台&#xff0c;需要直接在中央服务器上收集和处理数据的集中式解决方案&#xff0c;对于个性化物联网而言&#xff0c;训练各种特定领域场景的人工智能模型已变得不切实际。基于此&#xff0c;中山大学&#xff0c;南洋理工大学&#xf…...

使用 Nacos 的注意事项与最佳实践

&#x1f4f9; 背景 Nacos 凭借其强大&#x1f4aa;的服务发现、配置管理和服务管理能力&#xff0c;成为构建分布式系统的得力助手。然而&#xff0c;要充分发挥 Nacos 的优势&#xff0c;实现系统的高性能、高可用&#xff0c;掌握其使用过程中的注意事项和最佳实践至关…...

在Pytorch中使用Tensorboard可视化训练过程

【在Pytorch中使用Tensorboard可视化训练过程】 https://www.bilibili.com/video/BV1Qf4y1C7kz/?share_sourcecopy_web&vd_sourcef00bfb41b3b450c3767070ed82f30ac8 主要功能&#xff1a; 1.保存网络结构图 2.保存训练集的损失Loss&#xff0c;验证集的正确性Accuracy以…...

15.电感特性在EMC设计中的运用

电感特性在EMC设计中的运用 1. 共模电感与差模电感的差异2. 电感的高频等效特性![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/b4dc000672af4dd69a528450eb42cf10.png)3. 电感在EMC设计中的使用注意事项3.1 LC滤波计算3.2 并联型多级浪涌防护的电感退耦 1. 共模电感…...

代理设计模式:从底层原理到源代码 详解

代理设计模式&#xff08;Proxy Pattern&#xff09;是一种结构型设计模式&#xff0c;它通过创建一个代理对象来控制对目标对象的访问。代理对象充当客户端和目标对象之间的中介&#xff0c;允许在不修改目标对象的情况下添加额外的功能&#xff08;如权限控制、日志记录、延迟…...

25、简述.NET程序集(Assembly)

.NET 程序集&#xff08;Assembly&#xff09; .NET 程序集&#xff08;Assembly&#xff09; 是 .NET 应用程序的基本部署单元&#xff0c;包含以下核心内容&#xff1a; 类型与代码&#xff1a; 存储类、接口等类型的定义及实现&#xff08;以中间语言 IL 形式&#xff09;。…...

JavaScript 笔记 --- part 5 --- Web API (part 3)

(webAPI part3) BOM 操作 JS 执行机制 javascript 是单线程的, 也就是说, 只能同时执行一个任务。 为了解决这个问题, 利用多核 CPU 的计算能力, HTML5 提出 Web Worker API, 允许 JavaScript 脚本创建多个线程, 并将任务分配给这些线程。 于是, JS 出现了同步和异步的概念。…...

HCIP(OSPF)(3)

OSPF 报文结构 公共头部&#xff1a;包含版本&#xff08;8bit&#xff09;、类型&#xff08;8bit&#xff09;、报文长度&#xff08;16bit&#xff09;、路由器 ID&#xff08;32bit&#xff09;、区域 ID&#xff08;32bit&#xff09;、校验和&#xff08;16bit&#xff0…...

Docker:重塑应用开发与部署的未来[特殊字符]

Docker&#xff1a;重塑应用开发与部署的未来&#x1f680; 在数字化转型的浪潮中&#x1f30a;&#xff0c;应用开发与部署面临着诸多挑战&#xff0c;如环境一致性差、资源利用率低、运维复杂等。而Docker&#xff0c;作为容器技术领域的明星产品&#x1f31f;&#xff0c;凭…...

逻辑回归:损失和正则化技术的深入研究

逻辑回归&#xff1a;损失和正则化技术的深入研究 引言 逻辑回归是一种广泛应用于分类问题的统计模型&#xff0c;尤其在机器学习领域中占据着重要的地位。尽管其名称中包含"回归"&#xff0c;但逻辑回归本质上是一种分类算法。它的核心思想是在线性回归的基础上添…...

如何改电脑网络ip地址完整教程

更改电脑的网络IP地址以满足特定的网络需求&#xff0c;本文将为您提供一份详细的步骤指南。其实&#xff0c;改变IP地址并不是一件复杂的事&#xff0c;能解决因为IP限制带来的麻烦。以下是操作指南&#xff1a; 方法一&#xff1a;Windows 系统&#xff0c;通过图形界面修改 …...

Scenario Dreamer:用于生成驾驶模拟环境的矢量化潜扩散模型

25年3月来自加拿大 Mila AI研究院、蒙特利尔大学、蒙特利尔理工、普林斯顿、加拿大 CIFAR AI Chair 计划和 Torc 机器人公司的论文“Scenario Dreamer: Vectorized Latent Diffusion for Generating Driving Simulation Environments”。 Scenario Dreamer&#xff0c;是一个完…...

# 基于PyTorch的食品图像分类系统:从训练到部署全流程指南

基于PyTorch的食品图像分类系统&#xff1a;从训练到部署全流程指南 本文将详细介绍如何使用PyTorch框架构建一个完整的食品图像分类系统&#xff0c;涵盖数据预处理、模型构建、训练优化以及模型保存与加载的全过程。 1. 系统概述 本系统实现了一个基于卷积神经网络(CNN)的…...

【MCP Node.js SDK 全栈进阶指南】初级篇(1):MCP开发环境搭建详解

引言 Model Context Protocol (MCP) 是一种开放标准,旨在规范模型与应用程序之间的交互方式。本文作为MCP TypeScript-SDK系列的第一篇,将详细介绍如何搭建MCP开发环境,包括Node.js与TypeScript环境配置、SDK安装、开发工具推荐以及项目结构设计,帮助你快速入门MCP应用开发…...

unity脚本-FBX自动化模型面数校验

根据目前模型资源平均面数预算进行脚本制作&#xff0c;自动化校验模型面数是否符合规范。 *注&#xff1a;文件格式为.cs。需要放置在unity资源文件夹Assets>Editor下。 测试效果&#xff08;拖一个fbx文件进unity时自动检测&#xff09;&#xff1a; 以下为完整代码 us…...

压力容器的优化设计

1 优化设计概述 优化设计是一种寻找确定最优设计方案的技术。所谓“最优设计”&#xff0c;指的是一种方案可以满足所有的设计要求&#xff0c;而且所需的支出&#xff08;如重量&#xff0c;面积&#xff0c;体积&#xff0c;应力&#xff0c;费用等&#xff09;最小。也就是…...

在Windows上安装Git

一、安装 Git 下载 Git地址&#xff1a;Git - Downloads (git-scm.com) 1、在页面中找到适用于 Windows 系统的最新版本安装包&#xff08;通常为.exe 格式文件&#xff09;&#xff0c;点击下载链接。 出于访问Git官网需要科学上网&#xff0c;不会的可以私信我要软件包&…...

python包管理器,conda和uv 的区别

python包管理器&#xff0c;conda和uv 的区别 以下是 conda 和 uv 在 Python 包管理中的深度对比&#xff0c;结合知识库内容进行分析&#xff1a; 1. 核心设计理念 conda 以“环境为中心”&#xff0c;强调跨语言支持&#xff08;如 Python、R、Julia&#xff09;和严格的依赖…...

Oracle在ERP市场击败SAP

2024年&#xff0c;甲骨文&#xff08;Oracle&#xff09;以87亿美元的ERP收入和6.63%的市场份额&#xff0c;首次超越SAP&#xff0c;成为全球最大的ERP应用软件供应商&#xff0c;结束了SAP自上世纪80年代以来在该领域的长期霸主地位。据APPS RUN THE WORLD的市场调研&#x…...

Kafka 消息积压监控和报警配置的详细步骤

Kafka 消息积压监控和报警配置的详细步骤示例&#xff0c;涵盖常用工具&#xff08;如 Prometheus Grafana、云服务监控&#xff09;和自定义脚本方法&#xff1a; 一、监控配置 方法1&#xff1a;使用 Prometheus Grafana kafka-exporter 步骤1&#xff1a;部署 kafka-ex…...

记录一次使用面向对象的C语言封装步进电机驱动

简介 (2025/4/21) 本库对目前仅针对TB6600驱动下的42步进电机的基础功能进行了一定的封装, 也是我初次尝试以面向对象的思想去编写嵌入式代码, 和直流电机的驱动步骤相似在调用stepmotor_attach()函数和stepmotor_init()函数之后仅通过结构体数组stepm然后指定枚举变量中的id即…...

QTextDocument 入门

一、QTextDocument QTextDocument 是 Qt 中用于处理富文本文档的核心类,支持文本格式、图片、表格等复杂内容。 1. QTextDocument 入门 1.1 基本概念 QTextDocument 是 Qt 中用于处理富文本内容的核心类,它提供了: 结构化文本存储(段落、列表、表格等) 文本格式支持(…...

Arthas进阶用法

目录 查看已加载的类反编译代码动态执行代码排查 HTTP 请求问题热更新代码获取 Spring Context 并操作查看 JVM 信息自定义命令Web Console重置与退出 查看已加载的类 sc 命令 &#xff1a;可以查找所有 JVM 已经加载到的类。如果搜索的是接口&#xff0c;还会搜索所有的实现类…...

三生原理与现有密码学的核心区别?

AI辅助创作&#xff1a; 三生原理与现有密码学的核心区别 一、‌哲学基础与设计逻辑‌ ‌动态生成 vs 静态分析‌ 三生原理以“阴阳动态平衡”为核心&#xff0c;通过参数化生成&#xff08;如素数构造中的阴阳元联动公式&#xff09;模拟系统演化过程&#xff0c;而现有密码…...

定义python中的函数和类

1.函数 在Python中&#xff0c;定义一个函数要使用def语句&#xff0c;依次写出函数名、括号、括号中的参数和冒号:&#xff0c;然后&#xff0c;在缩进块中编写函数体&#xff0c;函数的返回值用return语句返回 1.1 定义函数 def showshow(sex):if sex1:return manelse:retu…...

明远智睿2351开发板四核1.4G Linux处理器:驱动创新的引擎

在科技日新月异的今天&#xff0c;创新成为了推动社会进步的核心动力。而在这场创新的浪潮中&#xff0c;一款性能卓越、功能全面的处理器无疑是不可或缺的引擎。今天&#xff0c;我们介绍的这款四核1.4G处理器搭配Linux系统的组合&#xff0c;正是这样一款能够驱动未来创新的强…...

【前端】【业务逻辑】【面试】JSONP处理跨域原理与封装

&#x1f9e0; 一、JSONP 是什么&#xff1f; 项目内容&#x1f4cc; 全称JSON with Padding&#x1f4cd; 用途跨域请求数据的一种方式&#xff0c;绕过同源策略&#x1f4e6; 本质通过 <script> 标签加载远程 JS 文件&#xff0c;这个文件执行一个回调函数并传入数据 …...

深入探索RAG:用LlamaIndex为大语言模型扩展知识,实现智能检索增强生成

大型语言模型&#xff08;LLM&#xff09;&#xff0c;如ChatGPT和Llama&#xff0c;在回答问题方面表现出色&#xff0c;但它们的知识仅限于训练时所获取的信息。它们无法访问私有数据&#xff0c;也无法在训练截止日期之后学习新知识。那么&#xff0c;核心问题就是……我们如…...

移远通信智能模组助力东成“无边界智能割草机器人“闪耀欧美市场

2025年4月21日&#xff0c;移远通信宣布&#xff0c;旗下SC206E-EM智能模组已成功应用于江苏东成电动工具有限公司旗下的DCK TERRAINA无边界智能割草机器人。 这款智能模组高度集成计算、通信、定位等多元能力&#xff0c;以小型化、低功耗、实时性强和低成本等综合优势&#…...

测试-时间规模化定律可以改进世界基础模型吗?

25年3月来自 UT Austin、UW Madison 和 Nvidia 的论文“Can Test-Time Scaling Improve World Foundation Model?”。 世界基础模型&#xff08;WFM&#xff09;通过根据当前的观察和输入预测未来状态来模拟物理世界&#xff0c;已成为许多物理智能&#xff08;PI&#xff09…...

VMwaer虚拟机复制粘贴、ROS系统安装

一、VMwaer虚拟机复制粘贴设置&#xff1a;设置完记得重启VMwaer 1.首先确定 虚拟机设置-->选项-->客户机隔离-->勾选 启用拖放 启用复制粘贴 2.如果还是不能粘贴 可能是 没有 VMware Tools 可参考 怎么在linux安装vmware tools-CSDN博客 设置完记得重启VMwaer。…...

安装 vmtools

第2章 安装 vmtools 1.安装 vmtools 的准备工作 1&#xff09;现在查看是否安装了 gcc ​ 查看是否安装gcc 打开终端 输入 gcc - v 安装 gcc 链接&#xff1a;https://blog.csdn.net/qq_45316173/article/details/122018354?ops_request_misc&request_id&biz_id10…...

HCIP(综合实验2)

1.实验拓补图 2.实验要求 1.根据提供材料划分VLAN以及IP地址&#xff0c;PC1/PC2属于生产一部员工划分VLAN10,PC3属于生产二部划分VLAN20 2.HJ-1HJ-2交换机需要配置链路聚合以保证业务数据访问的高带宽需求 3.VLAN的放通遵循最小VLAN透传原则 4.配置MSTP生成树解决二层环路问题…...

机器学习第一篇 线性回归

数据集&#xff1a;公开的World Happiness Report | Kaggle中的happiness dataset2017. 目标&#xff1a;基于GDP值预测幸福指数。&#xff08;单特征预测&#xff09; 代码&#xff1a; 文件一&#xff1a;prepare_for_traning.py """用于科学计算的一个库…...

Spark-Streaming简介及核心编程

一、核心概念&#xff1a; 1.Spark-Streaming 是流式数据处理框架&#xff0c;基于 **DStream&#xff08;离散化流&#xff09;** 抽象&#xff0c;将实时数据划分为多个时间区间的 RDD 序列。 DStream 本质是RDD 序列&#xff0c;每个时间区间数据对应一个 RDD。 2.特点&a…...

优化提示词方面可以使用的数学方法理论:信息熵,概率论 ,最优化理论

优化提示词方面可以使用的数学方法理论:信息熵,概率论 ,最优化理论 目录 优化提示词方面可以使用的数学方法理论:信息熵,概率论 ,最优化理论信息论信息熵明确问题主题提供具体细节限定回答方向规范语言表达概率论最优化理论信息论 原理:信息论中的熵可以衡量信息的不确定性。…...