网络编程套接字——实现简单的UDP网络程序

news2024/7/6 17:52:37

目录

1、预备知识

1.1、认识端口号

1.2、端口号 vs 进程pid

1.3、认识TCP协议

1.4、认识UDP协议

1.5、网络字节序

2、socket编程接口

2.1、socket常见API

2.2、sockaddr结构

3、实现一个简易的UDP服务器和客户端通信

log.hpp

UdpServer.hpp

UdpClient.cc

Main.cc

makefile


1、预备知识

1.1、认识端口号

首先我们先来解决一个问题,在进行网络通信的时候,是不是我们的两台机器在进行通信呢?

答案肯定是不是的,在网络协议中的下三层,主要解决的是,数据安全可靠的运送到远端机器,但是使我们的用户使用应用层软件,完成数据发送和接收的,软件对应的自然就是进程,所以,我们日常网络通信的本质:就是进程间通信!

那么问题又来了,我们两台主机在进行通信时,传输层的上层应用层不止有一个应用软件,该如何区分呢?用的就是端口号。

在公网上,IP地址能表示唯一的一台主机,端口号port,用来标识该主机上的唯一的一个进程,所以:IP:Port = 标识全网唯一的一个进程。

那么我们来总结一下端口号的特性;

· 端口号是一个2字节16位的整数

· 端口号用来标识一个进程,告诉操作系统,当前这个数据要交给哪个进程处理

· IP地址+端口号能够标识网络上的某一台主机的某一个进程

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

1.2、端口号 vs 进程pid

pid已经能够标识一台主机上进程的唯一性了,为什么还要搞一个端口号?

这个问题也很简单,因为网络是晚于操作系统的,而操作系统的每个进程都需要有pid,但是却不失所有的进程都要网络通信,另外,也是追求软件工程低耦合高内聚思想,创建一个端口号,实现了系统和网络功能的解耦。

再做一点小小的扩充,我们的客户端是如何知道服务器的端口号是多少的?

答:每一个服务的端口号都必须是众所周知,精心设计,被客户端知晓的。

那么这该如何实现呢?

如图所示,其实在传输层,有一张哈希表存储端口号,而进程会绑定哈希表中的端口号,这样就实现了端口号的要求,但是这里还有一些细节:

一个进程可以绑定多个端口号,但是一个端口号不可以被多个进程绑定,如果被多个进程绑定就会让端口号无法决定该将信息发送给哪个进程!

1.3、认识TCP协议

此处我们先对TCP(Transmission Control Protocol 传输控制协议)有一个直观的认识,后面再讨论TCP的细节问题。

· 传输层协议

· 有连接

· 可靠传输

· 面向字节流

1.4、认识UDP协议

此处也是对UDP(User Datagram Protocol 用户数据协议)有一个直观的认识,细节后面讨论。

· 传输层协议

· 无连接

· 不可靠传输

· 面向数据报

1.5、网络字节序

我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分,网络数据流同样有大端小端之分。那么如何定义网络数据流的地址呢?

· 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出。
· 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存。
· 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址。
· TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。
· 不管这台主机是大端机还是小端机,都会按照这个TCP/IP规定的网络字节序来发送/接收数据。
· 如果当前发送主机是小端,就需要先将数据转成大端。否则就忽略,直接发送即可。

为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。

#include <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地址转换后准备发送。

如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;

如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。

2、socket编程接口

2.1、socket常见API

// 创建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.2、sockaddr结构

socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,然而,各种网络协议的地址格式并不相同。

· IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型,16位端口号和32位IP地址。

· IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6。这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容。

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


3、实现一个简易的UDP服务器和客户端通信

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 logmessage(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\n", leftbuffer, rightbuffer);

    //     // printf("%s", logtxt); // 暂时打印
    //     printLog(level, logtxt);
    // }
    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;
};

// int sum(int n, ...)
// {
//     va_list s; // char*
//     va_start(s, n);

//     int sum = 0;
//     while(n)
//     {
//         sum += va_arg(s, int); // printf("hello %d, hello %s, hello %c, hello %d,", 1, "hello", 'c', 123);
//         n--;
//     }

//     va_end(s); //s = NULL
//     return sum;
// }

UdpServer.hpp

#pragma once

#include <iostream>
#include <string>
#include <string.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include <unordered_map>
#include "log.hpp"

typedef std::function<std::string(const std::string &, const std::string&, uint16_t)> func_t;

Log log;

enum{
    SOCKET_ERR=1,
    BIND_ERR=2
};

uint16_t defaultport = 8080;
std::string defaultip = "0.0.0.0";
const int size = 1024;

class UdpServer{
public:
    UdpServer(const uint16_t &port = defaultport, const std::string &ip = defaultip):sockfd_(0), port_(port), ip_(ip), isrunning_(false)
    {}
    void Init()
    {
        // 1. 创建udp socket
        //  Udp的socket是全双工的,允许被同时读写的
        sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);
        if (sockfd_ < 0)
        {
            log(Fatal, "socket creat error, sockfd: %d", sockfd_);
            exit(SOCKET_ERR);
        }
        log(Info, "socket create success, sockfd: %d", sockfd_);
        // 2. bind socket
        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()); //1. string -> uint32_t 2. uint32_t必须是网络序列的 // ??
        //local.sin_addr.s_addr = htonl(INADDR_ANY);

        int n = bind(sockfd_, (const struct sockaddr *)&local, sizeof(local));
        if(n < 0)
        {
            log(Fatal, "bind error, errno: %d, err string: %s", errno, strerror(errno));
            exit(BIND_ERR);
        }
        log(Info, "bind success, errno: %d, err string: %s", errno, strerror(errno));
    }

    void CheckUser(const struct sockaddr_in &client, const std::string clientip, uint16_t clientport)
    {
        auto iter = online_user_.find(clientip);
        if(iter == online_user_.end())
        {
            online_user_.insert({clientip, client});
            std::cout << "[" << clientip << ":" << clientport << "] add to online user. " << std::endl;
        }
    }
    
    void Broadcast(const std::string&info, const std::string clientip, uint16_t clientport)
    {
        for(const auto &user : online_user_)
        {
            std::string message = "[";
            message += clientip;
            message += ":";
            message += std::to_string(clientport);
            message += "]# ";
            message += info;
            socklen_t len = sizeof(user.second);
            sendto(sockfd_, message.c_str(), message.size(), 0, (struct sockaddr*)(&user.second), len);
        }
     }

    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;
            }

            uint16_t clientport = ntohs(client.sin_port);
            std::string clientip = inet_ntoa(client.sin_addr);
            CheckUser(client, clientip, clientport);

            std::string info = inbuffer;
            Broadcast(info, clientip, clientport);
         
        }
    }
    ~UdpServer()
    {
        if(sockfd_ > 0) close(sockfd_);
    }

private:
    int sockfd_;      // 网络文件描述符
    std::string ip_; // 任意地址bind 0
    uint16_t port_;   // 表明服务器进程的端口号
    bool isrunning_;
    std::unordered_map<std::string, struct sockaddr_in> online_user_;
};

UdpClient.cc

#include <iostream>
#include <unistd.h>
#include <string.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include "Terminal.hpp"

using namespace std;

void Usage(std::string proc)
{
    std::cout << "\n\tUsage:" << proc << "serverip serverport\n" << std::endl;
}

struct ThreadData
{
    struct sockaddr_in server;
    int sockfd;
    std::string serverip;
};

void *recv_message(void *args)
{
    //OpenTerminal();
    ThreadData *td = static_cast<ThreadData *>(args);
    char buffer[1024];
    while(true)
    {
        memset(buffer, 0, sizeof(nuffer));
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);

        ssize_t s = recvfrom(td->sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);
        if(s > 0)
        {
            buffer[s] = 0;
            cerr << buffer << endl;
        }
    }
}

void *send_message(void *args)
{
    ThreadData *td = static_cast<ThreadData *>(args);
    socklen_t len = sizeof(td->server);
    string message;

    std::string welcome = td->serverip;
    welcome += "coming...";
    sendto(td->sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&(td->server), len);

    while(true)
    {
         cout << "Please Enter@ ";
        getline(cin, message);

     
        // 1. 数据 2. 给谁发
        sendto(td->sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&(td->server), len);
    }
}

//多线程
// ./udpclient serverip serverport
int main(int argc, char *argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(0);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    struct ThreadData td;

    bzero(&td.server, sizeof(td.server));
    td.server.sin_family = AF_INET;
    td.server.sin_port = htons(serverport);
    td.server.sin_addr.s_addr = inet_addr(serverip.c_str());

    td.sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (td.sockfd < 0)
    {
        std::cout << "socker error" << endl;
        return 1;
    }

    td.serverip = serverip;

    // client 要bind吗?要!只不过不需要用户显式的bind!一般由OS自由随机选择!
    // 一个端口号只能被一个进程bind,对server是如此,对于client,也是如此!
    // 其实client的port是多少,其实不重要,只要保证主机上的唯一性就可以!
    // 系统什么时候给我bind呢?首次发送数据的时候
    pthread_t recvr, sender;
    pthread_create(&recvr, nullptr, recv_message, &td);
    pthread_create(&sender, nullptr, send_message, &td);

    pthread_join(recvr, nullptr);
    pthread_join(sender, nullptr);

    close(td.sockfd);
    return 0;
}

Main.cc

#include "UdpServer.hpp"
#include <memory>
#include <cstdio>
#include <vector>

void Usage(std::string proc)
{
    std::cout << "\n\tUsage:" << proc << "port[1024]\n" << std::endl;
}


// ./udpserver port
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(0);
    }

    uint16_t port = std::stoi(argv[1]);

    std::unique_ptr<UdpServer> svr(new UdpServer(port));

    svr->Init(/**/);
    svr->Run();

    return 0;
}

makefile

.PHONY:all
all:udpserver udpclient

udpserver:Main.cc
	g++ -o $@ $^ -std=c++11
udpclient:UdpClient.cc 
	g++ -o $@ $^ -lpthread -std=c++11

.PHONY:clean
clean:
	rm -f udpserver udpclient

这里实现了一个小型的聊天室

所有人发的聊天消息都会显示在客户端。

每有一位新用户,服务器就会显示添加信息。

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

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

相关文章

pytorch CV入门 - 汇总

初次编辑&#xff1a;2024/2/14&#xff1b;最后编辑&#xff1a;2024/3/9 参考网站-微软教程&#xff1a;https://learn.microsoft.com/en-us/training/modules/intro-computer-vision-pytorch 更多的内容可以参考本作者其他专栏&#xff1a; Pytorch基础&#xff1a;https…

力扣热题100_矩阵_240_搜索二维矩阵 II

文章目录 题目链接解题思路解题代码 题目链接 240. 搜索二维矩阵 II 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性&#xff1a; 每行的元素从左到右升序排列。 每列的元素从上到下升序排列。 示例 1&#xff1a; 输入&#xf…

计算机网络 |内网穿透

其实内网穿透&#xff0c;也挺好玩的&#xff0c;如果在大学的时候&#xff0c;那个时候讲计算机网络的老师能横向延展&#xff0c;估计课也会更有趣不少&#xff0c;本来计算机网络这门课就是计算机课程中可玩性最搞的。 只能说&#xff0c;怪可惜的 回到正题&#xff0c;内网…

提高安全投资回报:威胁建模和OPEN FAIR™风险分析

对大多数人和企业来说&#xff0c;安全意味着一种成本。但重要的是如何获得适合的量&#xff0c;而不是越多越好。然而&#xff0c;你如何决定什么时候可以有足够的安全性&#xff0c;以及你如何获得它&#xff1f;则完全是另一回事。 该篇文章是由The Open Group安全论坛主办&…

k8s之图形界面DashBoard【九】

文章目录 9. DashBoard9.1 部署Dashboard9.2 使用DashBoard 镇场 9. DashBoard 之前在kubernetes中完成的所有操作都是通过命令行工具kubectl完成的。其实&#xff0c;为了提供更丰富的用户体验&#xff0c;kubernetes还开发了一个基于web的用户界面&#xff08;Dashboard&…

leetcode 热题 100_环形链表 II

题解一&#xff1a; 哈希表&#xff1a;遍历链表&#xff0c;用哈希表存储遍历过的链表节点&#xff0c;判断链表节点是否在哈希表中存在&#xff0c;如果存在说明链表出现过&#xff0c;第一个重复出现的节点即为开始入环的第一个节点。 import java.util.HashSet;public cla…

第八阶段:uni-app小程序 --首页开发(2)

一&#xff1a;分析页面布局 1.1: 功能 搜索框&#xff1a; 轮播图&#xff1a; 分类的导航区&#xff1a; 楼层区&#xff1a; 二&#xff1a; 利用命令创建home分支 git branch git checkout -b home git branch 三&#xff1a; 配置网络请求(main.js 入口函数&#x…

python问题:vscode切换环境,pip安装库网络错误

python问题&#xff1a;vscode切换环境&#xff0c;pip安装库网络错误 vscode切换环境pip安装库网络错误 记录一下遇见的python问题。 vscode切换环境 在vscode上面的搜索框输入 > select interpreter然后选择需要的环境。 pip安装库网络错误 用requirements.txt来安装…

CentOS部署 JavaWeb 实现 MySql业务

一、项目打war包 在eclispe或idea中找到项目&#xff0c;右键打war包。 二、上传项目到linux 2.1云服务器虚拟机均可 以tomcat为例 /usr/local/tomcat/webapps 将war包通过ssh连接上传到webapps目录下。 如果是root目录则不需要项目名即 ip或域名端口直接访问&#xff08…

linux下重启ORACLE

切换到oracle用户 su - oracle 登录oracle sqlplus / as sysdba 启动数据库 startup 退出数据库 exit 启动监听 lsnrctl start FINISH

Docker使用(二)Docker安装和常见典型操作

Docker使用(二)Docker安装和常见典型操作 二、软件安装 1、Docker安装 &#xff08;1&#xff09;环境准备 [rootlocalhost ~]# uname -r 3.10.0-327.el7.x86_64 # cat /etc/os-release &#xff08;2&#xff09;卸载旧版本 $ sudo yum remove docker \ ​ docker-cli…

OpenCV4.9.0开源计算机视觉库在 Linux 中安装

返回目录&#xff1a;OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇&#xff1a;OpenCV 环境变量参考 下一篇&#xff1a;将OpenCV与gcc和CMake结合使用 引言&#xff1a; OpenCV是一个开源的计算机视觉库&#xff0c;由英特尔公司所赞助。它是一个跨…

KBP210-ASEMI新能源专用整流桥KBP210

编辑&#xff1a;ll KBP210-ASEMI新能源专用整流桥KBP210 型号&#xff1a;KBP210 品牌&#xff1a;ASEMI 封装&#xff1a;KBP-4 正向电流&#xff08;Id&#xff09;&#xff1a;2A 反向耐压&#xff08;VRRM&#xff09;&#xff1a;1000V 正向浪涌电流&#xff1a;6…

SpingBoot集成Rabbitmq及Docker部署

文章目录 介绍RabbitMQ的特点Rabbitmq术语消息发布接收流程 Docker部署管理界面说明Overview: 这个页面显示了RabbitMQ服务器的一般信息&#xff0c;例如集群节点的名字、状态、运行时间等。Connections: 在这里&#xff0c;可以查看、管理和关闭当前所有的TCP连接。Channels: …

#QT(定时轮播电子相册)

1.IDE&#xff1a;QTCreator 2.实验&#xff1a; &#xff08;1&#xff09;使用QOBJECT的TIMER &#xff08;2&#xff09;EVENT时间 &#xff08;3&#xff09;多定时器定时溢出判断 &#xff08;4&#xff09;QLABEL填充图片 3.记录 4.代码 widget.h #ifndef WIDGET_H…

数星星 刷题笔记 (树状数组)

依题意 要求每个点 x, y 的左下方有多少个星星 又因为 是按照y从小到大 给出的 所以 我们在计算个数的时候是按照y一层层变大来遍历的 因此我们在处理每一个点的时候 只需要看一下 当前的点有多少个点的x值比当前点小即可 树状数组的 操作模板 P3374 【模板】树…

R语言实现中介分析(1)

中介分析&#xff0c;也称为介导分析&#xff0c;是统计学中的一种方法&#xff0c;它用于评估一个或多个中介变量&#xff08;也称为中间变量&#xff09;在自变量和因变量之间关系中所起的作用。换句话说&#xff0c;中介分析用于探索自变量如何通过中介变量影响因变量的机制…

Python-GIS分析之地理数据空间聚类

地理空间数据聚类是空间分析和地理信息系统(GIS)领域的一项关键技术。这种方法对于理解地理数据固有的空间模式和结构、促进城市规划、环境管理、交通和公共卫生等各个领域的决策过程至关重要。本文探讨了地理空间数据聚类的概念、方法、应用、挑战和未来方向。 当模式出现…

《计算机视觉中的深度学习》之目标检测算法原理

参考&#xff1a;《计算机视觉中的深度学习》 概述 目标检测的挑战&#xff1a; 减少目标定位的准确度减少背景干扰提高目标定位的准确度 目标检测系统常用评价指标&#xff1a;检测速度和精度 提高精度&#xff1a;有效排除背景&#xff0c;光照和噪声的影响 提高检测速度…

wsl ubuntu 安装cuda nvcc环境

wsl ubuntu 安装cuda环境&#xff1a; CUDA Toolkit 11.6 Downloads | NVIDIA DeveloperDownload CUDA Toolkit 11.6 for Linux and Windows operating systems.https://developer.nvidia.com/cuda-11-6-0-download-archive?target_osLinux&target_archx86_64&Distri…