【Linux】网络地址 / socket 套接字

news2024/9/24 9:28:10

目录

    • 一. 网络地址
      • 1. IP 地址
      • 2. MAC 地址
      • 3. IP 地址 和 MAC 地址 的关系
      • 4. 端口号
      • 5. 传输层协议
      • 6. 网络字节序
    • 二. socket 套接字
      • 1. socket 常见API
      • 2. sockaddr 结构
    • 三. UDP 网络程序
      • 服务器端
        • 1. 创建套接字
        • 2. 绑定 IP 地址和端口号
        • 3. 接受/发送消息
        • 4. 加入多线程
      • 客户端

一. 网络地址

1. IP 地址

IP 地址 用于唯一标识和定位网络中不同的主机;

IP 地址分为两种:

  • IPv4 (Internet Protocol version 4): 由网络号和主机号组成, 使用 32 位二进制数表示, 也就是 4 字节的无符号整数, 通常使用点分十进制表示, 例如 192.168.0.1;
    网络号: 表示该设备所属的网络;
    主机号: 表示该设备在该网络中的编号;

  • IPv6 (Internet Protocol version 6): 使用 128 位二进制数表示, 16 字节的大小, 通常使用十六进制数字和冒号表示, 例如: ABCD:EF01:2345:6789:ABCD:EF01:2345:6789;

工作原理:
IP 协议在网络层(OSI模型中的第三层), 负责将数据封装成数据包(packet), 并根据目标 IP 地址进行路由的选择和转发;
当一个主机和另一个主机通信时, 它需要知道目标主机的 IP 地址, 并将其写入数据包头部;
然后根据路由表(routing table), 选择合适的下一跳(next hop), 也就是下一个转发该数据包的路由器或其他网络设备, 并将数据包发送出去;
当数据包到达下一跳时, 重复以上操作, 直至数据包到达目标主机所在的局域网为止;
在这个过程中, 每个路由器或其他网络设备只需要知道下一跳的地址, 并不需要知道目标主机或其他中间节点的具体位置或物理连接方式;

2. MAC 地址

MAC(Media Access Control), 也称为物理地址或硬件地址, 用于在局域网中唯一标识网络适配器(网卡);

MAC 地址 使用 48 位二进制数表示, 6 字节的大小, 通常使用十六进制数字和冒号表示, 例如: AB:CD:EF:01:23:45;

MAC 地址是数据链路层的一部分, 用于在局域网中寻找目标设备, 将数据包从源设备传输至目标设备;

工作原理:
MAC 协议在数据链路层(OSI模型中的第二层), 负责将数据封装成帧(frame), 并根据目标 MAC 地址进行寻址和传输;
当一个主机和另一个主机通信时, 它需要知道目标主机的 MAC 地址, 并将其写入帧头部;
然后根据物理媒介(如电缆、光纤等)的特性, 将帧发送出去;
当帧到达目标主机所在的局域网时, 局域网内的所有设备都会接收到该帧, 并根据帧头部的目标MAC地址判断是否是自己;
若为真, 则接收该帧, 并将其解封装成数据包, 交给网络层处理; 若为假, 则丢弃该帧;
在这个过程中, 每个设备只需要知道与自己直连的设备的 MAC 地址, 并不需要知道目标主机或其他中间节点的逻辑位置或网络连接方式;

3. IP 地址 和 MAC 地址 的关系

IP 地址与 MAC 地址 的区别: IP 地址是网络整个网络中有效的, 而 MAC 地址只是局域网内有效;

IP 地址在通信中分为:
源 IP 地址(相当于通信的起始 IP 地址, 从不改变),
目标 IP 地址(相当于通信的最终目标 IP 地址, 从不改变);

MAC 地址也分为:
源 MAC 地址(相当于当前通信的起始 MAC 地址, 可能会和一开始的源 MAC 地址不同),
目标 MAC 地址(相当于当前通信的目标 MAC 地址, 可能会和一开始的目标 MAC 地址不同);

通信过程:
当主机B 与主机A 在同一局域网内进行通信时, 主机A 通过目标主机 IP 地址 和 ARP 协议获取主机B 的 MAC 地址, 将 IP, MAC 地址封装在帧中发送出去; 源 IP 地址, 目标 IP 地址, 源 MAC 地址, 目标 MAC 地址 在此次通信中没有发生改变;

当主机B 与主机A 不在同一局域网内, 那么就需要经过路由器的转发;
主机A 就会获取路由器的 MAC 地址, 将当前 IP 和 MAC 地址为源, 主机B 的 IP 和 路由器的 MAC 地址为目的, 封装数据帧 转发至路由器;
当路由器收到数据帧后, 将数据帧的 源 MAC 地址修改为自身, 目的 MAC 地址修改为主机B 的 MAC 地址, 转发至主机B; 在此次通信中 MAC 地址会发生改变;

4. 端口号

端口号(Port) 用于标识同一主机中的不同网络进程;

端口号 使用 16 位二进制数表示, 2 字节的大小, 取值范围为 [0, 65535];

端口号根据传输协议分为 TCP端口和 UDP端口, 不同的传输协议可以使用相同的端口号;

IP 地址 + 端口号 可以标识公网环境下, 唯一的网络进程;
一个进程可以绑定多个端口号, 但一个端口号不允许被多个进程绑定;

5. 传输层协议

TCP(Transmission Control Protocol) 传输控制协议 特点:

  • 传输层协议;
  • 有连接;
  • 可靠传输;
  • 面向字节流;

UDP(User Datagram Protocol) 用户数据报协议 特点:

  • 传输层协议;
  • 无连接;
  • 不可靠传输;
  • 面向数据报;

6. 网络字节序

由于不同主机的大小端可能不同, 所以TCP/IP 协议规定: 网络中传输的数据, 统一采用大端存储方案, 也就是网络字节序;

并且提供了主机字节序和网络字节序互相转换的相关函数;

#include <arpa/inet.h>

// 主机字节序转网络字节序
uint32_t htonl(uint32_t hostlong); // l 表示32位长整数
uint32_t htons(uint32_t hostshort); // s 表示16位短整数

// 网络字节序转主机字节序
uint32_t ntohl(uint32_t netlong); // l 表示32位长整数
uint32_t ntohs(uint32_t netshort); // s 表示16位短整数

二. socket 套接字

1. socket 常见API

#include <sys/types.h>
#include <sys/socket.h>

// 创建 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 结构

struct sockaddr 结构体是用来表示 IP 地址的标准结构体;

而 socket API是一层抽象的网络编程接口, 适用于各种底层网络协议, 所以 struct sockaddr 结构体就需要适用于各种协议的 IP 地址 的格式;

struct sockaddr 结构体衍生出了两个不同的结构体:

  • sockaddr_in 网络套接字, 适用于网络通信;
  • sockaddr_un 域间套接字, 适用于域间通信;

通信时, 根据16 位地址类型, 判断通信类型;
在这里插入图片描述

三. UDP 网络程序

使用 socket 套接字接口及 UDP 协议的实现简单网络通信, 客户端向服务器发送消息, 服务器接受消息后再转发至所有的客户端, 客户端接受消息并打印, 类似聊天室;

服务器端

框架

Server.hpp

#pragma once
#include <sys/types.h> 
#include <sys/socket.h>

class UdpServer
{
public:
    UdpServer()
    {}

    void Start()
    {}

    ~UdpServer()
    {}


private:
    // 网络
    int _sockfd;
    uint16_t _port;
    sockaddr_in _sockaddr;
};
1. 创建套接字

进行网络通信, 首先需要创建套接字, 使用 socket() 函数

#include <sys/types.h>
#include <sys/socket.h>

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

参数:

  • domain: 表示套接字的地址族, 常用的有 AF_UNIX(unix), AF_INET(IPv4), AF_INET6(IPv6);
  • type: 表示数据的传输方式, SOCK_STREAM(流格式传输) 和 SOCK_DGRAM(数据报传输);
  • protocol: 表示传输协议, 常用的有 IPPROTO_TCP 和 IPPTOTO_UDP; 若地址类型和数据传输方式只被一种协议支持, 那么 protocol 可以为 0, 表示自动推导传输协议;

返回值:

  • 若成功, 返回套接字(文件描述符); 若失败, 返回 -1;

Server.hpp

这里使用的 UDP 协议, 所以地址族选择 AF_INET, 传输方式选择 SOCK_DGRAM;

#pragma once
#include "Log.hpp" 
#include <sys/types.h> 
#include <sys/socket.h>

class UdpServer
{
public:
    UdpServer()
    {
    	// 创建 socket 文件描述符
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)	// 若创建失败
        {
            LOG(FATAl, "socket fail");
            exit(1);
        } 
    }

private:
    // 网络
    int _sockfd;
};
2. 绑定 IP 地址和端口号

使用 bind() 函数进行绑定

#include <sys/types.h>
#include <sys/socket.h>

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

参数:

  • sockfd: socket 文件描述符;
  • addr: sockaddr 结构体变量的指针, 包含 IP 地址和端口号等内容;
  • addrlen: sockaddr 结构体变量的大小;

返回值:

  • 若成功, 返回 0; 若失败, 返回 -1;

这里是网络通信, 所以使用 sockaddr_in 结构体, 需包含两个头文件;

#include <netinet/in.h>
#include <arpa/inet.h>

sockaddr_in 结构体

/* Structure describing an Internet socket address.  */
struct sockaddr_in
{
  __SOCKADDR_COMMON (sin_);
  in_port_t sin_port;			/* Port number.  */
  struct in_addr sin_addr;		/* Internet address.  */

  /* Pad to size of `struct sockaddr'.  */
  unsigned char sin_zero[sizeof (struct sockaddr)
	   - __SOCKADDR_COMMON_SIZE
	   - sizeof (in_port_t)
	   - sizeof (struct in_addr)];
};

在这里插入图片描述

__SOCKADDR_COMMON 是一个宏函数, 使用 C 语言中一个语法 ## (拼接两个字符串);

/* POSIX.1g specifies this type name for the `sa_family' member.  */
typedef unsigned short int sa_family_t;

/* This macro is used to declare the initial common members
   of the data types used for socket addresses, `struct sockaddr',
   `struct sockaddr_in', `struct sockaddr_un', etc.  */

#define	__SOCKADDR_COMMON(sa_prefix) \
  sa_family_t sa_prefix##family

16 位地址类型的变量名实际上是 _SOCKADDR_COMMON 传入 sin 参数后, 拼接而成的;

sa_family_t sin_family;

端口号 in_port_t 类型 实际上是一个 2 字节, 16 位的无符号整数, 符合端口号的取值范围 [0, 65535];

typedef unsigned short int __uint16_t;
typedef __uint16_t uint16_t;

typedef uint16_t in_port_t;

IP 地址 in_addr 结构体, 其中包含了一个 32 位无符号整数, 存储 IP 地址;

typedef unsigned int __uint32_t;
typedef __uint32_t uint32_t;

/* Internet address.  */
typedef uint32_t in_addr_t;
struct in_addr
{
  in_addr_t s_addr;
};

填充字段不使用, 一般用 0 填充;

可以使用 bzero 函数, 将变量置 0;

#include <strings.h>

void bzero(void *s, size_t n);

Server.hpp

端口号自行设置;

#pragma once
#include "Log.hpp" 
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <strings.h>

class UdpServer
{
public:

    UdpServer(uint16_t port = 8888)
        :_port(port)
    {
        // 创建 socket 文件描述符
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)	// 若创建失败
        {
            LOG(FATAl, "socket fail");
            exit(1);
        }   

        // 设置 ip地址 和 端口号
        bzero(&_sockaddr, sizeof(_sockaddr));

        _sockaddr.sin_family = AF_INET;	// 设置 16 位地址类型
        _sockaddr.sin_addr.s_addr = INADDR_ANY;	// INADDR_ANY 即 0, 表示本机的所有 IP
        _sockaddr.sin_port = htons(_port);	// 设置端口号, 转网络字节序


        // 绑定 socket
        int flag = bind(_sockfd, (sockaddr*)&_sockaddr, sizeof(_sockaddr));
        if (flag < 0)
        {
            LOG(FATAl, "bind fail");
            exit(1);
        }
    }

private:
    // 网络
    int _sockfd;	// socket 文件描述符
    uint16_t _port;	// 端口号
    sockaddr_in _sockaddr;	// sockaddr 结构
};

INADDR_ANY 即 0.0.0.0, 表示本机的所有 IP;
若主机有多个网卡, IP地址, 绑定某个具体的 IP 地址, 就无法接受其他 IP 地址的数据;

点分十进制的字符串(“0.0.0.0”), 转换为无符号短整数, 可以使用 inet_addr() 函数, 并且此函数在进行转换的同时, 还会将主机序列转换为网络序列;

3. 接受/发送消息

使用 recvfrom() 函数可以获取数据;

#include <sys/types.h>
#include <sys/socket.h>

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

参数:

  • sockfd: socket 文件描述符;
  • buf: 输出型参数, 缓冲区;
  • len: 缓冲区大小;
  • flags: 读取方式(阻塞/非阻塞);
  • src_addr: 输出型参数, 发送数据的客户端地址信息的结构体;
  • addrlen: 输入输出型参数, src_addr 结构体大小;

返回值:

  • 若成功, 返回实际读取的字节数; 若失败, 返回 -1;
void* Recv()
{
    while (1)
    {
    	// 创建缓冲区, 对端结构体;
        char buf[128];
        sockaddr_in src_addr;
        socklen_t len = sizeof(src_addr);
	
        bzero(&src_addr, sizeof(src_addr));	// 置零

        int flag = recvfrom(_sockfd, (void*)buf, sizeof(buf)-1, 0, (sockaddr*)&src_addr, &len);
        if (flag < 0)	// 若失败
        {
            LOG(FATAl, "recvfrom fail");
            continue;
        }
		
		/*
	        数据处理...
        */
    }
    return 0;
}

使用 sendto() 函数可以发送数据;

#include <sys/types.h>
#include <sys/socket.h>

// 读取信息(TCP/UDP	服务器/客户端)
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

参数:

  • sockfd: socket 文件描述符;
  • buf: 缓冲区, 发送的数据;
  • len: 缓冲区大小;
  • flags: 读取方式(阻塞/非阻塞);
  • dest_addr: 接收数据的主机地址信息的结构体;
  • addrlen: dest_addr结构体大小;

返回值:

  • 若成功, 返回实际发送的字节数; 若失败, 返回 -1;
void* Send()
{
    string buf;
    sockaddr_in* user;
    while (1)
    {
 		/*
 		   获取消息, 接收端的 sockaddr 结构体...
 		*/
 		
 		
 		// 发送消息
        int flag = sendto(_sockfd, (void*)buf.c_str(), buf.size(), 0, (sockaddr*)user, sizeof(*user));
        if (flag < 0)	// 若失败
            LOG(FATAl, "sendto fail");
    }
    return 0;
}
4. 加入多线程

服务器端使用两个线程, 分别接收消息和发送消息; 一个堵塞队列, 保证线程的同步和互斥;
在接受消息后, 需保存发送端的 sockaddr 结构体, 并且在堵塞队列中推入 消息;
发送消息时, 从堵塞队列中推出 消息, 发送信息至所有已保存地址的 主机;

Server.hpp

#pragma once
#include "Log.hpp" 
#include "RingQueue.hpp" 
#include <stdlib.h> 
#include <strings.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unordered_map>
#include <algorithm>



class UdpServer
{
public:
    UdpServer(uint16_t port = 8888)    // 端口号
        :_port(port), _recv(bind(&UdpServer::Recv, this)), _send(bind(&UdpServer::Send, this))
    {
        // 创建 socket 文件描述符
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)	// 若创建失败
        {
            LOG(FATAl, "socket fail");
            exit(1);
        }   

        // 设置 ip地址 和 端口号
        bzero(&_sockaddr, sizeof(_sockaddr));

        _sockaddr.sin_family = AF_INET;	// 设置 16 位地址类型
        _sockaddr.sin_addr.s_addr = INADDR_ANY;	// INADDR_ANY 即 0, 表示本机的所有 IP
        _sockaddr.sin_port = htons(_port);	// 设置端口号, 转网络字节序


        // 绑定 socket
        int flag = bind(_sockfd, (sockaddr*)&_sockaddr, sizeof(_sockaddr));
        if (flag < 0)	// 若失败
        {
            LOG(FATAl, "bind fail");
            exit(1);
        }
    }

    void Start()
    {
        _recv.start();
        _send.start();
    }

    void* Recv()
    {
    	char buf[128];
        while (1)
        {
        	// 对端结构体;
            sockaddr_in src_addr;
            socklen_t len = sizeof(src_addr);

            bzero(&src_addr, sizeof(src_addr));
			
			// 接受消息
            int flag = recvfrom(_sockfd, (void*)buf, sizeof(buf)-1, 0, (sockaddr*)&src_addr, &len);
            if (flag < 0)
            {
                LOG(FATAl, "recvfrom fail");
                continue;
            }

            buf[flag] = 0;
            // cout << buf << endl;
            // 存储主机地址
            _guard.Lock();

            string user = inet_ntoa(src_addr.sin_addr) + to_string(ntohs(src_addr.sin_port));
            if (!_map.count(user))
                _map[user] = src_addr;

            _guard.Unlock();
			
			// 推入 消息
            _queue.Push(user+": "+buf);
        }
        return 0;
    }

    void* Send()
    {
        string buf;
        while (1)
        {
        	// 获取消息, 接收端的 sockaddr 结构体...
            _queue.Pop(buf);
            // cout << buf << endl;

            vector<sockaddr_in*> users;
            _guard.Lock();
            for (auto& user:_map)
                users.emplace_back(&user.second);
            _guard.Unlock();

			// 发送消息
            for (auto& user:users)
            {
                int flag = sendto(_sockfd, (void*)buf.c_str(), buf.size(), 0, (sockaddr*)user, sizeof(*user));
                if (flag < 0)
                    LOG(FATAl, "sendto fail");
            }
        }
        return 0;
    }

    ~UdpServer()
    {
        _recv.join();
        _send.join();
        close(_sockfd);
        LOG(DEBUG, "close");
    }

private:
    // 线程
    unordered_map<string,sockaddr_in> _map;	// 存储主机地址
    RingQueue<string> _queue;	// 堵塞队列
    LockGuard _guard;
    Thread _recv;
    Thread _send;

    // 网络
    int _sockfd;	// socket 文件描述符
    uint16_t _port;	// 端口号
    sockaddr_in _sockaddr;	// sockaddr 结构
};

Server.cc

#include "Server.hpp"


int main()
{
    UdpServer server;
    server.Start();

    return 0;
}

客户端

客户端相较于服务器端更简洁;
客户端在发送消息之前需要得知服务器端的地址;
和服务器端不同, 客户端不需要手动绑定 IP 地址与端口号, 避免指定重复的端口号, 操作系统会在首次传输数据时自动 bind, 而服务器的端口不能随意改变;

Client.hpp

#pragma once
#include "Log.hpp" 
#include "Thread.hpp" 
#include <stdlib.h> 
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <strings.h>

class UdpClient
{
public:
    UdpClient(const string& ip, uint16_t port)    // ip 和 端口号
        :_ip(ip), _port(port), _recv(bind(&UdpClient::Recv, this)), _send(bind(&UdpClient::Send, this))
    {
        // 创建 socket 文件描述符
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(FATAl, "socket fail");
            exit(1);
        }   

        // 保存服务端的 ip地址和端口号
        bzero(&_server, sizeof(_server));

        _server.sin_family = AF_INET;
        _server.sin_addr.s_addr = inet_addr(_ip.c_str());
        _server.sin_port = htons(_port);

    }


    void Start()
    {
        _recv.start();
        _send.start();
    }

    void* Recv()
    {
        char buf[128];
        while (1)
        {
            sockaddr_in src_addr;
            socklen_t len = sizeof(src_addr);

            bzero(&src_addr, sizeof(src_addr));

            // 接收消息
            int flag = recvfrom(_sockfd, (void*)buf, sizeof(buf)-1, 0, (sockaddr*)&src_addr, &len);
            _guard.Lock();
            if (flag < 0)
            {
                LOG(FATAl, "recvfrom fail");
                _guard.Unlock();
                continue;
            }

            buf[flag] = 0;
            cout << buf << endl;
            _guard.Unlock();
        }
        return 0;
    }

    void* Send()
    {
        string buf;
        while (1)
        {   
            cin >> buf;
            
            // 发送消息
            int flag = sendto(_sockfd, (void*)buf.c_str(), buf.size(), 0, (sockaddr*)&_server, sizeof(_server));
            _guard.Lock();
            if (flag < 0)
                LOG(FATAl, "sendto fail");
            _guard.Unlock();
        }
        return 0;
    }

    ~UdpClient()
    {
        _recv.join();
        _send.join();
        close(_sockfd);
        LOG(DEBUG, "close");
    }


private:

    LockGuard _guard;
    Thread _recv;
    Thread _send;

    int _sockfd;
    string _ip;
    uint16_t _port;
    sockaddr_in _server;
};

Client.cc

#include "Client.hpp"


int main(int argc, char* argv[])
{
    // 可以在命令行指定
    // if (argc != 3)
    //     LOG(FATAl, "argv fail");
    // UdpClient client(argv[1], atoi(argv[2]));
    // client.Start();

    // 也可以直接设置
    string ip = "127.0.0.1";
    uint16_t port = 8888;
    UdpClient client(ip, port);
    client.Start();

    return 0;
}

在这里插入图片描述

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

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

相关文章

权力之望怎么下载客户端 权力之望客户端一键下载

权力之望是一款MMORPG新作&#xff0c;支持PC和APP双版本游玩&#xff0c;玩家可以在这里体验自由成长和无限探索的乐趣&#xff0c;并加入这场声势浩大、危机四伏的夺权之战中。游戏的自由度极高&#xff0c;我们在创建角色时就可以感受的到&#xff0c;设有54种能力和60多种职…

SketchUp Pro 2024:现代科技之诗意体验

在那遥远的唐朝&#xff0c;李白曾以诗酒为伴&#xff0c;游历山川&#xff0c;挥洒才情。而今&#xff0c;若李白穿越时空&#xff0c;手握现代科技之利器——SketchUp Pro 2024&#xff0c;定会以诗意之笔&#xff0c;描绘这款软件的神奇与魅力。 初识SketchUp Pro 2024 初…

python--实验6 字典与集合

知识点 集合 集合&#xff08;Set&#xff09;在Python中是一种基本的数据结构&#xff0c;用于存储无序且不重复的元素。以下是关于集合的详细介绍&#xff1a; 集合的定义和特点 无序性&#xff1a;集合中的元素没有特定的顺序。 不重复性&#xff1a;集合中的元素都是唯…

【Ruby爬虫06】企业基本信息查询

查询结果 查询接口 # frozen_string_literal: true require http require json def query(company_name) # 获取基本信息 headers { User-Agent > Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 S…

高职人工智能基础教学解决方案

前言 随着人工智能技术的迅猛发展&#xff0c;其在各行各业的应用日益广泛&#xff0c;对人才的需求也呈现出爆发式增长。高职院校作为培养技术技能人才的重要基地&#xff0c;如何有效实施人工智能基础教学&#xff0c;培养具备人工智能素养的高素质人才&#xff0c;已成为当…

golang 项目打包部署环境变量设置

最近将 golang 项目打包部署在不同环境&#xff0c;总结一下自己的心得体会&#xff0c;供大家参考。 1、首先要明确自己目标服务器的系统类型(例如 windows 或者Linux) &#xff0c;如果是Linux 还需要注意目标服务器的CPU架构(amd或者arm) 目标服务器的CPU架构可执行命令&…

字节码编程javassist之修改返回值

写在前面 本文看下如何修改返回值。 代码 需要增强的类&#xff1a; package com.dahuyou.javassist.huohuo.cc;import java.math.BigDecimal;public class MyApiTestNoAnnotation {public double queryUserInfo(String uId){return BigDecimal.ONE.doubleValue();}}插桩类…

聚道云赋能,财务流程大提速,NC凭证对接一键通!

客户介绍 某煤炭交易中心有限公司是中国煤炭行业的重要交易与服务平台之一。公司依托丰富的煤炭资源优势和深厚的行业底蕴&#xff0c;致力于打造煤炭交易、物流、金融、信息、技术服务等多功能于一体的综合性服务平台。公司不仅促进了煤炭资源的合理流动和有效配置&#xff0…

如何在抖音小程序上展示VR全景?

如果您或者您服务的甲方需要在抖音小程序中打开720全景漫游链接&#xff0c;就需要在720云平台进行小程序校验。总体来说&#xff0c;在720云平台提交对应小程序信息后&#xff0c;您将获得720云业务域名&#xff0c;将此业务域名配置到抖音小程序后台&#xff0c;即完成校验&a…

本地部署,AuraSR全新图像超分辨率模型

目录 前言&#xff1a; Usage docker安装 运行结果 结论&#xff1a; Tip&#xff1a; 问题1&#xff1a;docker部署 问题2&#xff1a;API处理 问题3&#xff1a;网络问题 问题4&#xff1a;程序开发 前言&#xff1a; 一款名为AuraSR的全新图像超分辨率模型引起了…

springbootAl农作物病虫害预警系统-计算机毕业设计源码21875

摘要 随着农业现代化的推进&#xff0c;农作物病虫害的防治已成为农业生产中的重要环节。传统的病虫害防治方法往往依赖于农民的经验和观察&#xff0c;难以准确、及时地预测和防控病虫害的发生。因此&#xff0c;开发一种基于现代信息技术的农作物病虫害预警系统&#xff0c;对…

uni-app三部曲之一: Pinia使用

1.引言 最近在学习移动端的开发&#xff0c;使用uni-app前端应用框架&#xff0c;通过学习B站的视频以及找了一个开发模板&#xff0c;终于是有了一些心得体会。 B站视频1&#xff1a;Day1-01-uni-app小兔鲜儿导学视频_哔哩哔哩_bilibili B站视频2&#xff1a;01-课程和uni的…

Redis 实现的延时队列组件

最近看开源看到一个好用的延时队列组件&#xff0c;已经上生产。代码量很少&#xff0c;主要就是利用Redis监听过期键实现的。然后搞点策略模式柔和柔和。利用Spring Start 封装了一下&#xff0c;全是俺掌握的知识&#xff0c;稍微研究了下就搞懂了。觉得挺有用的&#xff0c;…

人话学Python-基础篇-数字计算

一&#xff1a;数字类型 对于最常见的数据类型,数字在Python中分为三类&#xff1a; 整型(int) 表示的是整数类型的所有数字&#xff0c;包括正整数&#xff0c;负整数和0。和C语言不同的是&#xff0c;Python中的int型没有范围的限制&#xff0c;理论上可以从无限小的整数取到…

想实现随时随地远程访问?解析可道云teamOS内网穿透功能

在数字化时代&#xff0c;无论是个人还是企业&#xff0c;都面临着数据共享与远程访问的迫切需求。 比如我有时会需要在家中加班&#xff0c;急需访问公司内网中的某个关键文件。 然而&#xff0c;由于公网与内网的天然隔阂&#xff0c;这些需求往往难以实现。这时&#xff0c…

智慧运维管理平台建设方案(PPT原件)

1、智慧运维系统建设背景 2、智慧运维系统建设目标 3、智慧运维系统建设内容 4、智慧运维系统建设技术 5、智慧运维系统建设流程 6、智慧运维系统建设收益 软件全套资料获取及学习&#xff1a;本文末个人名片直接获取或者进主页。

前端工程师

15年前&#xff0c;前端主流的框架jquery&#xff0c;那个时候还没有前端工程师,后端开发人员既要写后台业务逻辑&#xff0c;又要写页面设计&#xff0c;还要应付IE不同版本浏览器兼容问题&#xff0c;非常的繁琐、难搞。 现在前端框架很多、很强大&#xff0c;前端开发工程师…

响应式的vue框架搭建个人博客网站模板

Vue框架搭建个人博客站点&#xff0c;html5 响应式个人博客模板 微信扫码免费获取源码

(NeurIPS,2022)Knowledge-CLIP:使用知识图谱进行CLIP

文章目录 Contrastive Language-Image Pre-Training with Knowledge Graphs相关资料摘要引言回顾CLIPKnowledge-CLIP数据准备模型架构训练目标 Contrastive Language-Image Pre-Training with Knowledge Graphs 相关资料 论文&#xff1a;Contrastive Language-Image Pre-Tra…