Linux网络套接字

news2025/4/7 21:43:15

Socket 编程 UDP

本章将函数介绍和代码编写实战一起使用。
IPv4 的 socket 网络编程,sockaddr_in 中的成员 struct in_addr.sin_addr 表示 32 位 的 IP 地址
但是我们通常用点分十进制的字符串表示 IP 地址,以下函数可以在字符串表示和in_addr 表示之间转换;
字符串转 in_addr 的函数:

#include <arpa/inet.h>
int inet_aton(const char:*strptr,struct inaddr *addrptr);
int_addr_t inet_addr(const char *strptr);
int inet_pton(int family,const char *strptr,void *addrptr);

in_addr 转字符串的函数:

char *inet_ntoa(struct in addrinaddr);
const char *inet_ntop(int family,const void *addrptr, char *strptr,size t len);

查看OS所有UDP和进程信息:

netstat -naup

这个函数是创建一个套接字:

int socket(int domain, int type,int protocol);
第一个参数是套接字的域,就是确定是ipv4还是ipv6等待。
第二个是套接字的数据流类型。
第三个参数是协议类型。
返回值是:返回成功返回一个文件操作符,失败返回-1。
其实socket也就相当于创建了一个文件。

在这里插入图片描述
这个函数是绑定套接字:

int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
第一个参数是网络文件描述符
第二个参数是用哪一种套接字(让绑定过来的套接字实现哪一种功能)

在这里插入图片描述

将指定内存全部初始化为0的函数:

void bzero(void *s, size_t n);
第一个参数是传地址
第二个参数是缓冲区的大小

在这里插入图片描述

in_addr_t inet_addr(const char *cp);
让字符串转换成网络ip风格的四字节

在这里插入图片描述

接收信息函数:

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
第二个参数为接收容器
第三个参数为信息长度
第四个参数为设置阻塞与非阻塞
第六个参数为套接字种类的长度

在这里插入图片描述
发送信息函数:

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
参数和上一个函数差不多,只有最后一个参数是不需要取地址的

在这里插入图片描述

首先有一个代码的预备工作,实现一个日志附带文件打印功能的代码

//log.hpp
#pragma once

#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

#define SIZE 1024

#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4

#define Screen 1
#define Onefile 2
#define Classfile 3

#define LogFile "log.txt"

class Log
{
public:
    Log()
    {
        printMethod = Screen;
        path = "./log/";
    }
    void Enable(int method)
    {
        printMethod = method;
    }
    std::string levelToString(int level)
    {
        switch (level)
        {
        case Info:
            return "Info";
        case Debug:
            return "Debug";
        case Warning:
            return "Warning";
        case Error:
            return "Error";
        case Fatal:
            return "Fatal";
        default:
            return "None";
        }
    }

    void printLog(int level, const std::string &logtxt)
    {
        switch (printMethod)
        {
        case Screen:
            std::cout << logtxt << std::endl;
            break;
        case Onefile:
            printOneFile(LogFile, logtxt);
            break;
        case Classfile:
            printClassFile(level, logtxt);
            break;
        default:
            break;
        }
    }
    void printOneFile(const std::string &logname, const std::string &logtxt)
    {
        std::string _logname = path + logname;
        int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"
        if (fd < 0)
            return;
        write(fd, logtxt.c_str(), logtxt.size());
        close(fd);
    }
    void printClassFile(int level, const std::string &logtxt)
    {
        std::string filename = LogFile;
        filename += ".";
        filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"
        printOneFile(filename, logtxt);
    }

    ~Log()
    {
    }
    void operator()(int level, const char *format, ...)
    {
        time_t t = time(nullptr);
        struct tm *ctime = localtime(&t);
        char leftbuffer[SIZE];
        snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
                 ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
                 ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

        va_list s;
        va_start(s, format);
        char rightbuffer[SIZE];
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
        va_end(s);

        // 格式:默认部分+自定义部分
        char logtxt[SIZE * 2];
        snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);

        // printf("%s", logtxt); // 暂时打印
        printLog(level, logtxt);
    }

private:
    int printMethod;
    std::string path;
};
Log log;

模拟服务器

#include "udpserver.hpp"
#pragma once
#include <memory>
#include <cstring>
#include <sys/types.h>          
#include <sys/socket.h>
#include <strings.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;

extern Log log;//声明变量log
enum{
    SOCKET_ERR = 1,//套接字创建失败
    BIND_ERR
};

uint16_t defaultport = 8080;//默认端口
//绑定端口号的时候要注意,很多端口都是被应用层协议固定使用[0,1023]这个区间:http:80 https:443等等
//建议使用1024以上,但也要注意,比如mysql:3306
string defaultip = "0.0.0.0";//默认ip,bind为0就是任意ip地址都可以进到服务器来

const int size = 1024;

class UdpServer
{
public:
    UdpServer(const uint16_t &port = defaultport, const string &ip = defaultip):sockfd_(0),port_(port),ip_(ip),isrunning_(false)
    {

    }
    void Init()
    {
        //1.创建udp socket
        sockfd_ = socket(AF_INET,SOCK_DGRAM,0);
        if(sockfd_ < 0)
        {
            log(Fatal,"socket create error, socket:%d",sockfd_);
            exit(SOCKET_ERR);
        }
        log(Info,"socket create success, socket:%d",sockfd_);
        //2.bind socket
        struct sockaddr_in local;
        bzero(&local,sizeof(local));
        local.sin_family = AF_INET;//设置自己为IPV4
        local.sin_port = htons(port_);//因为端口号需要给对方发送,所以必须要保证我的端口号是网络字节序列
        local.sin_addr.s_addr = inet_addr(ip_.c_str());
        //1.将string->uint32_t 2.必须是网络序列
        //sin_addr里面还有一个成员,s_addr才是真实的本体 
        //local.sin_addr.s_addr=INADDR_ANY;也是绑定任意IP地址的方法
        int n = bind(sockfd_,(const struct sockaddr *)&local,sizeof(local));
        if(n < 0)
        {
            log(Fatal, "bind error, error: %d, err string:%s",errno, strerror(errno));
            exit(BIND_ERR);
        }
        log(Info,"bind success, errno: %d, err string: %s",errno,strerror(errno));

    }
    void Run()
    {
        isrunning_ = true;
        char inbuffer[size];
        while(isrunning_)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client); 
            ssize_t n = recvfrom(sockfd_,inbuffer,sizeof(inbuffer)-1,0,(struct sockaddr*)&client,&len);
            if(n < 0)
            {
                log(Warning,"recvfrom error,errno: %d,err string:%s",errno,strerror(errno));
                continue;
            }
            string info = inbuffer;
            string echo_string = "server echo#" + info;
            sendto(sockfd_,echo_string.c_str(),echo_string.size(),0,(struct sockaddr*)&client,len);
        }
    }
    ~UdpServer()
    {
        if(sockfd_ > 0) close(sockfd_); 
    }
private:
    int sockfd_;//网络文件描述符
    string ip_;//服务器iP地址
    uint16_t port_;//服务器进程的端口号
    bool isrunning_;//服务器是否在运行
};

#include "log.hpp"
#include "udpserver.hpp"

void Usage(string proc)
{
    cout << "\n\rUsage:" << proc << "port[1024+]\n" << endl;
}
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));
    svr->Init();
    svr->Run();
    return 0;
}

客户端

#pragma once
#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <cstring>
#include <sys/types.h>          
#include <sys/socket.h>
#include <strings.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <strings.h>
using namespace std;

void Usage(string proc)
{
    cout << "\n\rUsage:" << proc << "port[1024+]\n" << endl;
}
int main(int argc,char* argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(0);
    }
    string serverip = argv[1];
    uint16_t serverport = stoi(argv[2]);

    struct sockaddr_in server;
    bzero(&server, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    server.sin_addr.s_addr = inet_addr(serverip.c_str());
    socklen_t len = sizeof(server);
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd < 0)
    {
        cout << "socket error" << endl;
        exit(1);
    }
    //客户端需要绑定,但是不需要显示绑定,由OS自己选择
    //为了防止进程端口出现冲突
    //OS什么时候给我绑定的呢?是在首次发送数据的时候
    string message;
    char buffer[1024];
    while(true)
    {
        cout << "Please Enter@ ";
        getline(cin,message);
      
        //1.数据2.发给谁
        
        sendto(sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,len);
        
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        ssize_t s = recvfrom(sockfd,buffer,1023,0,(struct sockaddr*)&temp,&len);
        if(s > 0)
        {
            buffer[s] = 0;
            cout << buffer <<endl;
        }
    }
    close(sockfd);
    return 0;
}

Socket 编程 TCP

测试服务器工具,指定服务器远程登陆:

telnet 127.0.0.1 端口号
127.0.0.1表示本地环回。

第一个函数也是将字符串的ip转换成网络四字节的ip。
在这里插入图片描述
因为TCP是面向连接的,服务器比较被动,一直处于等待链接到来的状态,所以用监听的方式查看是否有客户端到来。
这个函数是将套接字设置监听状态:

int listen(int sockfd, int backlog);
第二个参数等后面TCP协议在进行解释

在这里插入图片描述
接收消息函数,并获知哪个客户端连接上了自己:

int accept4(int sockfd, struct sockaddr *addr,socklen_t *addrlen, int flags);
这里返回值也是一个套接字,那么我们第一个参数也是套接字,有什么区别呢?
我们输入的套接字就相当于饭店外面的接待员,并不参与真正的服务当中,返回值的套接字才是真正的服务员,服务被接待过的客人。
也就是说,第一个参数的套接字仅仅是帮助我们获取新连接的工具人。

在这里插入图片描述
通过指定的套接字发送连接。

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

在这里插入图片描述
这个客户端与服务器的程序还是要用到上面log的代码:
服务器

#pragma once
#include "log.hpp"
#include <memory>
#include <sys/socket.h>
#include <cstdlib>
#include <cstdlib>
#include <cstring>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string>
#include <pthread.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
using namespace std;
extern Log log;
const int defaultfd = -1;
const string defaultip = "0.0.0.0";
const int backlog = 10;
enum 
{
    UsageError = 1,
    SocketError,
    BindError,
    ListenError
};
class TcpServer;
class ThreadData
{
public:
    ThreadData(int fd, const string &ip, const uint16_t &p, TcpServer *t): sockfd(fd), clientip(ip), clientport(p), tsvr(t)
    {}
public:
    int sockfd;
    string clientip;
    uint16_t clientport;
    TcpServer *tsvr;
};
class TcpServer
{
public:
    TcpServer(const uint16_t &port,const string &ip = defaultip):listensock_(defaultfd),port_(port),ip_(ip)
    {

    }
    void InitServer()
    {
        listensock_ = socket(AF_INET,SOCK_STREAM,0);
        if(listensock_ < 0)
        {
            log(Fatal,"create socket,errno: %d,errstring: %s",errno,strerror(errno));
            exit(SocketError);
        }
        log(Info,"create socket success, sockfd:%d",listensock_);
        struct sockaddr_in local;
        memset(&local,0,sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(port_);
        inet_aton(ip_.c_str(),&(local.sin_addr));
        if(bind(listensock_,(struct sockaddr*)&local,sizeof(local)) < 0)
        {
            log(Fatal,"bind error, errno: %d, errstring:%s",errno,strerror(errno));
            exit(BindError);
        }
        if(listen(listensock_,backlog)<0)
        {
            log(Fatal, "listen error, errno: %d, errstring: %s", errno, strerror(errno));
            exit(ListenError);
        }
        log(Info, "listen socket success, listensock_: %d", listensock_);
    }
    static void *Routine(void *args)
    {
        pthread_detach(pthread_self());//分离状态不用让主线程去等待,互不影响
        ThreadData *td = static_cast<ThreadData *>(args);
        td->tsvr->Service(td->sockfd, td->clientip, td->clientport);
        delete td;
        return nullptr;
    }
    void Start()
    {
        log(Info, "tcpServer is running....");
        for(;;)
        {
            //1.获取新连接
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(listensock_,(struct sockaddr*)&client,&len);
            if(sockfd < 0)
            {
                log(Warning, "accept error, errno: %d, errstring: %s", errno, strerror(errno)); //获取一个失败不必推出,再次进行下一个获取即可
                continue;
            }
            uint16_t clientport = ntohs(client.sin_port);
            char clientip[32];
            inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));
            //2. 根据新连接来进行通信
            log(Info, "get a new link..., sockfd: %d, client ip: %s, client port: %d", sockfd, clientip, clientport);
            //单进程版本,无法让多个客户端进行连接
            /*Service(sockfd, clientip,clientport);
            close(sockfd);*/
            //多进程版,让子进程去处理客户端,父进程继续向下执行。创建多个进程就能连接多个客户端
            /*pid_t id = fork();
            if(id == 0)
            {
                //child
                close(listensock_);//这个是父进程用的fd,防止误操作
                if(fork() > 0) exit(0);//因为是阻塞等待,所以让子进程再创建子进程去处理客户端
                Service(sockfd, clientip, clientport); //孙子进程来处理客户端,system 领养
                close(sockfd);
                exit(0);
            }
            close(sockfd);//这里必须关闭,因为sockfd已经交给子进程处理了,父进程不需要在管理了,不然父进程的文件描述符会越用越少
            // father
            pid_t rid = waitpid(id, nullptr, 0);
            (void)rid;*/
            //多线程版
            ThreadData *td = new ThreadData(sockfd, clientip, clientport, this);
            pthread_t tid;
            pthread_create(&tid, nullptr, Routine, td);
        }
    }
    void Service(int sockfd,const string& clientip,const uint16_t &clientport)//因为是面向字节流的,所以用read和write对网络文件进行读写即可
    {
        char buffer[4096];
        while (true)
        {
            ssize_t n = read(sockfd, buffer, sizeof(buffer));
            if (n > 0)
            {
                buffer[n] = 0;
                cout << "client say# " << buffer << endl;
                string echo_string = "tcpserver echo# ";
                echo_string += buffer;
    
                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else if (n == 0)//客户端退出,就会关闭套接字
            {
                log(Info, "%s:%d quit, server close sockfd: %d", clientip.c_str(), clientport, sockfd);
                break;
            }
            else
            {
                log(Warning, "read error, sockfd: %d, client ip: %s, client port: %d", sockfd, clientip.c_str(), clientport);
                break;
            }
        }
    }
    ~TcpServer()
    {

    }
private:
    int listensock_;
    string ip_;
    uint16_t port_;
};
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
using namespace std;
void Usage(const string &proc)
{
    cout << "\n\rUsage: " << proc << " serverip serverport\n"
              << endl;
}
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    string serverip = argv[1];
    uint16_t serverport = stoi(argv[2]);

    //TCP方式的客户端bind实在什么时候呢?
    //是在connect的时候OS自动绑定
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    inet_pton(AF_INET, serverip.c_str(), &(server.sin_addr));
    int sockfd = 0;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        cerr << "socket error" << endl;
        return 1;
    }
    int n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));
    if (n < 0)
    {
        cerr << "connect error..., reconnect: "<< endl;
    }
    while(true)
    {
        string message;
        cout << "Please Enter# ";
        getline(cin, message);
        int n = write(sockfd, message.c_str(), message.size());
        if (n < 0)
        {
            cerr << "write error..." << endl;
            
        }
        char inbuffer[4096];
        n = read(sockfd, inbuffer, sizeof(inbuffer));
        if (n > 0)
        {
            inbuffer[n] = 0;
            cout << inbuffer << endl;
        }
    }
    close(sockfd);
    return 0;
}

客户端

#include "tcpserver.hpp"
void Usage(std::string proc)
{
    std::cout << "\n\rUsage: " << proc << " port[1024+]\n" << std::endl;
}
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(UsageError);
    }
    uint16_t port = std::stoi(argv[1]);
    std::unique_ptr<TcpServer> tcp_svr(new TcpServer(port));
    tcp_svr->InitServer();
    tcp_svr->Start();
    return 0;
}

守护进程

两个人在使用一个服务器,那么服务器就会生成两个“会话”(session),会话里面包含了命令行解释器(bash),和多个进程。
在这里插入图片描述
眼下,两个bash都是前台进程,其他的都是后台进程,并且一个会话当中只能有一个前台进程。
无论前台还是后台进程都可以向显示器进行打印,但是能用键盘的只有前台。(比如说将bash变成后台,另一个进程变成前台进程,Ctrl+C就能让这个进程停止,然后将bash换回到前台)——谁拥有键盘文件谁就是前台。

如何将进程后台运行呢?
只需要在启动可执行文件的时候加上一个&即可。
如果想让进程变成前台需要

fg+任务号

用jobs指令来查看后台任务号。

如果某一个前台进程被暂停之后放到后台了,想让这个后台继续运行:

bg+任务号

进程间的关系
在这里插入图片描述
这里的PGID是进程组的ID,SID是会话的ID。
会话也是要被OS组织管理的。
第一个进程PID和PGID是一样的,说明这个进程自成一组。
剩下三个PGID一样说明他们三个是一组。(一般来说,第一个进程是组长)
组长是组内中PID和PGID相同的进程。

平时所有任务其实都是进程组在完成!
也就是说,前台进程和后台进程其实并不正确,应该叫做前台任务和后台任务!

那么SID的ID是谁呢?
其实就是bash。

如果客户端退出了呢?
在这里插入图片描述

TTY全变成了?也就是说跟终端无关了。
TPGID变成了1。
并且退出的客户端的进程全都被OS给领养了。
也就是说这些进程收到了客户端登录和退出的影响。

如果不想让这些进程受到客户端的影响,那么这就叫守护进程化。(也叫精灵进程)

注销
windowsOS党总有一个操作叫做注销,注销就是将整个会话给关闭。
当重新登录的时候就相当于重新创建一个会话。

守护进程
如何进行守护进程化呢?
那就是让一个会话当中的某个进程脱离当前会话,自成一个会话,上一个会话进行销毁也就和这个进程无关了。

函数接口

#include <unistd.h>
pid_t setsid(void);
谁调用这个函数谁就被设置成为守护进程,成功返回一个新的pid,失败返回-1.

在这里插入图片描述
但是这个函数不会让这个进程成为新会话的组长。
可是新的会话只有这个进程,那怎么办呢?只要不让这个进程是第一个进程就好了。

if(fork()>0) exit(0);
srtsid();

所以守护进程的本质也是孤儿进程。

如果程序生成一个守护进程(以服务器举例),分为以下几个步骤:

1.忽略部分异常信号
2.将自己变成独立会话
3.更改当前调用进程的工作目录
4.标准输入输出错误不要在打印到屏幕上,重定向到/dev/null(也可以放在一个文件里形成文件的日志)

这样就能让一个服务器在后台持久运行了。
注意:守护进程命名习惯是后面以d为结尾。

让进程和以上效果相同的函数:
第一个参数是设置为0是将工作目录设置为根目录,否则就是当前目录,
第二个参数是设置为0是将标准输入输出错误重定向到/dev/null。
在这里插入图片描述

TCP简单的特性

三次握手与四次挥手
TCP会三次握手来进行链接的建立:
在这里插入图片描述
通过四次挥手进行释放:
在这里插入图片描述

注意:TCP是全双工的。(可以互相通信)
那么为什么不会相互收到影响呢?
在这里插入图片描述
因为在两个客户端当中,双方网络文件的上层都有自己的两种缓冲区,下层也是,所以不会冲突。(双方资源是隔离的)
也就是说我们上面用的read和write都是在对网络上下的两个缓冲区之间进行拷贝。

但TCP是面向字节流的,我们如何保证都上来的是一个完整的报文?
在用read和write的时候,TCP会有一个传输控制协议!
什么时候发,发多少,出错了如何解决?
也就是说write写的时候,其实从自己的缓冲区发送到网络的缓冲区就直接返回了,我们并不知道到底有没有发送到对方手里,因为这是TCP决定的。(其实就是给了OS,因为TCP也是在OS当中实现的,也就是TCP网络模块)
同理,read也是一样的。

所以这就需要协议定制,序列化和反序列化。

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

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

相关文章

看爬山虎学本领 软爬机器人来创新 各种场景能适应

*本文只做阅读笔记分享* 一、灵感来源&#xff1a;向植物取经 大家好&#xff01;今天来聊一款超酷的软爬机器人&#xff0c;它的灵感来自会攀爬的植物——爬山虎。 大家都知道&#xff0c;爬墙高手爬山虎能在各种复杂墙面轻松生长攀爬&#xff0c;可现有的攀爬机器人在复杂…

极客天成NVFile:无缓存直击存储性能天花板,重新定义AI时代并行存储新范式

在AI算力需求呈指数级爆发的今天&#xff0c;存储系统正面临一场前所未有的范式革命。传统存储架构中复杂的缓存机制、冗余的数据路径、僵化的扩展能力&#xff0c;已成为制约千卡GPU集群算力释放的重要因素。极客天成NVFile并行文件存储系统以全栈并行化架构设计和无缓存直通数…

Java实现N皇后问题的双路径探索:递归回溯与迭代回溯算法详解

N皇后问题要求在NN的棋盘上放置N个皇后&#xff0c;使得她们无法互相攻击。本文提供递归和循环迭代两种解法&#xff0c;并通过图示解释核心逻辑。 一、算法核心思想 使用回溯法逐行放置皇后&#xff0c;通过冲突检测保证每行、每列、对角线上只有一个皇后。发现无效路径时回退…

谷歌开源单个 GPU 可运行的Gemma 3 模型,27B 超越 671B 参数的 DeepSeek

自从 DeepSeek 把训练成本打下来之后&#xff0c;各个模型厂家现在不再堆参数进行模型的能力对比。而是转向了训练成本优化方面&#xff0c;且还要保证模型能力不减反增的效果。包括使用较少的模型参数&#xff0c;降低 GPU 使用数量&#xff0c;降低模型内存占用等等技术手段。…

C++_类和对象(下)

【本节目标】 再谈构造函数Static成员友元内部类匿名对象拷贝对象时的一些编译器优化再次理解封装 1. 再谈构造函数 1.1 构造函数体赋值 在创建对象时&#xff0c;编译器通过调用构造函数&#xff0c;给对象中各个成员变量一个合适的初始值。 class Date { public:Date(in…

Docker设置代理

目录 前言创建代理文件重载守护进程并重启Docker检查代理验证 前言 拉取flowable/flowable-ui失败&#xff0c;用DaoCloud源也没拉下来&#xff0c;不知道是不是没同步。索性想用代理拉镜像。在此记录一下。 创建代理文件 创建docker代理配置 sudo mkdir -p /etc/systemd/s…

一键自动备份:数据安全的双重保障

随着数字化时代的到来&#xff0c;数据已成为企业和个人不可或缺的核心资产。在享受数据带来的便捷与高效的同时&#xff0c;数据丢失的风险也随之增加。因此&#xff0c;备份文件的重要性不言而喻。本文将深入探讨备份文件的重要性&#xff0c;并介绍两种实用的自动备份方法&a…

HeidiSQL:多数据库管理工具

HeidiSQL 是一款广受欢迎的免费开源数据库管理工具&#xff0c;专为数据库管理员及开发者设计。无论您是刚接触数据库领域的新手&#xff0c;还是需要同时处理多种数据库系统的专业开发者&#xff0c;该工具都能凭借其直观的界面和强大的功能&#xff0c;助您轻松完成数据管理任…

医药档案区块链系统

1. 医生用户模块​​ ​​目标用户​​&#xff1a;医护人员 ​​核心功能​​&#xff1a; ​​检索档案​​&#xff1a;通过关键词或筛选条件快速定位患者健康档案。​​请求授权​​&#xff1a;向个人用户发起档案访问权限申请&#xff0c;需经对方确认。​​查看档案​…

蓝桥云客--浓缩咖啡液

4.浓缩咖啡液【算法赛】 - 蓝桥云课 问题描述 蓝桥杯备赛选手小蓝最近刷题刷到犯困&#xff0c;决定靠咖啡续命。他手上有 N 种浓缩咖啡液&#xff0c;浓度分别是 A1​%, A2​%, …, AN​%&#xff0c;每种存货都是无限的。为了提神又不炸脑&#xff0c;小蓝需要按比例混合这…

SQLark(百灵连接):一款面向信创应用开发者的数据库开发和管理工具

SQLark&#xff08;百灵连接&#xff09;是一款面向信创应用开发者的数据库开发和管理工具&#xff0c;用于快速查询、创建和管理不同类型的数据库系统。 目前可以支持达梦数据库、Oracle 以及 MySQL。 SQL 智能编辑器 基于语法语义解析实现代码补全能力&#xff0c;为你提供…

计算机视觉——为什么 mAP 是目标检测的黄金标准

概述 在目标检测领域&#xff0c;有一个指标被广泛认为是衡量模型性能的“黄金标准”&#xff0c;它就是 mAP&#xff08;Mean Average Precision&#xff0c;平均精确率均值&#xff09;。如果你曾经接触过目标检测模型&#xff08;如 YOLO、Faster R-CNN 或 SSD&#xff09;…

Frame Of Reference压缩算法

文章目录 1_概述2_算法基本步骤3_过程优化4_优势以及局限5_模拟实现6_总结 1_概述 Frame of Reference&#xff08;FoR&#xff09;压缩算法 是一种用于压缩数值数据的算法&#xff0c;特别是在处理大规模数据集时&#xff0c;利用数据的局部性和重复性来减少存储和传输的开销…

esp32cam 开发板搭载ov3660摄像头在arduino中调用kimi进行图像识别

首先呢,最近搞一个项目,需要一个摄像头拍摄图片 就买了个ov3660开发板,用的esp32S芯片 淘宝商家给的教程是arduino的,所以先用arduino跑起来 arduino配置esp32-cam开发环境 - 简书1、安装arduino https://www.arduino.cc/en/Main/Software?setlang=cn 2、配置esp32 打开…

二十种中药果实识别分类系统,Python/resnet18/pytorch

二十种中药果实识别分类系统,Python/resnet18/pytorch 基于pytorch训练, resnet18网络&#xff0c;可用于训练其他分类问题&#xff0c;也可自己重新训练 20类中药材具体包括&#xff1a;(1) 补骨脂&#xff0c;(2) 草豆蔻&#xff0c;(3) 川楝子&#xff0c;(4) 地肤子&…

如何实现两个视频融合EasyCVR平台的数据同步?详细步骤指南

有用户咨询&#xff0c;现场需要数据库同步&#xff0c;如何将两个EasyCVR平台的数据进行同步呢&#xff1f; 这篇文章我们将详细介绍如何通过简单的接口调用&#xff0c;高效完成两个平台的数据同步操作。 1&#xff09;获取token 使用Postman调用登录接口&#xff0c;获取…

WindowsPE文件格式入门05.PE加载器LoadPE

https://bpsend.net/thread-316-1-1.html LoadPE - pe 加载器 壳的前身 如果想访问一个程序运行起来的内存,一种方法就是跨进程读写内存,但是跨进程读写内存需要来回调用api,不如直接访问地址来得方便,那么如果我们需要直接访问地址,该怎么做呢?.需要把dll注进程,注进去的代码…

使用Cusor 生成 Figma UI 设计稿

一、开发环境 系统&#xff1a;MacOS 软件版本&#xff1a; Figma&#xff08;网页或APP版&#xff09; 注&#xff1a;最好是app版&#xff0c;网页版figma 没有选项 import from manifest app下载地址&#xff1a;Figma Downloads | Web Design App for Desktops & …

Golang的文件同步与备份

Golang的文件同步与备份 一、Golang介绍 也称为Go语言&#xff0c;是谷歌开发的一种编程语言&#xff0c;具有高效的并发编程能力和出色的内存管理。由于其快速的编译速度和强大的标准库&#xff0c;Golang在网络应用、云平台和大数据等领域得到了广泛应用。 二、文件同步与备份…

如何用人工智能大模型,进行作业批改?

今天我们学习人工智能大模型如何进行作业批改。手把手学习视频请访问https://edu.csdn.net/learn/40402/666452 第一步&#xff0c;进入讯飞星火。打开google浏览器&#xff0c;输入百度地址后&#xff0c;搜索”讯飞星火”&#xff0c;在搜索的结果中&#xff0c;点第一个讯飞…