Linux——socket套接字与udp通信

目录

一、理解IP地址与端口

二、socket套接字

三、TCP与UDP的关系

四、网络字节序

五、socket编程

1.socket()创建套接字

2.填充sockaddr_in 结构体

3.bind() 绑定信息

4.recvfrom()接收消息

5.sendto()发送消息

六、UdpServer代码


一、理解IP地址与端口

IP地址是Internet Protocol(互联网协议)的缩写,是网络上用于标识和定位设备的唯一地址

他分为公网IP与内网IP,我们通常使用并且查询到的都是内网IP,如下,查询到的就是内网ip。

公网IP地址和内网IP地址共同构成了一个设备的所有IP地址,公网IP地址用于设备与互联网上的其他设备通信,而内网IP地址用于设备在局域网内部通信。

而在IP数据包头部中, 有两个IP地址, 分别叫做源IP地址, 和目的IP地址,直到的从哪里来到哪里去,我们才能将消息发到对方设备上。

但是仅仅只知道IP地址还不够,比如你聊QQ的时候,消息不仅仅要到你的电脑中,还得在QQ中显示出来,因此消息还得认识端口号。

端口号(port)是传输层协议的内容

  • 端口号是一个2字节16位的整数;
  • 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
  • IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
  • 一个端口号只能被一个进程占用.

由此可以看出,网络通信的本质都是进程间通信,只不过这两个进程不一定在同一台电脑上。ip地址用来表示互联网中唯一的一台主机,端口号用来指定该机器中唯一的进程。 

二、socket套接字

这种IP地址和端口号的组合通常被称为套接字(socket)。

  • 一般情况,一个端口号和一个进程相关联。
  • 特殊情况,一个进程可以绑定多个端口号,但是一个端口号不能被多个进程绑定。

比如A进程绑定了很多端口号,那么往这些端口号发送的数据都会来到A这里。但是B端口号绑定了很多进程,那往B端口号发送数据,到底给哪一个进程呢?如果全部都给岂不是数据会很冗余,因此一端口号不能被多个进程绑定。

三、TCP与UDP的关系

TCP(传输控制协议)和UDP(用户数据报协议)是两种常用的网络传输协议。

TCP的特点

  1. 传输层协议
  2. 有连接
  3. 可靠传输
  4. 面向字节流

UDP的特点

  1. 传输层协议
  2. 无连接
  3. 不可靠传输
  4. 面向数据报

目前,我们只需要知道TCP有连接,并且可靠,UDP无连接,并且不可靠,字节流与数据包后续再讨论。

由于TCP有连接并可靠,因此需要付出更多的代价去完成TPC通信,适合如支付、发送机密文件重要场景。

UDP无连接并不可靠,因此要简单一些,适合视频通话、打游戏(低延迟)等场景

四、网络字节序

按数据在计算机存储中的排列方式的分为大端机与小端机。

比如在内存中的数据为 0x11223344 

如果是大端机就是低位字节处存高地址,高位字节处存低地址。

而小端机就是低位字节处存低地址,高位字节处存高地址。

既然无法保证所有的电脑都是小端机或者大端机,那么在网络通信的时候,需要考虑数据的字节序问题,通常需要进行字节序的转换。

所以网络规定:所有到达网络的数据,必须是大端的,因此所有从网络收到数据的机器,都知道数据是大端的。

所有数据具在发送到网络时,都必须要做小端到大端的转化,你是大端,直接发给网络就可以,你是小段,先转化完再发给网络。数据在到达机器时,也要做相应的判断,进行合适的转化。

如下接口可以进行相应的转化。

这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。

  • 例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
  • 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回
  • 如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。 

五、socket编程

如下是socket编程的重要接口

// 创建 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);

其中有一个struct sockaddr* address。 

在网络编程的时候,socket分了很多类别如域间socket、网络socket,原始socket等等每一个socket都有自己独特的一些东西,但他们的本质都是socket。因此我们可以使用struct sockaddr* 这个指针来指向所有的socket。实现了C风格的多态。

今天我们着重学习一下网络socket。

1.socket()创建套接字

使用函数socket(int domain,int type,int protocol)接口创建socket。

网络socket的domain为AF_INIT,使用UDP数据包 type 为 SOCK_DGRAM,protocol默认填0。

Linux一切皆文件,这样我们就可以得到一个文件描述符,后面就可以通过该文件描述符在网络上进行通信。

2.填充sockaddr_in 结构体

有了文件描述符,还需要填充sockaddr_in结构体里面的字段,sockaddr 是一个通用的套接字地址结构体,而sockaddr_in则专门为网络socket设计的,他是用于IPv4套接字的地址结构体。

里面有

  • sin_family,代表使用哪个地址族,一般填 AF_INET 代表IPv4
  • sin_addr.s_addr,代表那个ip地址
  • sin_port,代表那个端口号

3.bind() 绑定信息

现在有了网络套接字并填充了sockaddr_in字段,但是由于现在这些内容还是栈上面变量,因此需要通过bind将该套接字绑定到指定的网络地址上,后面就可以通信了。

4.recvfrom()接收消息

recvfrom可以接受消息,接受消息放到buf里,预期接受len个字节的消息,flags默认为0,代表的阻塞模式,src_addr与addrlen为输入输出参数,输出发送放相关sokect消息。(因为有其他网络中的线程进行发送,你来接受,你输出的是你发送方的套接字地址结构体)

5.sendto()发送消息

 接口与recevfrom类似,位于区别是adrlen不需要传地址。

 通过这些操作与接口,就可以进行网络通信了,话不多说,我们直接用来看看。 

六、UdpServer代码

Comm.hpp(错误码头文件)

#pragma once

//错误码
enum{
    Usage_Err = 1,
    Socket_Err,
    Bind_Err,
};

 InetAddr.hpp(网络套接字地址结构体封装)

#pragma once
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
using namespace std;
class InetAddr
{
public:
    InetAddr(struct sockaddr_in& peer):_addr(peer)
    {
        _port = ntohs(peer.sin_port); // ntohs网络转主机short
        _ip = inet_ntoa(peer.sin_addr); // inet_ntoa sin_addr转点分十进制字符串ip
    }
    string GetIp()
    {
        return _ip;
    }
    uint16_t GetPort()
    {
        return _port;
    }
    
    string PrintDebug()
    {
        string info = _ip;
        info+=":";
        info+=to_string(_port);
        return info;
    }
    ~InetAddr()
    {
    }

private:
    string _ip;
    uint16_t _port;
    struct sockaddr_in _addr;
};

Log.hpp(日志文件)

#pragma once

#include<iostream>
#include<cstdarg>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
using namespace std;
enum{
    Debug = 0,
    Info,
    Warnig,
    Error,
    Fatal
};

enum{
    Screen = 10,
    OneFile,
    ClassFile
};

string LevelToString(int level)
{
    switch (level)
    {
    case Debug:
        return "Debug";
    case Info:
        return "Info";
    case Warnig:
        return "Warning";
    case Error:
        return "Error";
    case Fatal:
        return "Fatal";
    
    default:
        return "Unkonw"; 
    }
}
 
const int default_style = Screen;
const string default_filename = "Log.";
const string logdir = "log";

class Log  
{
public:
    Log(int style = default_style,string filename = default_filename)
        :_style(style),_filename(filename)
    {
        if(_style != Screen)
            mkdir(logdir.c_str(),0775);
    }

    //更改打印方式
    void Enable(int style)
    {
        _style = style;
    }

    //时间戳转化为年月日时分秒
    string GetTime()
    {
        time_t currtime = time(nullptr);
        struct tm* curr = localtime(&currtime);
        char time_buffer[128];
        snprintf(time_buffer,sizeof(time_buffer),"%d-%d-%d %d:%d:%d",
        curr->tm_year+1900,curr->tm_mon+1,curr->tm_mday,curr->tm_hour,curr->tm_min,curr->tm_sec);
        return time_buffer;
    }

    //写入到文件中
    void WriteLogToOneFile(const string& logname,const string& message)
    {
        FILE* fp = fopen(logname.c_str(),"a");
        if(fp==nullptr)
        {
            perror("fopen filed");
            exit(-1);
        }
        fprintf(fp, "%s\n", message.c_str());

        fclose(fp);
    }

    //打印日志
    void WriteLogToClassFile(const string& levelstr,const string& message)
    {
        string logname = logdir;
        logname+="/";
        logname+=_filename;
        logname+=levelstr;
        WriteLogToOneFile(logname,message);
    }

    void WriteLog(const string& levelstr,const string& message)
    {
        switch (_style) 
        {
        case Screen:
            cout<<message<<endl;//打印到屏幕中
            break;
        case OneFile:
            WriteLogToClassFile("all",message);//给定all,直接写到all里
            break;
        case ClassFile:
            WriteLogToClassFile(levelstr,message);//写入levelstr里
            break;
        default:
            break;
        }
    }

    //打印日志
    void LogMessage(int level,const char* format,...)
    {
        char rightbuffer[1024];//处理消息
        va_list args;   //va_list 是指针
        va_start(args,format);//初始化va_list对象,format是最后一个确定的参数
        //现在args指向了可变参数部分
        vsnprintf(rightbuffer,sizeof(rightbuffer),format,args);//写入到leftbuffer中
        
        va_end(args);

        char leftbuffer[1024];//处理日志等级、pid、时间
        string levelstr = LevelToString(level);
        string currtime = GetTime();
        string idstr = to_string(getpid());

        snprintf(leftbuffer,sizeof(leftbuffer),"[%s][%s][%s]",levelstr.c_str()
        ,currtime.c_str(),idstr.c_str());

        string loginfo = leftbuffer;
        loginfo+=rightbuffer;
        WriteLog(levelstr,loginfo);
    }

    //提供接口给运算符重载使用
    void _LogMessage(int level,char* rightbuffer)
    {
        char leftbuffer[1024];
        string levelstr = LevelToString(level);
        string currtime = GetTime();
        string idstr = to_string(getpid());

        snprintf(leftbuffer,sizeof(leftbuffer),"[%s][%s][%s]",levelstr.c_str()
        ,currtime.c_str(),idstr.c_str());

        string messages = leftbuffer;
        messages+=rightbuffer;
        WriteLog(levelstr,messages);
    }

    //运算符重载
    void operator()(int level,const char* format,...)
    {
        char rightbuffer[1024];
        va_list args;   //va_list 是指针
        va_start(args,format);//初始化va_list对象,format是最后一个确定的参数
        vsnprintf(rightbuffer,sizeof(rightbuffer),format,args);//写入到leftbuffer中
        va_end(args);
        _LogMessage(level,rightbuffer);
    }

    ~Log() 
    {}
private:
    int _style;
    string _filename;
};

Log lg;

class Conf
{
public:
    Conf()
    {
        lg.Enable(Screen); 
    }
    ~Conf()
    {}
};

Conf conf;

 nocopy.hpp(服务端继承该文件,进栈拷贝构造与赋值拷贝构造)

#pragma once

class nocopy
{
public:
    nocopy(){}
    
    nocopy(const nocopy& n) = delete;
    nocopy& operator=(const nocopy& n) = delete;
    ~nocopy(){}
}; 

 UdpServer.hpp(服务器封装的类)

#pragma once

#include <iostream>
#include <string>
#include <cstring>

#include <sys/types.h>          //网络常用4小只
#include <sys/socket.h>         //网络常用4小只
#include <netinet/in.h>         //网络常用4小只
#include <arpa/inet.h>          //网络常用4小只

#include <cerrno>
#include "nocopy.hpp"
#include "Log.hpp"
#include "Comm.hpp"
#include "InetAddr.hpp"

using namespace std;

const static string defaultip = "0.0.0.0";
const static uint16_t defaultport = 8888;
const static int defaultfd = -1;

class UdpServer :public nocopy
{
public:
    //固定绑定ip才需要 _ip
    // UdpServer(const string& ip = defaultip,uint16_t port = defaultport)
    //     :_ip(ip),_port(port),_sockfd(defaultfd)
    // {
    // }

    UdpServer(uint16_t port = defaultport)
        :_port(port),_sockfd(defaultfd)
    {
    }
    void Init()
    {
        //1.创建套接字  AF_INET表示网络套接字 SOCK_DGRAM 表示 UDP 协议的数据报套接字
        _sockfd = socket(AF_INET,SOCK_DGRAM,0);
        if(_sockfd<0)
        {
            lg.LogMessage(Fatal,"socket error, %d : %s",errno,strerror(errno));//打印日志
            exit(Socket_Err);
        }
        lg.LogMessage(Info,"socket success, sockfd : %d", _sockfd);

        //2.填充 sockaddr_in 结构体 指定网络信息
        struct sockaddr_in local;
        memset(&local,0,sizeof(local));
        local.sin_family = AF_INET;         //表示要使用的是 IPv4 地址
        // local.sin_addr.s_addr = inet_addr(_ip.c_str()); //inet_addr将字符串转32位二进制整数(固定绑定)

        local.sin_addr.s_addr = INADDR_ANY;//IP动态绑定(不固定)
        local.sin_port = htons(_port);  //_port主机转网络

        //绑定,让设备知道该结构体中的网络与端口
        int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
        if(n!=0)
        {
            //绑定失败
            lg.LogMessage(Fatal,"bind error, %d : %s",errno,strerror(errno));
            exit(Bind_Err);
        }
    }
    void Start()
    {
        //服务器一直要接受消息,永不退出
        char buff[1024];
        for(;;)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);

            //接受消息放到buff里,预期接受sizeof(buff)-1个字节的消息,peer为输入输出参数,输出clien的sokect消息
            ssize_t n = recvfrom(_sockfd,buff,sizeof(buff)-1,0,(struct sockaddr*)&peer,&len);
            if(n>0)
            {
                InetAddr addr(peer);


                buff[n] = 0;
                cout<<"["<<addr.PrintDebug()<<"]# " <<buff<<endl;

                //sendto 发送消息给peer
                ssize_t m = sendto(_sockfd,buff,strlen(buff),0,(struct sockaddr*)&peer,len);
            }
        }
    }
    ~UdpServer()
    {
    }

private:
    // string _ip;(固定绑定服务器ip才需要,我们选择不固定,动态绑定
    uint16_t _port;
    int _sockfd;
};

 UdpClient.cc (客户端)

#include<iostream>
#include <cerrno>
#include <cstring>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include "Comm.hpp"

using namespace std;

void Usage(string proc)
{
    cout<<"Usage: \n\t" <<proc<<"local_ip local_port\n"<<endl;
}

int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        return Usage_Err;
    }
    string serverip = argv[1];
    uint16_t serverport = stoi(argv[2]);

    //创建套接字
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd<0)
    {
        cerr<<"socket error: "<<strerror(errno)<<endl; 
        exit(Socket_Err);
    }
    cout<<"create socket success, sockfd:"<<sockfd<<endl;

    //客户端需要bind,但是不需要显示bind(手动发生数据自动bind)
    //因为服务器的端口号只有一个,是总所周知并且固定的,客户端会很多并不一定在线

    //填充server信息
    struct sockaddr_in server;
    memset(&server,0,sizeof(server));
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(serverip.c_str());
    server.sin_port = htons(serverport);
    socklen_t len = sizeof(server);

    while(true)
    {
        string inbuffer; 
        cout<<"Please Enter#";
        getline(cin,inbuffer);

        //给server发送消息
        ssize_t n = sendto(sockfd,inbuffer.c_str(),inbuffer.size(),0,(struct sockaddr*)&server,len);
        if(n > 0)
        {
            char buff[1024];
            //发送成功,并且接收消息
            struct sockaddr_in tmp;
            socklen_t tmp_len = sizeof(tmp);
            ssize_t m = recvfrom(sockfd,buff,sizeof(buff)-1,0,(struct sockaddr*)&tmp,&len);
            if(m>0)
            {
                //接受成功
                buff[m] = 0;
                cout<<"server echo# "<<buff<<endl;
            }
        }
    }

    return 0;
}

 Main.cc (服务器的主函数)

#include "UdpServer.hpp"
#include"Comm.hpp"
#include<memory>

//固定绑定ip
// void Usage(string proc)
// {
//     cout<<"Usage: \n\t" <<proc<<"local_ip local_port\n"<<endl;
// }

// int main(int argc,char* argv[])
// {
//     if(argc!=3)
//     {
//         Usage(argv[0]);
//         return Usage_Err;
//     }

//     string ip = argv[1];
//     uint16_t port = stoi(argv[2]);

//     unique_ptr<UdpServer> usvr (new UdpServer(ip,port));
//     usvr->Init();
//     usvr->Start();
//     return 0;
// }

//动态绑定ip
void Usage(string proc)
{
    cout<<"Usage: \n\t" <<proc<<"local_port\n"<<endl;
}
int main(int argc,char* argv[])
{
    if(argc!=2)
    {
        Usage(argv[0]);
        return Usage_Err;
    }

    uint16_t port = stoi(argv[1]);

    unique_ptr<UdpServer> usvr (new UdpServer(port));
    usvr->Init();
    usvr->Start();
    return 0;
}

 Makefile(进行一键构造)

.PHONY:all
all:udp_server udp_client
udp_server:Main.cc
	g++ -o $@ $^ -std=c++11
udp_client:UdpClient.cc
	g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm -f udp_server udp_client

运行结果如下,客户端可以给服务器发消息,服务器将客户端的发来消息做回应。

代码地址 

1.test为本文内容。

2.udp_server_excute为远程发送bash指令

3.udp_server_chat为多人聊天室。

谢谢大家观看!!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Leetcode—1256. 加密数字【中等】Plus(bitset、find_first_not_of、erase)

2024每日刷题&#xff08;120&#xff09; Leetcode—1256. 加密数字 实现代码 class Solution { public:string encode(int num) {string ans;num 1;while(num ! 0) {ans to_string(num & 1);num num >> 1;}if(ans.empty()) {return "";} else {stri…

17 如何设计一锤子买卖的SDK

在前三个模块里&#xff0c;我将微服务根据目的性划分为三大类&#xff1a;读、写与扣减类&#xff0c;并针对每一大类涉及的各项技术问题讲解了应对方案。其实&#xff0c;每一类微服务除了本身业务特点涉及的技术问题外&#xff0c;在纯技术维度也有很多共性问题&#xff0c;…

房产中介小程序高效开发攻略:从模板到上线一站式服务

对于房产中介而言&#xff0c;拥有一个高效且用户友好的小程序是提升业务、增强客户黏性的关键。而采用直接复制模板的开发方式&#xff0c;无疑是实现这一目标的最佳途径&#xff0c;不仅简单快捷&#xff0c;而且性价比极高。 在众多小程序模板开发平台中&#xff0c;乔拓云网…

docker容器通俗理解

前言 如果大家没使用过Docker,就在电脑上下载一个VMware Workstation Pro&#xff0c;创建一个虚拟机安装一个windows操作一下感受一下,为什么我的电脑上还以再安装一台windows主机&#xff1f;其实你可以理解为Docker就是Linux系统的一个虚拟机软件。 我的Windows也可以安装…

WMS仓库库存管理软件如何优化工厂的仓库管理-亿发

如果一家工厂没有专业的WMS仓储软件支撑&#xff0c;管理原材料、辅料、半成品和产成品等环节可能会面临诸多问题。 在仓库管理方面&#xff0c;缺乏安全库存的管理会导致库存不足或过剩&#xff0c;而没有及时的缺货分析可能会导致生产中断。全凭人工核算剩余库存和订单质检的…

金价大跳水,美梦变噩梦!2024真正适合普通人的靠谱创业项目!2024适合30-40岁轻资产小生意

4月22日晚间&#xff0c;向上“狂飙”了一个多月的金价突然就“大跳水”。当日&#xff0c;每克金价均下调14块。在这次跳水中&#xff0c;有人欢喜有人愁&#xff1a;有投资者自报做空金价一夜狂赚14万&#xff0c;也有投资者哭诉&#xff0c;头晚进货到早上就净亏损2万&#…

Android 11 bindService 流程分析

我们可以使用bindService来跨进程通信&#xff0c;其使用方法如下 Intent intent new Intent("xxx"); intent.setPackage("xxx"); boolean result bindService(intent,new ServiceConn(),BIND_AUTO_CREATE);private class ServiceConn implements Servi…

STM32入门_江协科技_1~2_OB记录的自学笔记_STM32简介

1.综述 1.1. 课程简介 手打代码是加入了实操&#xff0c;增加学习效果&#xff1b; STM最小系统板面包板的硬件平台&#xff1b; 配套0.96寸的显示屏&#xff0c;便于调试&#xff1b; 因为使用面板板&#xff0c;所以如果程序现象不出来也有可能是硬件连接的问题&#xff1b; …

Allegro画PCB时如何只删除走线的一部分

如何只删除走线的一部分 1、第一步&#xff1a; 2、第二步&#xff1a; 3、第三步&#xff0c;点击相应的走线段就能删除了。 说明&#xff1a;上面的Cline和Line只的是电线和线,您按下删除后,就可以删除这两种东西,但删除的是一整条折线.把这两个取消掉,换成Cline Segs和Ot…

【代码随想录刷题记录】LeetCode283移动零

题目地址 1. 思路 1.1 基本思路及假设 拿到这个题&#xff0c;首先想到&#xff0c;这是类似删除元素的方法&#xff0c;因为删除元素也是移动元素&#xff0c;但是移动的方向和删除元素的方法刚好相反&#xff0c;我们都知道&#xff0c;如果在数组中删除某个元素&#xff…

【Docker】docker部署lnmp和wordpress网站

环境准备 docker&#xff1a;192.168.67.30 虚拟机&#xff1a;4核4G systemctl stop firewalld systemctl disable firewalld setenforce 0 安装docker #安装依赖包 yum -y install yum-utils device-mapper-persistent-data lvm2 #设置阿里云镜像 yum-config-manager --add…

vue2主体页面进行拆分

目录 一.组件化 二.新建Header.vue页面 三.Aside.vue代码 四.Main.vue代码如下 五.Home.vue代码如下 六.index.js代码如下&#xff1a; 七.项目效果图 在Vue.js 2中&#xff0c;将主体页面进行拆分是一种常见的做法&#xff0c;它有助于提高代码的可维护性和可读性。页面…

js实现简单的级联下拉列表

代码如下&#xff1a; <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title><script src"js/jquery.min.js" type"text/javascript" charset"utf-8"></script><st…

Linux的磁盘分区,格式化,挂载

1.需要提前添加几个磁盘&#xff0c;以做实验 2.把nvme0n2磁盘用来分区实验 3.分了一个主分区&#xff0c;和一个扩展分区&#xff08;扩展分区是不能使用的&#xff0c;所以又在扩展分区里分了一个逻辑分区&#xff09;分区的大小自己定义 4.格式化分出来的区&#xff0c;这…

618不可错过的数码好物精选!等等党必看清单汇总

无论是追求高效工作的职场人士&#xff0c;还是热爱科技、追求品质生活的消费者&#xff0c;都希望能找到那些既实用又富有创新精神的数码好物&#xff0c;现在正值618购物狂欢节来临之际&#xff0c;我精心为大家挑选了一份不可错过的数码好物清单&#xff0c;这份清单不仅汇聚…

App一键直达,Xinstall助力提升用户体验

在这个移动互联网时代&#xff0c;App已经成为了我们日常生活中不可或缺的一部分。然而&#xff0c;每当我们在浏览器或社交平台上看到一个有趣的App推荐&#xff0c;点击下载后却往往要经历一系列繁琐的跳转和确认过程&#xff0c;这无疑大大降低了用户体验。那么&#xff0c;…

数据结构 - C/C++

快速跳转 数组链表栈队列串树 目录 数据结构 逻辑结构 物理结构 数据结构 数据 数据不仅仅包括整型、实型等数值类型&#xff0c;还包括字符及声音、图像、视频等非数值类型。 计算机可以理解并按照指定格式处理。 结构 元素相互之间存在一种或多种特定关系的数据集合。 …

Git 常用命令大全

&#x1f680; Git安装与基础知识学习 &#x1f310; &#x1f3af; Git作为一款全球开发者广泛使用的分布式版本控制系统&#xff0c;能够有效帮助团队协作并追踪项目历史版本。接下来&#xff0c;我们将详细展开Git的安装流程、基础命令操作、高级用法以及应对常见问题的方法…

西湖大学赵世钰老师【强化学习的数学原理】学习笔记1节

强化学习的数学原理是由西湖大学赵世钰老师带来的关于RL理论方面的详细课程&#xff0c;本课程深入浅出地介绍了RL的基础原理&#xff0c;前置技能只需要基础的编程能力、概率论以及一部分的高等数学&#xff0c;你听完之后会在大脑里面清晰的勾勒出RL公式推导链条中的每一个部…

信息系统集成对企业的影响到底有多大?

什么是信息系统集成 系统集成&#xff08;System Integration&#xff09;是指将若干个独立运作的系统或服务联通并整合的过程&#xff0c;旨在将那些存在交集或重复功能的分散模块融合为一个协同工作的整体&#xff0c;以实现效能的最大化和资源的最优配置&#xff0c;避免不…