【Linux】IO多路转接-poll

news2024/11/29 4:06:29

文章目录

  • I/O多路转接-poll
    • poll初识
    • poll函数
    • poll的小测试-监控标准输入
    • poll服务器
      • poll_server.cc
    • poll的优点
    • poll的缺点

I/O多路转接-poll

poll初识

poll也是系统提供的一个多路转接接口, poll系统调用也可以让我们的程序同时监视多个文件描述符上的事件是否就绪,和select的定位是一样的,适用场景也是一样的


poll函数

#include <poll.h>
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
//int poll(struct pollfd *fds, nfds_t nfds, int timeout);

参数说明

  • fds:数组起始地址,每一个struct pollfd元素包含三部分内容:文件描述符,监视的事件集合,就绪的事件集合
  • nfds:表示fds数组的长度
  • timeout:表示poll函数的超时时间,单位是毫秒(ms)

返回值说明

  • 如果函数调用成功,则返回有事件就绪的文件描述符个数,
  • 如果timeout时间耗尽,则返回0,
  • 如果函数调用失败,则返回-1,同时错误码会被设置
    • poll调用失败时,错误码可能被设置为:
      • EINVAL:nfds值超过RLIMIT_NOFILE值, EINTR:此调用被信号所中断
      • EINVAL:nfds值超过RLIMIT_NOFILE值, ENOMEM:核心内存不足,

关于 struct pollfd结构

image-20221005111034760

struct pollfd结构当中包含三个成员:

  • fd:表示你要关心哪个文件描述符的事件,若设置为负值则忽略events字段并且revents字段返回0,
  • events:需要OS帮你监视该文件描述符fd上的哪些事件 (用户告诉内核所要关心的事件)
  • revents:poll函数返回时告知用户该文件描述符fd上的哪些事件已经就绪, (内核告诉用户的就绪事件)

关于events和revents的取值:

事件描述是否可作为输入是否可作为输出
POLLIN数据(包括普通数据和优先数据)可读
POLLRDNORM普通数据可读
POLLRDBAND优先级带数据可读(Linux不支持)
POLLPRI高优先级数据可读,比如TCP带外数据
POLLOUT数据(包括普通数据和优先数据)可写
POLLWRNORM普通数据可写
POLLWRBAND优先级带数据可写
POLLRDHUPTCP连接被对方关闭,或者对方关闭了写操作,它由GNU引入
POLLERR错误
POLLHUP挂起。比如管道的写端被关闭后,读端描述符上将收到POLLHUP事件
POLLNVAL文件描述符没有打开

实际上,这些都是以宏的方式进行定义的,并且这些宏的特点是:它们的二进制序列当中有且只有一个比特位是1,并且比特位为1的位置不会重复,是唯一的

image-20221005160359327

因此在调用poll函数之前,可以通过|运算符将要监视的事件添加到events成员当中

例子:关心读和写事件是否就绪:

events |= POLLIN;	events |= POLLIOUT;

在poll函数返回后,可以通过&运算符检测revents成员中是否包含特定事件,以得知对应文件描述符的特定事件是否就绪

例子:检测事件是否就绪

//写事件是否就绪
if(revents & POLLIOUT) {}
//读事件是否就绪
if(revents & POLLIOUT) {}

参数timeout的取值:

  • -1:poll调用后进行阻塞等待,直到被监视的某个文件描述符上的某个事件就绪
  • 0:poll调用后进行非阻塞等待,无论被监视的文件描述符上的事件是否就绪,poll检测后都会立即返回
  • 特定的时间值:poll调用后在指定的时间内进行阻塞等待,如果被监视的文件描述符上一直没有事件就绪,则在该时间后poll进行超时返回

poll的小测试-监控标准输入

我们关心标准输入-0号描述符的读取事件是否就绪, 是因为标准输入有明显的读取事件就绪和不就绪的特征,也就是不给它输入数据就是不就绪,给它输入数据的话,读取事件就是就绪的

#include <iostream>
#include <unistd.h>
#include <poll.h>

int main()
{
    struct pollfd rfds;
    rfds.fd = 0;//你要关心的文件描述符
    rfds.events = POLLIN; 
    rfds.revents = 0;

    //事件循环
    while(1)
    {
        //int n = poll(&rfds, 1, 1000); //每隔1000ms即1s就timeout一次 1s之内有事件就绪就返回,没有事件就绪就timeout
        //int n = poll(&rfds, 1, 0); //非阻塞轮询
        int n = poll(&rfds, 1, -1); //永久阻塞
        switch(n)
        {
            case 0://超时
                std::cout << "time out ..." << std::endl;
                break;
            case -1://出错
                std::cerr << "poll error" << std::endl;
                break;
            default:
                std::cout << "有事件发生..." << std::endl;
                //因为当前只关心0号描述符,不需要轮询检测哪个文件描述符的事件发生
                if(rfds.revents & POLLIN)   //判断读取事件是否发生
                {
                    std::cout << rfds.fd << " 上面的读事件发生了" << std::endl;
                    char buffer[128];
                    //从标准输入里读取,读到buffer中,我们认为读取到的是字符串,所以少读一个字节,方便后续在字符串末尾添加\0
                    ssize_t s = read(0, buffer, sizeof(buffer)-1);
                    if(s > 0)
                    {
                        buffer[s] = '\0';
                        std::cout << "有人说# " << buffer << std::endl;
                    }
                }
                break;
        }
    }
    return 0;
}

演示:

情况1:当读取事件就绪,但是没有进行读取的时候:

image-20221005205035433

为什么只输入了一次数据,就一直显示有事件发生?

原因就是:数据在键盘输入了,回车也按下去了,但是没有人读取,这个数据在输入缓冲区当中,有事件发生就是:没有人读取,但是数据一直存在,poll就会一直通知你, 如何让他不再通知我? 上层把数据取走


timeout设为1000 :即1s就timeout一次 1s之内有事件就绪就返回,没有事件就绪就timeout

image-20221005205440426

timeout为0:非阻塞轮询

image-20221005205358754

timeout为-1:阻塞等待,有事件就绪了才返回

image-20221005205318861

poll服务器

为了方便后序使用,我们可以对套接字的创建,绑定,监听等进行封装在Sock.hpp文件里面

poll的工作流程和select是基本类似的,这里我们也实现一个简单poll服务器,该服务器也只是读取客户端发来的数据并进行打印


1)首先,我们需要创建套接字,完成绑定,监听的操作

2)poll服务器要做的就是不断调用poll函数,当事件就绪时对应执行某种动作即可,

  • 在poll服务器开始死循环调用poll函数之前,需要定义一个数组,该数组当中的每个位置都是一个struct pollfd结构体
  • 后续调用poll函数时会作为参数进行传入,先将数组当中每个位置初始化为无效,并将监听套接字添加到数组当中,服务器刚开始运行时只需要监视监听套接字的读事件是否就绪

3)此后,poll服务器就不断调用poll函数监视读事件是否就绪

  • 如果poll函数的返回值大于0,则说明poll函数调用成功,此时已经有文件描述符的读事件就绪,接下来就应该对就绪事件进行处理
  • 如果poll函数的返回值等于0,则说明timeout时间耗尽,此时直接准备进行下一次poll调用即可
  • 如果poll函数的返回值为-1,则说明poll调用失败,此时也让服务器准备进行下一次poll调用
    • 但实际应该进一步判断错误码,根据错误码来判断是否应该继续调用poll函数

局部代码:

#include <iostream>
#include <string>
#include <poll.h>
#include "Sock.hpp"
#define NUM 128 //数组长度
#define INVALID_FD -1 //无效fd
struct pollfd pollfds[NUM];

static void Usage(std::string proc)
{
    std::cout << "Usage: " << proc << " port" << std::endl;
}
//我们是后序这样启动服务器的: ./poll_server 端口号
int main(int argc,char* argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(1);
    }
    //创建套接字,绑定,监听
    uint16_t port = atoi(argv[1]);
    int listen_sock = Sock::Socket();
    Sock::Bind(listen_sock,port);
    Sock::Listen(listen_sock);

    //先将数组的所有位置的文件描述符设为无效
    for (int i = 0; i < num; i++)
    {
        fds[i].fd = INVALID_FD;
        fds[i].events = 0;
        fds[i].revents = 0;
	}

    //将监听套接字添加到数组0下标位置中,并关心其读事件
    pollfds[0].fd = listen_sock;
    pollfds[0].events = POLLIN;//关心listen_sock的读取事件是否就绪
    pollfds[0].revents = 0;

    for(;;)
    {
        int n = poll(pollfds, NUM, -1)
        switch (n) 
        {
            case 0: //超时
                std::cout << "poll timeout" << std::endl;
                break;
            case -1: //调用失败
                std::cerr << "poll failed" << std::endl;
                break;
            default://有事件就绪了
                std::cout << "有fd对应的事件就绪啦!" << std::endl;
                break;
        }
    }
    return 0;
}

事件处理

当poll检测到有文件描述符的读事件就绪,内核就会在其对应的struct pollfd结构中的revents成员中添加上读事件就绪(本质是和revents和一个宏进行或运算)并返回,接下来poll服务器就应该对就绪事件进行处理了,事件处理过程如下:

  • 首先遍历数组的元素, (struct pollfd结构体), 如果该结构当中的fd有效,且revents当中包含读事件就绪的宏,则说明该文件描述符的读事件就绪,接下来就需要进一步判断该文件描述符是监听套接字还是与客户端建立的套接字

    • 如果是监听套接字的读事件就绪:调用accept函数将底层建立好的连接获取上来
  • 注意:此时不能直接读取,因为有新链接到来,不代表有数据就绪,不代表能读取,如果此时没有数据,但是我们进行了读取,那么进程就会阻塞在那里进行等待,

    • 那么什么时候数据到来呢?不知道 ,可是,谁可以最清楚的知道那些fd上面可以读取了?poll!

    • 我们可以将获取到的套接字添加到数组当中,表示下一次调用poll函数时需要监视该套接字的读事件

    • 注意:因为这里将数组的大小是固定设置的,因此在将新获取连接对应的文件描述符添加到数组时,可能存在数组已满而添加失败,这时poll服务器只能将刚刚获取上来的连接对应的套接字进行关闭

  • 如果是与客户端建立的连接对应的文件描述符((普通文件描述符))读事件就绪

    • 调用read函数读取客户端发来的数据,并将读取到的数据在服务器端进行打印
      • 此时因为读取事件已经就绪了,所以调用read函数并不会阻塞
  • 如果在调用read函数时发现客户端将连接关闭或read函数调用失败

    • 则poll服务器也直接关闭对应的连接
      • 并将该连接对应的文件描述符从fds数组当中清除, 下一次调用poll函数时无需再监视该套接字的事件

poll_server.cc

#include <iostream>
#include <string>
#include <poll.h>
#include "Sock.hpp"

#define NUM 128 //数组长度
#define INVALID_FD -1 //无效fd

struct pollfd pollfds[NUM];

static void Usage(std::string proc)
{
    std::cout << "Usage: " << proc << " port" << std::endl;
}
//我们是后序这样启动服务器的: ./poll_server 端口号
int main(int argc,char* argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(1);
    }
    //创建套接字,绑定,监听
    uint16_t port = atoi(argv[1]);
    int listen_sock = Sock::Socket();
    Sock::Bind(listen_sock,port);
    Sock::Listen(listen_sock);

    //先将数组的所有位置的文件描述符设为无效
    for (int i = 0; i < NUM; i++)
    {
        pollfds[i].fd = INVALID_FD;
        pollfds[i].events = 0;
        pollfds[i].revents = 0;
	}

    //将监听套接字添加到数组0下标位置中,并关心其读事件
    pollfds[0].fd = listen_sock;
    pollfds[0].events = POLLIN;//关心listen_sock的读取事件是否就绪
    pollfds[0].revents = 0;

    for(;;)
    {
        int n = poll(pollfds, NUM, -1);//-1表示阻塞等待
        switch (n) 
        {
            case 0: //超时
                std::cout << "poll timeout" << std::endl;
                break;
            case -1: //调用失败
                std::cerr << "poll failed" << std::endl;
                break;
            default://有事件就绪了
                std::cout << "有fd对应的事件就绪啦!" << std::endl;
                //需要遍历数组,看是哪个文件描述符上的事件就绪了
                for(int i = 0;i<NUM;i++)
                {
                    if(pollfds[i].fd == INVALID_FD) //跳过无效的位置
                        continue;

                    if(pollfds[i].fd == listen_sock && pollfds[i].revents&POLLIN)//监听套接字上的读取事件就绪了
                    {
                        std::cout << "listen_sock: " << listen_sock << " 有了新的链接到来" << std::endl;

                        int sock = Sock::Accept(listen_sock);
                        if(sock>=0)
                        {
                            std::cout << "listen_sock: " << listen_sock << " 获取新的链接成功" << std::endl;    
                            //此时不能直接进行读取,因为新链接到来,不意味着有数据到来
                            //所以需要将新获取到的套接字添加到数组中,并关心其读事件是否就绪

                            //在数组中找一个没有被使用的位置
                            int pos = 1;
                            for(;pos<NUM;pos++)
                            {
                                if(pollfds[pos].fd  == INVALID_FD)
                                {
                                    break;
                                }
                            }
                            //跳出循环有两种情况:
                            if(pos < NUM)//case1: 找到了一个位置没有被使用
                            {
                                std::cout << "新链接: " << sock << " 已经被添加到了数组[" << pos << "]的位置" << std::endl;
                                pollfds[pos].fd = sock;
                                pollfds[pos].events = POLLIN;
                            }
                            else//case2: 没有空位置,说明服务器已经满载,没法处理新的请求了
                            {
                                std::cout << "服务器已经满载了,关闭新的链接" << std::endl;
                                close(sock);
                            }
                        }
                    }
                    else if(pollfds[i].revents &POLLIN) //普通文件描述符上的读事件就绪
                    {
                        std::cout << "sock: " << pollfds[i].fd << " 上面有了读事件,可以读取了" << std::endl;
                        char buffer[1024];
                        ssize_t s = read(pollfds[i].fd,buffer,sizeof(buffer)-1);
                        if(s>0)
                        {
                            buffer[s] = '\0';
                            std::cout << "client[ " << pollfds[i].fd << "]# " << buffer << std::endl;
                        }
                        else if(s == 0) //对端关闭了链接
                        {
                            std::cout << "sock: " << pollfds[i].fd << "关闭了, client退出啦!" << std::endl;
                            close(pollfds[i].fd);
                            std::cout << "已经在数组下标pollfds[" << i << "]"<< "中,去掉了sock: " << pollfds[i].fd<< std::endl;
                            //连接对应的文件描述符从fds数组当中清除,下一次调用poll函数时无需再监视该套接字的事件
                            pollfds[i].fd = INVALID_FD;
                            pollfds[i].events = 0;
		                    pollfds[i].revents = 0;
                        }
                        else//读取失败
                        {
                            close(pollfds[i].fd);
                            std::cout << "已经在数组下标pollfds[" << i << "]" << "中,去掉了sock: " << pollfds[i].fd << std::endl;
                            //连接对应的文件描述符从fds数组当中清除,下一次调用poll函数时无需再监视该套接字的事件
                            pollfds[i].fd = INVALID_FD;
                            pollfds[i].events = 0;
		                    pollfds[i].revents = 0;
                        }
                    }
                }
                break;
        }
    }
    return 0;
}

测试

我们编写的poll服务器在调用poll函数时,将timeout的值设置成了-1,因此运行服务器后如果没有客户端发来连接请求,那么服务器就会在调用poll函数后进行阻塞等待

image-20221005203953410


最初poll的数组中只有监听套接字, 当我们用telnet工具连接poll服务器后,poll服务器调用的poll函数在检测到监听套接字的读事件就绪后就会调用accept获取建立好的连接,然后将新获取到的套接字添加到数组中,并关心其读事件是否就绪, 当数据就绪了,就会通知我们,此时客户端发来的数据也能够成功被poll服务器收到并进行打印输出

image-20221005204245492


此外,poll服务器也是一个单进程服务器,但是它也可以同时为多个客户端提供服务

image-20221005204335745

当服务器端检测到客户端退出后,也会关闭对应的连接,并将对应的套接字从数组当中清除

image-20221005204358409


poll的优点

  • struct pollfd结构当中包含了events和revents,将fd和事件集合到结构体中,使用更方便
    • 相当于将select的输入输出型参数进行分离,避免原始数据被修改, 因此在每次调用poll之前,不需要像select一样重新对参数进行设置,
  • 改fd集为pollfd数组,理论上解决了监视fd的个数有上限的问题
    • poll可监控的文件描述符数量没有限制
  • poll也可以同时等待多个文件描述符,能够提高IO的效率

说明一下:

  • 虽然代码中将fds数组的元素个数定义为1024,但fds数组的大小是可以继续增大的,poll函数能够帮你监视多少个文件描述符是由传入poll函数的第二个参数决定的,
  • 而fd_set类型只有1024个比特位,因此select函数最多只能监视1024个文件描述符,

poll的缺点

  • 和select函数一样,当poll返回后,仍要轮询检测pollfd数组中就绪的事件
  • 每次调用poll,都需要把大量的struct pollfd结构从用户态拷贝到内核态,这个开销也会随着poll监视的文件描述符数目的增多而增大,
  • 同时每次调用poll都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大,

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

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

相关文章

DDos攻击概述

1.def&#xff1a; 通过大规模互联网流量淹没目标服务器或其周边基础设施&#xff0c;以破坏目标服务器、服务或网络正常流量的恶意行为 目标服务器类比作商店&#xff1b; 网络的正常流量类比作顾客&#xff1b; 此恶意行为便相当于让一堆小混混装成正常顾客涌入商店&…

软件自动化测试有什么优势?自动化测试框架有哪些?

一、 软件自动化测试的优势 在软件测试过程中&#xff0c;自动化测试不断被提高到更高的级别&#xff0c;以提高测试效率以及降低测试成本。 1.节省时间和成本 手动测试需要耗费大量的时间和精力&#xff0c;而自动化测试可以在较短时间内执行多次测试&#xff0c;并且可以在…

Alibaba Sentinel整合SpringBoot,为微服务保驾护航!

前言 随着微服务的流行&#xff0c;服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件&#xff0c;主要以流量为切入点&#xff0c;从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来…

08-05 应用层设计

伸缩性的架构设计——服务器集群的伸缩性 DNS负载均衡 DNS服务器将访问的域名转发到对应的网关&#xff0c;网关层做反向代理。 利用消息组件对业务场景进行解耦 适合用消息组件解耦的场景 长任务&#xff08;时间长&#xff0c;逻辑复杂&#xff0c;可异步&#xff09…

React Antv G2Plot 「指标拆解图」 前端可视化实战 实现渲染、重置、筛选功能

背景 实现对指定数据的「指标拆解图」 渲染&#xff0c;并且可以根据筛选项进行变化。 任务分解 antv 的图表&#xff0c;以及请求后端的载荷对传入的数据结构有严格要求 一个工具函数将后端接口返回的数据格式化成 antv 图表要求的格式一个工具函数将前端提交的请求数据格…

Copilot入门

文章目录 简介安装初试快捷键取消订阅参考文献 简介 Copilot 是一款 GitHub 和 OpenAI 合作开发的 AI 结对编程工具&#xff0c;支持 Visual Studio、Neovim、VS Code、JetBrains IDEs&#xff0c;用于自动补全代码。 本文以 Python PyCharm 为例。 安装 GitHub Copilot&am…

【数据结构】栈及其实现

目录 &#x1f920;前言 什么是栈&#xff1f; 栈的定义及初始化 栈的定义 栈的初始化 栈的判空 栈顶压栈 栈顶出栈 栈的数据个数 栈的销毁 完整代码 总结 &#x1f920;前言 学了相当长一段时间的链表&#xff0c;总算是跨过了一个阶段。从今天开始我们将进入栈和…

什么是AIGC

AIGC是人工智能创意生成的缩写&#xff08;Artificial Intelligence Generated Creativity&#xff09;&#xff0c;指的是利用人工智能技术实现的创意生成。通俗来说&#xff0c;就是让机器产生新颖、独特且有创造性的作品或方案&#xff0c;例如音乐、绘画、视频、文本等等。…

Sui基金会宣布面向APAC的Office Hours计划

诚挚邀请构建者与Sui基金会的Growth团队一起开启“Office Hours”计划&#xff0c;共同努力&#xff0c;迈向业务增长的下一步。 Sui基金会致力于推动Sui在全球范围内的普及。为此&#xff0c;我们通过积极支持开发人员的开发者资助计划、Builder House和大使计划在Sui上开始…

如何高效运行Omniverse,无惧本地硬件压力

无论是创造能够表达原始情感的逼真数字人&#xff0c;还是构建身临其境的虚拟世界&#xff0c;全球设计、工程、创意和其他行业的人士都在通过3D工作流&#xff0c;突破技术壁垒并拓展创意可能&#xff0c;让虚拟世界和现实世界交融与观众产生共鸣。 而在众多连接未来创作内容的…

ES6中函数新增的方式方法

1.1函数形参的默认值 1.1.1基本用法 ES6 之前&#xff0c;不能直接为函数的参数指定默认值&#xff0c;只能采用变通的方法。如下代码&#xff1a; function func(x,y){y y || "tom";console.log(x,y);}func("hello"); //hello tomfunc("…

数据库标准化之核心结局变量集(COS)

COS介绍 核心结果集&#xff08;COS&#xff09;是在特定健康状况下进行的所有临床试验中应测量和报告的一组最少结局变量集。COS数据库是这些变量集的集合&#xff0c;这些集合是通过循证和迭代过程开发的。该数据库由COMET倡议维护&#xff0c;该计划是研究人员&#xff0c;…

Linux 系统上 C 程序的编译与调试 2 总结

1.gcc分布编译链接 &#xff08;1) 预编译 : gcc -E main.c -o main.i (2) 编译&#xff1a; gcc -S main.i -o main.s (3) 汇编&#xff1a; gcc -c main.s -o main.o (4) 链接&#xff1a; gcc main.o -o main gcc -E hello.c -o hello.i #预处理 gcc -S hello.i -o h…

【FMC134】ADC12DJ3200之4通道3.2GSPS(2 通道6.4GSPS) 12 位AD高速采集子卡设计原理图及调试经验

板卡概述 FMC134 是一款4 通道3.2GSPS&#xff08;或者配置成2 通道6.4GSPS&#xff09;采样率的12 位AD 采集FMC子卡模块&#xff0c;该板卡为FMC标准&#xff0c;符合VITA57.4 规范&#xff0c;可以作为一个理想的IO 模块耦合至FPGA 前端&#xff0c;射频模拟信号数字化后通过…

【多线程进阶一】常见的锁策略

目录 一、常见的锁策略 &#x1f345;1、常见的锁策略 &#x1f345;2、Synchronized实现了哪些锁策略&#xff1f; &#x1f345;3、自旋锁的实现方式—CAS &#xff08;1&#xff09;CAS伪代码 &#xff08;2&#xff09;演示 使用CAS方式来实现自增操作&#xff1a; &am…

Linux 系统修改环境变量的方法

1. Linux 系统修改环境变量 正常情况下改变环境变量可以修改的文件有两类&#xff1a; 第一类是 “系统的全局环境变量”&#xff0c;修改之后可作用于整个系统包含的所有用户都会生效&#xff1b;(文件&#xff1a;/etc/profile) 第二类是 “局部环境变量”&#xff0c;也就…

Linux C程序多文件编译

C程序多文件编译 在Linux平台C编程&#xff0c;实现求两数最大值和两数之和的功能 1.编写add.c wysDESKTOP-2OU3HRV:~/mycode/day02$ vi add.c1 int add(int x,int y)2 {3 return x y;4 } 2.编写头文件add.h wysDESKTOP-2OU3HRV:~/mycode/day02$ vi add.hint add(…

Python 实验五 字符串与正则表达式

1.输入一个字符串&#xff0c;将该字符串中下标为偶数的字符组成新串并通过字符串格式化方式显示。 a input("请输入一个字符串&#xff1a;") b a[1::2] print("老串为&#xff1a;%a&#xff0c;新串为&#xff1a;%a"%(a,b))2.编写程序&#xff0c;生…

LInux系统下使用git的三板斧以及报错处理

LInux使用git 我们应该知道git是什么东西&#xff0c;还有git的三板斧&#xff0c;git是一个工具&#xff0c;使用git来将文件上传到代码仓库 文章目录 LInux使用gitcloneaddcommitpush查看当前git的状态 clone 第一步找到你创建的仓库&#xff0c;然后复制http地址&#xf…

目前可以用的ChatGPT网址大全

ChatGPT是一个基于人工智能的聊天机器人&#xff0c;可以与用户进行自然语言交互。它可以回答各种问题&#xff0c;提供有用的信息和建议&#xff0c;还可以进行闲聊和娱乐。ChatGPT使用最先进的自然语言处理技术&#xff0c;可以理解和解释人类语言&#xff0c;从而提供准确和…